mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-11-04 18:13:42 +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>
		
			
				
	
	
		
			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}
 | 
						|
}
 |