mirror of
https://gitea.com/Lydanne/buildx.git
synced 2025-05-18 09:17:49 +08:00

With changes to the lazy evaluation, the evaluation order is no longer fixed - this means that we can follow long and confusing paths to get to an error. Because of the co-recursive nature of the lazy evaluation, we need to take special care that the original HCL diagnostics are not discarded and are preserved so that the original source of the error can be detected. Preserving the full trace is not necessary, and probably not useful to the user - all of the file that is not lazily loaded will be eagerly loaded after all struct blocks are loaded - so the error would be found regardless. Signed-off-by: Justin Chadwell <me@jedevc.com> (cherry picked from commit fbb4f4dec86541dd243e99816ef84b3a4d4741c2)
146 lines
3.4 KiB
Go
146 lines
3.4 KiB
Go
package hclparser
|
|
|
|
import (
|
|
"reflect"
|
|
"unsafe"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func funcCalls(exp hcl.Expression) ([]string, hcl.Diagnostics) {
|
|
node, ok := exp.(hclsyntax.Node)
|
|
if !ok {
|
|
fns, err := jsonFuncCallsRecursive(exp)
|
|
if err != nil {
|
|
return nil, wrapErrorDiagnostic("Invalid expression", err, exp.Range().Ptr(), exp.Range().Ptr())
|
|
}
|
|
return fns, nil
|
|
}
|
|
|
|
var funcnames []string
|
|
hcldiags := hclsyntax.VisitAll(node, func(n hclsyntax.Node) hcl.Diagnostics {
|
|
if fe, ok := n.(*hclsyntax.FunctionCallExpr); ok {
|
|
funcnames = append(funcnames, fe.Name)
|
|
}
|
|
return nil
|
|
})
|
|
if hcldiags.HasErrors() {
|
|
return nil, hcldiags
|
|
}
|
|
return funcnames, nil
|
|
}
|
|
|
|
func jsonFuncCallsRecursive(exp hcl.Expression) ([]string, error) {
|
|
je, ok := exp.(jsonExp)
|
|
if !ok {
|
|
return nil, errors.Errorf("invalid expression type %T", exp)
|
|
}
|
|
m := map[string]struct{}{}
|
|
for _, e := range elementExpressions(je, exp) {
|
|
if err := appendJSONFuncCalls(e, m); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
arr := make([]string, 0, len(m))
|
|
for n := range m {
|
|
arr = append(arr, n)
|
|
}
|
|
return arr, nil
|
|
}
|
|
|
|
func appendJSONFuncCalls(exp hcl.Expression, m map[string]struct{}) error {
|
|
v := reflect.ValueOf(exp)
|
|
if v.Kind() != reflect.Ptr || v.IsNil() {
|
|
return errors.Errorf("invalid json expression kind %T %v", exp, v.Kind())
|
|
}
|
|
src := v.Elem().FieldByName("src")
|
|
if src.IsZero() {
|
|
return errors.Errorf("%v has no property src", v.Elem().Type())
|
|
}
|
|
if src.Kind() != reflect.Interface {
|
|
return errors.Errorf("%v src is not interface: %v", src.Type(), src.Kind())
|
|
}
|
|
src = src.Elem()
|
|
if src.IsNil() {
|
|
return nil
|
|
}
|
|
if src.Kind() == reflect.Ptr {
|
|
src = src.Elem()
|
|
}
|
|
if src.Kind() != reflect.Struct {
|
|
return errors.Errorf("%v is not struct: %v", src.Type(), src.Kind())
|
|
}
|
|
|
|
// hcl/v2/json/ast#stringVal
|
|
val := src.FieldByName("Value")
|
|
if !val.IsValid() || val.IsZero() {
|
|
return nil
|
|
}
|
|
rng := src.FieldByName("SrcRange")
|
|
if rng.IsZero() {
|
|
return nil
|
|
}
|
|
var stringVal struct {
|
|
Value string
|
|
SrcRange hcl.Range
|
|
}
|
|
|
|
if !val.Type().AssignableTo(reflect.ValueOf(stringVal.Value).Type()) {
|
|
return nil
|
|
}
|
|
if !rng.Type().AssignableTo(reflect.ValueOf(stringVal.SrcRange).Type()) {
|
|
return nil
|
|
}
|
|
// reflect.Set does not work for unexported fields
|
|
stringVal.Value = *(*string)(unsafe.Pointer(val.UnsafeAddr()))
|
|
stringVal.SrcRange = *(*hcl.Range)(unsafe.Pointer(rng.UnsafeAddr()))
|
|
|
|
expr, diags := hclsyntax.ParseExpression([]byte(stringVal.Value), stringVal.SrcRange.Filename, stringVal.SrcRange.Start)
|
|
if diags.HasErrors() {
|
|
return nil
|
|
}
|
|
|
|
fns, err := funcCalls(expr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, fn := range fns {
|
|
m[fn] = struct{}{}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type jsonExp interface {
|
|
ExprList() []hcl.Expression
|
|
ExprMap() []hcl.KeyValuePair
|
|
}
|
|
|
|
func elementExpressions(je jsonExp, exp hcl.Expression) []hcl.Expression {
|
|
list := je.ExprList()
|
|
if len(list) != 0 {
|
|
exp := make([]hcl.Expression, 0, len(list))
|
|
for _, e := range list {
|
|
if je, ok := e.(jsonExp); ok {
|
|
exp = append(exp, elementExpressions(je, e)...)
|
|
}
|
|
}
|
|
return exp
|
|
}
|
|
kvlist := je.ExprMap()
|
|
if len(kvlist) != 0 {
|
|
exp := make([]hcl.Expression, 0, len(kvlist)*2)
|
|
for _, p := range kvlist {
|
|
exp = append(exp, p.Key)
|
|
if je, ok := p.Value.(jsonExp); ok {
|
|
exp = append(exp, elementExpressions(je, p.Value)...)
|
|
}
|
|
}
|
|
return exp
|
|
}
|
|
return []hcl.Expression{exp}
|
|
}
|