go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mqlc/mqlc.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package mqlc
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"regexp"
    10  	"sort"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/hashicorp/go-version"
    15  	"github.com/lithammer/fuzzysearch/fuzzy"
    16  	"github.com/rs/zerolog/log"
    17  	"go.mondoo.com/cnquery"
    18  	"go.mondoo.com/cnquery/llx"
    19  	"go.mondoo.com/cnquery/mqlc/parser"
    20  	"go.mondoo.com/cnquery/providers-sdk/v1/resources"
    21  	"go.mondoo.com/cnquery/types"
    22  	"go.mondoo.com/cnquery/utils/sortx"
    23  )
    24  
    25  type variable struct {
    26  	name string
    27  	ref  uint64
    28  	typ  types.Type
    29  	// callback is run when the variable is used by the compiler.
    30  	// This is particularly useful when dealing with pre-defined
    31  	// variables which may or may not be used in the actual code
    32  	// (like `key` and `value`). One use-case is to tell the
    33  	// block compiler that its bound value has been used.
    34  	callback func()
    35  }
    36  
    37  type varmap struct {
    38  	blockref uint64
    39  	parent   *varmap
    40  	vars     map[string]variable
    41  }
    42  
    43  func newvarmap(blockref uint64, parent *varmap) *varmap {
    44  	return &varmap{
    45  		blockref: blockref,
    46  		parent:   parent,
    47  		vars:     map[string]variable{},
    48  	}
    49  }
    50  
    51  func (vm *varmap) lookup(name string) (variable, bool) {
    52  	if v, ok := vm.vars[name]; ok {
    53  		return v, true
    54  	}
    55  	if vm.parent == nil {
    56  		return variable{}, false
    57  	}
    58  	return vm.parent.lookup(name)
    59  }
    60  
    61  func (vm *varmap) add(name string, v variable) {
    62  	vm.vars[name] = v
    63  }
    64  
    65  func (vm *varmap) len() int {
    66  	return len(vm.vars)
    67  }
    68  
    69  type compilerConfig struct {
    70  	Schema          llx.Schema
    71  	UseAssetContext bool
    72  }
    73  
    74  func NewConfig(schema llx.Schema, features cnquery.Features) compilerConfig {
    75  	return compilerConfig{
    76  		Schema:          schema,
    77  		UseAssetContext: features.IsActive(cnquery.MQLAssetContext),
    78  	}
    79  }
    80  
    81  type compiler struct {
    82  	compilerConfig
    83  
    84  	Result    *llx.CodeBundle
    85  	Binding   *variable
    86  	vars      *varmap
    87  	parent    *compiler
    88  	block     *llx.Block
    89  	blockRef  uint64
    90  	blockDeps []uint64
    91  	props     map[string]*llx.Primitive
    92  	comment   string
    93  
    94  	// a standalone code is one that doesn't call any of its bindings
    95  	// examples:
    96  	//   file(xyz).content          is standalone
    97  	//   file(xyz).content == _     is not
    98  	standalone bool
    99  
   100  	// helps chaining of builtin calls like `if (..) else if (..) else ..`
   101  	prevID string
   102  }
   103  
   104  func (c *compiler) isInMyBlock(ref uint64) bool {
   105  	return (ref >> 32) == (c.blockRef >> 32)
   106  }
   107  
   108  func (c *compiler) addChunk(chunk *llx.Chunk) {
   109  	c.block.AddChunk(c.Result.CodeV2, c.blockRef, chunk)
   110  }
   111  
   112  func (c *compiler) popChunk() (prev *llx.Chunk, isEntrypoint bool, isDatapoint bool) {
   113  	return c.block.PopChunk(c.Result.CodeV2, c.blockRef)
   114  }
   115  
   116  func (c *compiler) addArgumentPlaceholder(typ types.Type, checksum string) {
   117  	c.block.AddArgumentPlaceholder(c.Result.CodeV2, c.blockRef, typ, checksum)
   118  }
   119  
   120  func (c *compiler) tailRef() uint64 {
   121  	return c.block.TailRef(c.blockRef)
   122  }
   123  
   124  // Creates a new block and its accompanying compiler.
   125  // It carries a set of variables that apply within the scope of this block.
   126  func (c *compiler) newBlockCompiler(binding *variable) compiler {
   127  	code := c.Result.CodeV2
   128  	block, ref := code.AddBlock()
   129  
   130  	vars := map[string]variable{}
   131  	blockDeps := []uint64{}
   132  	if binding != nil {
   133  		vars["_"] = *binding
   134  		blockDeps = append(blockDeps, binding.ref)
   135  	}
   136  
   137  	return compiler{
   138  		compilerConfig: c.compilerConfig,
   139  		Result:         c.Result,
   140  		Binding:        binding,
   141  		blockDeps:      blockDeps,
   142  		vars:           newvarmap(ref, c.vars),
   143  		parent:         c,
   144  		block:          block,
   145  		blockRef:       ref,
   146  		props:          c.props,
   147  		standalone:     true,
   148  	}
   149  }
   150  
   151  func findFuzzy(name string, names []string) fuzzy.Ranks {
   152  	suggested := fuzzy.RankFind(name, names)
   153  
   154  	sort.SliceStable(suggested, func(i, j int) bool {
   155  		a := suggested[i]
   156  		b := suggested[j]
   157  		ha := strings.HasPrefix(a.Target, name)
   158  		hb := strings.HasPrefix(b.Target, name)
   159  		if ha && hb {
   160  			// here it's just going by order, because it has the prefix
   161  			return a.Target < b.Target
   162  		}
   163  		if ha {
   164  			return true
   165  		}
   166  		if hb {
   167  			return false
   168  		}
   169  		// unlike here where we sort by fuzzy distance
   170  		return a.Distance < b.Distance
   171  	})
   172  
   173  	return suggested
   174  }
   175  
   176  func addResourceSuggestions(schema llx.Schema, name string, res *llx.CodeBundle) {
   177  	resourceInfos := schema.AllResources()
   178  	names := make([]string, len(resourceInfos))
   179  	i := 0
   180  	for key := range resourceInfos {
   181  		names[i] = key
   182  		i++
   183  	}
   184  
   185  	suggested := findFuzzy(name, names)
   186  
   187  	res.Suggestions = make([]*llx.Documentation, len(suggested))
   188  	var info *resources.ResourceInfo
   189  	for i := range suggested {
   190  		field := suggested[i].Target
   191  		info = resourceInfos[field]
   192  		if info != nil {
   193  			res.Suggestions[i] = &llx.Documentation{
   194  				Field: field,
   195  				Title: info.Title,
   196  				Desc:  info.Desc,
   197  			}
   198  		} else {
   199  			res.Suggestions[i] = &llx.Documentation{
   200  				Field: field,
   201  			}
   202  		}
   203  	}
   204  }
   205  
   206  func addFieldSuggestions(fields map[string]llx.Documentation, fieldName string, res *llx.CodeBundle) {
   207  	names := make([]string, len(fields))
   208  	i := 0
   209  	for key := range fields {
   210  		names[i] = key
   211  		i++
   212  	}
   213  
   214  	suggested := findFuzzy(fieldName, names)
   215  
   216  	res.Suggestions = make([]*llx.Documentation, len(suggested))
   217  	for i := range suggested {
   218  		info := fields[suggested[i].Target]
   219  		res.Suggestions[i] = &info
   220  	}
   221  }
   222  
   223  // func (c *compiler) addAccessor(call *Call, typ types.Type) types.Type {
   224  // 	binding := c.Result.Code.ChunkIndex()
   225  // 	ownerType := c.Result.Code.LastChunk().Type(c.Result.Code)
   226  
   227  // 	if call.Accessors != nil {
   228  // 		arg, err := c.compileValue(call.Accessors)
   229  // 		if err != nil {
   230  // 			panic(err.Error())
   231  // 		}
   232  
   233  // 		c.Result.Code.AddChunk(&llx.Chunk{
   234  // 			Call: llx.Chunk_FUNCTION,
   235  // 			Id:   "[]",
   236  // 			Function: &llx.Function{
   237  // 				Type:    string(ownerType.Child()),
   238  // 				Binding: binding,
   239  // 				Args:    []*llx.Primitive{arg},
   240  // 			},
   241  // 		})
   242  
   243  // 		return ownerType.Child()
   244  // 	}
   245  
   246  // 	if call.Params != nil {
   247  // 		panic("We have not yet implemented adding more unnamed function calls")
   248  // 	}
   249  
   250  // 	panic("Tried to add accessor calls for a call that has no accessors or params")
   251  // }
   252  
   253  // func (c *compiler) addAccessorCalls(calls []*Call, typ types.Type) types.Type {
   254  // 	if calls == nil || len(calls) == 0 {
   255  // 		return typ
   256  // 	}
   257  // 	for i := range calls {
   258  // 		typ = c.addAccessorCall(calls[i], typ)
   259  // 	}
   260  // 	return typ
   261  // }
   262  
   263  func blockCallType(typ types.Type, schema llx.Schema) types.Type {
   264  	if typ.IsArray() {
   265  		return types.Array(types.Block)
   266  	}
   267  
   268  	if !typ.IsResource() {
   269  		return types.Block
   270  	}
   271  
   272  	info := schema.Lookup(typ.ResourceName())
   273  	if info != nil && info.ListType != "" {
   274  		return types.Array(types.Block)
   275  	}
   276  
   277  	return types.Block
   278  }
   279  
   280  // compileBlock on a context
   281  func (c *compiler) compileBlock(expressions []*parser.Expression, typ types.Type, bindingRef uint64) (types.Type, error) {
   282  	// For resource, users may indicate to query all fields. It also works for list of resources.
   283  	// This is a special case which is handled here:
   284  	if len(expressions) == 1 && (typ.IsResource() || (typ.IsArray() && typ.Child().IsResource())) {
   285  		x := expressions[0]
   286  
   287  		// Special handling for the glob operation on resource fields. It will
   288  		// try to grab all valid fields and return them.
   289  		if x.Operand != nil && x.Operand.Value != nil && x.Operand.Value.Ident != nil && *(x.Operand.Value.Ident) == "*" {
   290  			var fields map[string]llx.Documentation
   291  			if typ.IsArray() {
   292  				fields = availableGlobFields(c, typ.Child(), false)
   293  			} else {
   294  				fields = availableGlobFields(c, typ, true)
   295  			}
   296  
   297  			expressions = []*parser.Expression{}
   298  			keys := sortx.Keys(fields)
   299  			for _, v := range keys {
   300  				name := v
   301  				expressions = append(expressions, &parser.Expression{
   302  					Operand: &parser.Operand{
   303  						Value: &parser.Value{Ident: &name},
   304  					},
   305  				})
   306  			}
   307  		}
   308  	}
   309  
   310  	refs, err := c.blockExpressions(expressions, typ, bindingRef)
   311  	if err != nil {
   312  		return types.Nil, err
   313  	}
   314  	if refs.block == 0 {
   315  		return typ, nil
   316  	}
   317  
   318  	args := []*llx.Primitive{llx.FunctionPrimitive(refs.block)}
   319  	for _, v := range refs.deps {
   320  		if c.isInMyBlock(v) {
   321  			args = append(args, llx.RefPrimitiveV2(v))
   322  		}
   323  	}
   324  	c.blockDeps = append(c.blockDeps, refs.deps...)
   325  
   326  	resultType := blockCallType(typ, c.Schema)
   327  	c.addChunk(&llx.Chunk{
   328  		Call: llx.Chunk_FUNCTION,
   329  		Id:   "{}",
   330  		Function: &llx.Function{
   331  			Type:    string(resultType),
   332  			Binding: refs.binding,
   333  			Args:    args,
   334  		},
   335  	})
   336  
   337  	return resultType, nil
   338  }
   339  
   340  func (c *compiler) compileIfBlock(expressions []*parser.Expression, chunk *llx.Chunk) (types.Type, error) {
   341  	// if `else { .. }` is called, we reset the prevID to indicate there is no
   342  	// more chaining happening
   343  	if c.prevID == "else" {
   344  		c.prevID = ""
   345  	}
   346  
   347  	blockCompiler := c.newBlockCompiler(c.Binding)
   348  	err := blockCompiler.compileExpressions(expressions)
   349  	if err != nil {
   350  		return types.Nil, err
   351  	}
   352  	blockCompiler.updateEntrypoints(false)
   353  
   354  	block := blockCompiler.block
   355  
   356  	// we set this to true, so that we can decide how to handle all following expressions
   357  	if block.SingleValue {
   358  		c.block.SingleValue = true
   359  	}
   360  
   361  	// insert a body if we are in standalone mode to return a value
   362  	if len(block.Chunks) == 0 && c.standalone {
   363  		blockCompiler.addChunk(&llx.Chunk{
   364  			Call:      llx.Chunk_PRIMITIVE,
   365  			Primitive: llx.NilPrimitive,
   366  		})
   367  		blockCompiler.addChunk(&llx.Chunk{
   368  			Call: llx.Chunk_FUNCTION,
   369  			Id:   "return",
   370  			Function: &llx.Function{
   371  				Type: string(types.Nil),
   372  				// FIXME: this is gonna crash on c.Binding == nil
   373  				Args: []*llx.Primitive{llx.RefPrimitiveV2(blockCompiler.blockRef | 1)},
   374  			},
   375  		})
   376  		block.SingleValue = true
   377  		block.Entrypoints = []uint64{blockCompiler.blockRef | 2}
   378  	}
   379  
   380  	depArgs := []*llx.Primitive{}
   381  	for _, v := range blockCompiler.blockDeps {
   382  		if c.isInMyBlock(v) {
   383  			depArgs = append(depArgs, llx.RefPrimitiveV2(v))
   384  		}
   385  	}
   386  
   387  	// the last chunk in this case is the `if` function call
   388  	chunk.Function.Args = append(chunk.Function.Args,
   389  		llx.FunctionPrimitive(blockCompiler.blockRef),
   390  		llx.ArrayPrimitive(depArgs, types.Ref),
   391  	)
   392  
   393  	c.blockDeps = append(c.blockDeps, blockCompiler.blockDeps...)
   394  
   395  	if len(block.Chunks) != 0 {
   396  		var typeToEnforce types.Type
   397  		if c.block.SingleValue {
   398  			last := block.LastChunk()
   399  			typeToEnforce = last.Type()
   400  		} else {
   401  			typeToEnforce = types.Block
   402  		}
   403  
   404  		t, ok := types.Enforce(types.Type(chunk.Function.Type), typeToEnforce)
   405  		if !ok {
   406  			return types.Nil, errors.New("mismatched return type for child block of if-function; make sure all return types are the same")
   407  		}
   408  		chunk.Function.Type = string(t)
   409  	}
   410  
   411  	return types.Nil, nil
   412  }
   413  
   414  func (c *compiler) compileSwitchCase(expression *parser.Expression, bind *variable, chunk *llx.Chunk) error {
   415  	// for the default case, we get a nil expression
   416  	if expression == nil {
   417  		chunk.Function.Args = append(chunk.Function.Args, llx.BoolPrimitive(true))
   418  		return nil
   419  	}
   420  
   421  	prevBind := c.Binding
   422  	c.Binding = bind
   423  	defer func() {
   424  		c.Binding = prevBind
   425  	}()
   426  
   427  	argValue, err := c.compileExpression(expression)
   428  	if err != nil {
   429  		return err
   430  	}
   431  	chunk.Function.Args = append(chunk.Function.Args, argValue)
   432  	return nil
   433  }
   434  
   435  func (c *compiler) compileSwitchBlock(expressions []*parser.Expression, chunk *llx.Chunk) (types.Type, error) {
   436  	// determine if there is a binding
   437  	// i.e. something inside of those `switch( ?? )` calls
   438  	var bind *variable
   439  	arg := chunk.Function.Args[0]
   440  
   441  	// we have to pop the switch chunk from the compiler stack, because it needs
   442  	// to be the last item on the stack. otherwise the last reference (top of stack)
   443  	// will not be pointing to it and an additional entrypoint will be generated
   444  
   445  	lastRef := c.block.TailRef(c.blockRef)
   446  	if c.block.LastChunk() != chunk {
   447  		return types.Nil, errors.New("failed to compile switch statement, it wasn't on the top of the compile stack")
   448  	}
   449  
   450  	c.block.Chunks = c.block.Chunks[:len(c.block.Chunks)-1]
   451  	c.Result.CodeV2.Checksums[lastRef] = ""
   452  
   453  	defer func() {
   454  		c.addChunk(chunk)
   455  	}()
   456  
   457  	if types.Type(arg.Type) != types.Unset {
   458  		if types.Type(arg.Type) == types.Ref {
   459  			val, ok := arg.RefV2()
   460  			if !ok {
   461  				return types.Nil, errors.New("could not resolve references of switch argument")
   462  			}
   463  			bind = &variable{
   464  				typ: types.Type(arg.Type),
   465  				ref: val,
   466  			}
   467  		} else {
   468  			c.addChunk(&llx.Chunk{
   469  				Call:      llx.Chunk_PRIMITIVE,
   470  				Primitive: arg,
   471  			})
   472  			ref := c.block.TailRef(c.blockRef)
   473  			bind = &variable{typ: types.Type(arg.Type), ref: ref}
   474  		}
   475  	}
   476  
   477  	var lastType types.Type = types.Unset
   478  	for i := 0; i < len(expressions); i += 2 {
   479  		err := c.compileSwitchCase(expressions[i], bind, chunk)
   480  		if err != nil {
   481  			return types.Nil, err
   482  		}
   483  
   484  		// compile the block of this case/default
   485  		if i+1 >= len(expressions) {
   486  			return types.Nil, errors.New("missing block expression in calling `case`/`default` statement")
   487  		}
   488  
   489  		block := expressions[i+1]
   490  		if *block.Operand.Value.Ident != "{}" {
   491  			return types.Nil, errors.New("expected block inside case/default statement")
   492  		}
   493  
   494  		expressions := block.Operand.Block
   495  
   496  		blockCompiler := c.newBlockCompiler(bind)
   497  		// TODO(jaym): Discuss with dom: don't understand what
   498  		// standalone is used for here
   499  		blockCompiler.standalone = true
   500  
   501  		err = blockCompiler.compileExpressions(expressions)
   502  		if err != nil {
   503  			return types.Nil, err
   504  		}
   505  		blockCompiler.updateEntrypoints(false)
   506  
   507  		// TODO(jaym): Discuss with dom: v1 seems to hardcore this as
   508  		// single valued
   509  		blockCompiler.block.SingleValue = true
   510  
   511  		// Check the types
   512  		lastChunk := blockCompiler.block.LastChunk()
   513  		if lastType == types.Unset {
   514  			lastType = lastChunk.Type()
   515  		} else {
   516  			// If the last type is not the same as the current type, then
   517  			// we set the type to any
   518  			if lastChunk.Type() != lastType {
   519  				lastType = types.Any
   520  			}
   521  			chunk.Function.Type = string(lastType)
   522  		}
   523  
   524  		depArgs := []*llx.Primitive{}
   525  		for _, v := range blockCompiler.blockDeps {
   526  			if c.isInMyBlock(v) {
   527  				depArgs = append(depArgs, llx.RefPrimitiveV2(v))
   528  			}
   529  		}
   530  
   531  		chunk.Function.Args = append(chunk.Function.Args,
   532  			llx.FunctionPrimitive(blockCompiler.blockRef),
   533  			llx.ArrayPrimitive(depArgs, types.Ref),
   534  		)
   535  
   536  		c.blockDeps = append(c.blockDeps, blockCompiler.blockDeps...)
   537  
   538  	}
   539  
   540  	// FIXME: I'm pretty sure we don't need this ...
   541  	// c.Result.Code.RefreshChunkChecksum(chunk)
   542  
   543  	return types.Nil, nil
   544  }
   545  
   546  func (c *compiler) compileUnboundBlock(expressions []*parser.Expression, chunk *llx.Chunk) (types.Type, error) {
   547  	switch chunk.Id {
   548  	case "if":
   549  		t, err := c.compileIfBlock(expressions, chunk)
   550  		if err == nil {
   551  			code := c.Result.CodeV2
   552  			code.Checksums[c.tailRef()] = chunk.ChecksumV2(c.blockRef, code)
   553  		}
   554  		return t, err
   555  
   556  	case "switch":
   557  		return c.compileSwitchBlock(expressions, chunk)
   558  	default:
   559  		return types.Nil, errors.New("don't know how to compile unbound block on call `" + chunk.Id + "`")
   560  	}
   561  }
   562  
   563  type blockRefs struct {
   564  	// reference to the block that was created
   565  	block uint64
   566  	// references to all dependencies of the block
   567  	deps []uint64
   568  	// if it's a standalone bloc
   569  	isStandalone bool
   570  	// any changes to binding that might have occurred during the block compilation
   571  	binding uint64
   572  }
   573  
   574  // evaluates the given expressions on a non-array resource (eg: no `[]int` nor `groups`)
   575  // and creates a function, whose reference is returned
   576  func (c *compiler) blockOnResource(expressions []*parser.Expression, typ types.Type, binding uint64) (blockRefs, error) {
   577  	blockCompiler := c.newBlockCompiler(nil)
   578  	blockCompiler.block.AddArgumentPlaceholder(blockCompiler.Result.CodeV2,
   579  		blockCompiler.blockRef, typ, blockCompiler.Result.CodeV2.Checksums[binding])
   580  	v := variable{
   581  		ref: blockCompiler.blockRef | 1,
   582  		typ: typ,
   583  		callback: func() {
   584  			blockCompiler.standalone = false
   585  		},
   586  	}
   587  	blockCompiler.vars.add("_", v)
   588  	blockCompiler.Binding = &v
   589  
   590  	err := blockCompiler.compileExpressions(expressions)
   591  	if err != nil {
   592  
   593  		// FIXME: DEPRECATED, remove in v8.0 vv
   594  		// We are introducing this workaround to make old list block calls possible
   595  		// after introducing the new mechanism. I.e. in the new paradigm you
   596  		// only write `users { * }` to get all children. But in the previous mode
   597  		// we supported `users { list }`. Support this ladder example with a brute-
   598  		// force approach here. This entire handling can be removed once we hit v8.
   599  		tailChunk := c.Result.CodeV2.Chunk(binding)
   600  		if tailChunk.Id == "list" && tailChunk.Function != nil && tailChunk.Function.Binding != 0 {
   601  			// pop off the last block if the compiler created it
   602  			if blockCompiler.blockRef != 0 {
   603  				c.Result.CodeV2.Blocks = c.Result.CodeV2.Blocks[0 : len(c.Result.CodeV2.Blocks)-1]
   604  			}
   605  			// pop off the list call
   606  			nuRef := tailChunk.Function.Binding
   607  			nuRefChunk := c.Result.CodeV2.Chunk(nuRef)
   608  			nuTyp := nuRefChunk.Type()
   609  			c.Result.CodeV2.Block(binding).PopChunk(c.Result.CodeV2, binding)
   610  
   611  			blockCompiler := c.newBlockCompiler(nil)
   612  			blockCompiler.block.AddArgumentPlaceholder(blockCompiler.Result.CodeV2,
   613  				blockCompiler.blockRef, nuTyp, blockCompiler.Result.CodeV2.Checksums[nuRef])
   614  			v := variable{
   615  				ref: blockCompiler.blockRef | 1,
   616  				typ: nuTyp,
   617  			}
   618  			blockCompiler.vars.add("_", v)
   619  			blockCompiler.Binding = &v
   620  			retryErr := blockCompiler.compileExpressions(expressions)
   621  			if retryErr != nil {
   622  				return blockRefs{}, err
   623  			}
   624  
   625  			blockCompiler.updateEntrypoints(false)
   626  			childType := tailChunk.Type().Label()
   627  			log.Warn().Msg("deprecated call: Blocks on list resources now only affect child elements. " +
   628  				"You are trying to call a block on '" + nuRefChunk.Id + "' with fields that do not exist in its child elements " +
   629  				"(i.e. in " + childType + ").")
   630  			return blockRefs{
   631  				block:        blockCompiler.blockRef,
   632  				deps:         blockCompiler.blockDeps,
   633  				isStandalone: blockCompiler.standalone,
   634  				binding:      nuRef,
   635  			}, nil
   636  		} else {
   637  			// ^^  (and retain the part inside the else clause)
   638  
   639  			return blockRefs{}, err
   640  		}
   641  	}
   642  
   643  	blockCompiler.updateEntrypoints(false)
   644  	blockCompiler.updateLabels()
   645  
   646  	return blockRefs{
   647  		block:        blockCompiler.blockRef,
   648  		deps:         blockCompiler.blockDeps,
   649  		isStandalone: blockCompiler.standalone,
   650  		binding:      binding,
   651  	}, nil
   652  }
   653  
   654  // blockExpressions evaluates the given expressions as if called by a block and
   655  // returns the compiled function reference
   656  func (c *compiler) blockExpressions(expressions []*parser.Expression, typ types.Type, binding uint64) (blockRefs, error) {
   657  	if len(expressions) == 0 {
   658  		return blockRefs{}, nil
   659  	}
   660  
   661  	if typ.IsArray() {
   662  		return c.blockOnResource(expressions, typ.Child(), binding)
   663  	}
   664  
   665  	// when calling a block {} on an array resource, we expand it to all its list
   666  	// items and apply the block to those only
   667  	if typ.IsResource() {
   668  		info := c.Schema.Lookup(typ.ResourceName())
   669  		if info != nil && info.ListType != "" {
   670  			typ = types.Type(info.ListType)
   671  			c.addChunk(&llx.Chunk{
   672  				Call: llx.Chunk_FUNCTION,
   673  				Id:   "list",
   674  				Function: &llx.Function{
   675  					Binding: binding,
   676  					Type:    string(types.Array(typ)),
   677  				},
   678  			})
   679  			binding = c.tailRef()
   680  		}
   681  	}
   682  
   683  	return c.blockOnResource(expressions, typ, binding)
   684  }
   685  
   686  // Returns the singular return type of the given block.
   687  // Error if the block has multiple entrypoints (i.e. non singular)
   688  func (c *compiler) blockType(ref uint64) (types.Type, error) {
   689  	block := c.Result.CodeV2.Block(ref)
   690  	if block == nil {
   691  		return types.Nil, errors.New("cannot find block for block ref " + strconv.Itoa(int(ref>>32)))
   692  	}
   693  
   694  	if len(block.Entrypoints) != 1 {
   695  		return types.Nil, errors.New("block should only return 1 value (got: " + strconv.Itoa(len(block.Entrypoints)) + ")")
   696  	}
   697  
   698  	ep := block.Entrypoints[0]
   699  	chunk := block.Chunks[(ep&0xFFFFFFFF)-1]
   700  	// TODO: this could be a ref! not sure if we can handle that... maybe dereference?
   701  	return chunk.Type(), nil
   702  }
   703  
   704  func (c *compiler) dereferenceType(val *llx.Primitive) (types.Type, error) {
   705  	valType := types.Type(val.Type)
   706  	if types.Type(val.Type) != types.Ref {
   707  		return valType, nil
   708  	}
   709  
   710  	ref, ok := val.RefV2()
   711  	if !ok {
   712  		return types.Nil, errors.New("found a reference type that doesn't return a reference value")
   713  	}
   714  
   715  	chunk := c.Result.CodeV2.Chunk(ref)
   716  	if chunk.Primitive == val {
   717  		return types.Nil, errors.New("recursive reference connections detected")
   718  	}
   719  
   720  	if chunk.Primitive != nil {
   721  		return c.dereferenceType(chunk.Primitive)
   722  	}
   723  
   724  	valType = chunk.DereferencedTypeV2(c.Result.CodeV2)
   725  	return valType, nil
   726  }
   727  
   728  func (c *compiler) unnamedArgs(callerLabel string, init *resources.Init, args []*parser.Arg) ([]*llx.Primitive, error) {
   729  	if len(args) > len(init.Args) {
   730  		return nil, errors.New("Called " + callerLabel +
   731  			" with too many arguments (expected " + strconv.Itoa(len(init.Args)) +
   732  			" but got " + strconv.Itoa(len(args)) + ")")
   733  	}
   734  
   735  	// add all calls to the chunk stack
   736  	// collect all their types and call references
   737  	res := make([]*llx.Primitive, len(args)*2)
   738  
   739  	for idx := range args {
   740  		arg := args[idx]
   741  
   742  		v, err := c.compileExpression(arg.Value)
   743  		if err != nil {
   744  			return nil, errors.New("addResourceCall error: " + err.Error())
   745  		}
   746  
   747  		vType := types.Type(v.Type)
   748  		if vType == types.Ref {
   749  			vType, err = c.dereferenceType(v)
   750  			if err != nil {
   751  				return nil, err
   752  			}
   753  		}
   754  
   755  		expected := init.Args[idx]
   756  		expectedType := types.Type(expected.Type)
   757  		if vType != expectedType {
   758  			// TODO: We are looking for dict types to see if we can type-cast them
   759  			// This needs massive improvements to dynamically cast them in LLX.
   760  			// For a full description see: https://gitlab.com/mondoolabs/mondoo/-/issues/241
   761  			// This is ONLY a temporary workaround which works in a few cases:
   762  			if vType == types.Dict && expectedType == types.String {
   763  				// we are good, LLX will handle it
   764  			} else {
   765  				return nil, errors.New("Incorrect type on argument " + strconv.Itoa(idx) +
   766  					" in " + callerLabel + ": expected " + expectedType.Label() +
   767  					", got: " + vType.Label())
   768  			}
   769  		}
   770  
   771  		res[idx*2] = llx.StringPrimitive(expected.Name)
   772  		res[idx*2+1] = v
   773  	}
   774  
   775  	return res, nil
   776  }
   777  
   778  func (c *compiler) unnamedResourceArgs(resource *resources.ResourceInfo, args []*parser.Arg) ([]*llx.Primitive, error) {
   779  	if resource.Init == nil {
   780  		return nil, errors.New("cannot find init call for resource " + resource.Id)
   781  	}
   782  
   783  	return c.unnamedArgs("resource "+resource.Name, resource.Init, args)
   784  }
   785  
   786  // resourceArgs turns the list of arguments for the resource into a list of
   787  // primitives that are used as arguments to initialize that resource
   788  // only works if len(args) > 0 !!
   789  // only works if args are either ALL named or not named !!
   790  func (c *compiler) resourceArgs(resource *resources.ResourceInfo, args []*parser.Arg) ([]*llx.Primitive, error) {
   791  	if args[0].Name == "" {
   792  		return c.unnamedResourceArgs(resource, args)
   793  	}
   794  
   795  	res := make([]*llx.Primitive, len(args)*2)
   796  	for idx := range args {
   797  		arg := args[idx]
   798  		field, ok := resource.Fields[arg.Name]
   799  		if !ok {
   800  			return nil, errors.New("resource " + resource.Name + " does not have a field named " + arg.Name)
   801  		}
   802  
   803  		v, err := c.compileExpression(arg.Value)
   804  		if err != nil {
   805  			return nil, errors.New("resourceArgs error: " + err.Error())
   806  		}
   807  
   808  		vt, err := c.dereferenceType(v)
   809  		if err != nil {
   810  			return nil, err
   811  		}
   812  
   813  		ft := types.Type(field.Type)
   814  		if vt != ft {
   815  			return nil, errors.New("Wrong type for field " + arg.Name + " in resource " + resource.Name + ": expected " + ft.Label() + ", got " + vt.Label())
   816  		}
   817  
   818  		res[idx*2] = llx.StringPrimitive(arg.Name)
   819  		res[idx*2+1] = v
   820  	}
   821  
   822  	return res, nil
   823  }
   824  
   825  func (c *compiler) compileBuiltinFunction(h *compileHandler, id string, binding *variable, call *parser.Call) (types.Type, error) {
   826  	if h.compile != nil {
   827  		return h.compile(c, binding.typ, binding.ref, id, call)
   828  	}
   829  
   830  	var args []*llx.Primitive
   831  
   832  	if call != nil {
   833  		for idx := range call.Function {
   834  			arg := call.Function[idx]
   835  			x, err := c.compileExpression(arg.Value)
   836  			if err != nil {
   837  				return types.Nil, err
   838  			}
   839  			if x != nil {
   840  				args = append(args, x)
   841  			}
   842  		}
   843  	}
   844  
   845  	if err := h.signature.Validate(args, c); err != nil {
   846  		return types.Nil, err
   847  	}
   848  
   849  	resType := h.typ(binding.typ)
   850  	c.addChunk(&llx.Chunk{
   851  		Call: llx.Chunk_FUNCTION,
   852  		Id:   id,
   853  		Function: &llx.Function{
   854  			Type:    string(resType),
   855  			Binding: binding.ref,
   856  			Args:    args,
   857  		},
   858  	})
   859  	return resType, nil
   860  }
   861  
   862  func filterTrailingNullArgs(call *parser.Call) *parser.Call {
   863  	if call == nil {
   864  		return call
   865  	}
   866  
   867  	res := parser.Call{
   868  		Comments: call.Comments,
   869  		Ident:    call.Ident,
   870  		Function: call.Function,
   871  		Accessor: call.Accessor,
   872  	}
   873  
   874  	args := call.Function
   875  	if len(args) == 0 {
   876  		return &res
   877  	}
   878  
   879  	lastIdx := len(args) - 1
   880  	x := args[lastIdx]
   881  	if x.Value.IsEmpty() {
   882  		res.Function = args[0:lastIdx]
   883  	}
   884  
   885  	return &res
   886  }
   887  
   888  func filterEmptyExpressions(expressions []*parser.Expression) []*parser.Expression {
   889  	res := []*parser.Expression{}
   890  	for i := range expressions {
   891  		exp := expressions[i]
   892  		if exp.IsEmpty() {
   893  			continue
   894  		}
   895  		res = append(res, exp)
   896  	}
   897  
   898  	return res
   899  }
   900  
   901  type fieldPath []string
   902  
   903  // TODO: embed this into the Schema LookupField call!
   904  func (c *compiler) findField(resource *resources.ResourceInfo, fieldName string) (fieldPath, []*resources.Field, bool) {
   905  	fieldInfo, ok := resource.Fields[fieldName]
   906  	if ok {
   907  		return fieldPath{fieldName}, []*resources.Field{fieldInfo}, true
   908  	}
   909  
   910  	for _, f := range resource.Fields {
   911  		if f.IsEmbedded {
   912  			typ := types.Type(f.Type)
   913  			nextResource := c.Schema.Lookup(typ.ResourceName())
   914  			if nextResource == nil {
   915  				continue
   916  			}
   917  			childFieldPath, childFieldInfos, ok := c.findField(nextResource, fieldName)
   918  			if ok {
   919  				fp := make(fieldPath, len(childFieldPath)+1)
   920  				fieldInfos := make([]*resources.Field, len(childFieldPath)+1)
   921  				fp[0] = f.Name
   922  				fieldInfos[0] = f
   923  				for i, n := range childFieldPath {
   924  					fp[i+1] = n
   925  				}
   926  				for i, f := range childFieldInfos {
   927  					fieldInfos[i+1] = f
   928  				}
   929  				return fp, fieldInfos, true
   930  			}
   931  		}
   932  	}
   933  	return nil, nil, false
   934  }
   935  
   936  // compile a bound identifier to its binding
   937  // example: user { name } , where name is compiled bound to the user
   938  // it will return false if it cannot bind the identifier
   939  func (c *compiler) compileBoundIdentifier(id string, binding *variable, call *parser.Call) (bool, types.Type, error) {
   940  	if c.UseAssetContext {
   941  		return c.compileBoundIdentifierWithMqlCtx(id, binding, call)
   942  	} else {
   943  		return c.compileBoundIdentifierWithoutMqlCtx(id, binding, call)
   944  	}
   945  }
   946  
   947  func (c *compiler) compileBoundIdentifierWithMqlCtx(id string, binding *variable, call *parser.Call) (bool, types.Type, error) {
   948  	typ := binding.typ
   949  
   950  	if typ.IsResource() {
   951  		resource, _ := c.Schema.LookupField(typ.ResourceName(), id)
   952  		if resource == nil {
   953  			return true, types.Nil, errors.New("cannot find resource that is called by '" + id + "' of type " + typ.Label())
   954  		}
   955  
   956  		fieldPath, fieldinfos, ok := c.findField(resource, id)
   957  		if ok {
   958  			fieldinfo := fieldinfos[len(fieldinfos)-1]
   959  
   960  			if call != nil && len(call.Function) > 0 && !fieldinfo.IsImplicitResource {
   961  				return true, types.Nil, errors.New("cannot call resource field with arguments yet")
   962  			}
   963  
   964  			c.Result.MinMondooVersion = getMinMondooVersion(c.Schema, c.Result.MinMondooVersion, typ.ResourceName(), id)
   965  
   966  			// this only happens when we call a field of a bridging resource,
   967  			// in which case we don't call the field (since there is nothing to do)
   968  			// and instead we call the resource directly:
   969  			typ := types.Type(fieldinfo.Type)
   970  			if fieldinfo.IsImplicitResource {
   971  				name := typ.ResourceName()
   972  
   973  				if binding.ref == 0 {
   974  					c.addChunk(&llx.Chunk{
   975  						Call: llx.Chunk_FUNCTION,
   976  						Id:   name,
   977  					})
   978  				} else {
   979  					f := &llx.Function{
   980  						Type: string(types.Resource(name)),
   981  						Args: []*llx.Primitive{
   982  							llx.RefPrimitiveV2(binding.ref),
   983  						},
   984  					}
   985  					if call != nil && len(call.Function) > 0 {
   986  						realResource := c.Schema.Lookup(typ.ResourceName())
   987  						if realResource == nil {
   988  							return true, types.Nil, errors.New("could not find resource " + typ.ResourceName())
   989  						}
   990  						args, err := c.resourceArgs(realResource, call.Function)
   991  						if err != nil {
   992  							return true, types.Nil, err
   993  						}
   994  						f.Args = append(f.Args, args...)
   995  					}
   996  
   997  					c.addChunk(&llx.Chunk{
   998  						Call:     llx.Chunk_FUNCTION,
   999  						Id:       "createResource",
  1000  						Function: f,
  1001  					})
  1002  				}
  1003  
  1004  				// the new ID is now the full resource call, which is not what the
  1005  				// field is originally labeled when we get it, so we have to fix it
  1006  				checksum := c.Result.CodeV2.Checksums[c.tailRef()]
  1007  				c.Result.Labels.Labels[checksum] = id
  1008  				return true, typ, nil
  1009  			}
  1010  
  1011  			lastRef := binding.ref
  1012  			for i, p := range fieldPath {
  1013  				c.addChunk(&llx.Chunk{
  1014  					Call: llx.Chunk_FUNCTION,
  1015  					Id:   p,
  1016  					Function: &llx.Function{
  1017  						Type:    fieldinfos[i].Type,
  1018  						Binding: lastRef,
  1019  					},
  1020  				})
  1021  				lastRef = c.tailRef()
  1022  			}
  1023  
  1024  			return true, typ, nil
  1025  		}
  1026  	}
  1027  
  1028  	h, _ := builtinFunction(typ, id)
  1029  	if h != nil {
  1030  		call = filterTrailingNullArgs(call)
  1031  		typ, err := c.compileBuiltinFunction(h, id, binding, call)
  1032  		return true, typ, err
  1033  	}
  1034  
  1035  	return false, types.Nil, nil
  1036  }
  1037  
  1038  // compileBoundIdentifierWithoutMqlCtx will compile a bound identifier without being able
  1039  // create implicit resources with context attached. The reason this is needed is because
  1040  // that feature requires use of a new global function 'createResource'. We cannot start
  1041  // automatically adding that to compiled queries without breaking existing clients
  1042  func (c *compiler) compileBoundIdentifierWithoutMqlCtx(id string, binding *variable, call *parser.Call) (bool, types.Type, error) {
  1043  	typ := binding.typ
  1044  
  1045  	if typ.IsResource() {
  1046  		resource, fieldinfo := c.Schema.LookupField(typ.ResourceName(), id)
  1047  		if resource == nil {
  1048  			return true, types.Nil, errors.New("cannot find resource that is called by '" + id + "' of type " + typ.Label())
  1049  		}
  1050  
  1051  		if fieldinfo != nil {
  1052  			if call != nil && len(call.Function) > 0 {
  1053  				return true, types.Nil, errors.New("cannot call resource field with arguments yet")
  1054  			}
  1055  
  1056  			if fieldinfo.IsEmbedded {
  1057  				return true, types.Nil, fmt.Errorf("field '%s' on '%s' requires the MQLAssetContext feature", id, typ.Label())
  1058  			}
  1059  
  1060  			c.Result.MinMondooVersion = getMinMondooVersion(c.Schema, c.Result.MinMondooVersion, typ.ResourceName(), id)
  1061  
  1062  			// this only happens when we call a field of a bridging resource,
  1063  			// in which case we don't call the field (since there is nothing to do)
  1064  			// and instead we call the resource directly:
  1065  			typ := types.Type(fieldinfo.Type)
  1066  			if fieldinfo.IsImplicitResource {
  1067  				name := typ.ResourceName()
  1068  				c.addChunk(&llx.Chunk{
  1069  					Call: llx.Chunk_FUNCTION,
  1070  					Id:   name,
  1071  				})
  1072  
  1073  				// the new ID is now the full resource call, which is not what the
  1074  				// field is originally labeled when we get it, so we have to fix it
  1075  				checksum := c.Result.CodeV2.Checksums[c.tailRef()]
  1076  				c.Result.Labels.Labels[checksum] = id
  1077  				return true, typ, nil
  1078  			}
  1079  
  1080  			c.addChunk(&llx.Chunk{
  1081  				Call: llx.Chunk_FUNCTION,
  1082  				Id:   id,
  1083  				Function: &llx.Function{
  1084  					Type:    fieldinfo.Type,
  1085  					Binding: binding.ref,
  1086  				},
  1087  			})
  1088  			return true, typ, nil
  1089  		}
  1090  	}
  1091  
  1092  	h, _ := builtinFunction(typ, id)
  1093  	if h != nil {
  1094  		call = filterTrailingNullArgs(call)
  1095  		typ, err := c.compileBuiltinFunction(h, id, binding, call)
  1096  		return true, typ, err
  1097  	}
  1098  
  1099  	return false, types.Nil, nil
  1100  }
  1101  
  1102  // compile a resource from an identifier, trying to find the longest matching resource
  1103  // and execute all call functions if there are any
  1104  func (c *compiler) compileResource(id string, calls []*parser.Call) (bool, []*parser.Call, types.Type, error) {
  1105  	resource := c.Schema.Lookup(id)
  1106  	if resource == nil {
  1107  		return false, nil, types.Nil, nil
  1108  	}
  1109  
  1110  	for len(calls) > 0 && calls[0].Ident != nil {
  1111  		nuID := id + "." + (*calls[0].Ident)
  1112  		nuResource := c.Schema.Lookup(nuID)
  1113  		if nuResource == nil {
  1114  			break
  1115  		}
  1116  		resource, id = nuResource, nuID
  1117  		calls = calls[1:]
  1118  	}
  1119  
  1120  	var call *parser.Call
  1121  	if len(calls) > 0 && calls[0].Function != nil {
  1122  		call = calls[0]
  1123  		calls = calls[1:]
  1124  	}
  1125  
  1126  	c.Result.MinMondooVersion = getMinMondooVersion(c.Schema, c.Result.MinMondooVersion, id, "")
  1127  
  1128  	typ, err := c.addResource(id, resource, call)
  1129  	return true, calls, typ, err
  1130  }
  1131  
  1132  func (c *compiler) addResource(id string, resource *resources.ResourceInfo, call *parser.Call) (types.Type, error) {
  1133  	var function *llx.Function
  1134  	var err error
  1135  	typ := types.Resource(id)
  1136  
  1137  	if call != nil && len(call.Function) > 0 {
  1138  		function = &llx.Function{Type: string(typ)}
  1139  		function.Args, err = c.resourceArgs(resource, call.Function)
  1140  		if err != nil {
  1141  			return types.Nil, err
  1142  		}
  1143  	}
  1144  
  1145  	c.addChunk(&llx.Chunk{
  1146  		Call:     llx.Chunk_FUNCTION,
  1147  		Id:       id,
  1148  		Function: function,
  1149  	})
  1150  	return typ, nil
  1151  }
  1152  
  1153  // compileIdentifier within a context of a binding
  1154  // 1. global f(): 			expect, ...
  1155  // 2. global resource: 	sshd, sshd.config
  1156  // 3. bound field: 			user { name }
  1157  // x. called field: 		user.name <= not in this scope
  1158  func (c *compiler) compileIdentifier(id string, callBinding *variable, calls []*parser.Call) ([]*parser.Call, types.Type, error) {
  1159  	var call *parser.Call
  1160  	restCalls := calls
  1161  	if len(calls) > 0 && calls[0].Function != nil {
  1162  		call = calls[0]
  1163  		restCalls = calls[1:]
  1164  	}
  1165  
  1166  	var typ types.Type
  1167  	var err error
  1168  	var found bool
  1169  	if callBinding != nil {
  1170  		// special handling for the `self` operator
  1171  		if id == "_" {
  1172  			c.standalone = false
  1173  
  1174  			if len(restCalls) == 0 {
  1175  				return restCalls, callBinding.typ, nil
  1176  			}
  1177  
  1178  			nextCall := restCalls[0]
  1179  
  1180  			if nextCall.Ident != nil {
  1181  				calls = restCalls[1:]
  1182  				call = nil
  1183  				if len(calls) > 0 && calls[0].Function != nil {
  1184  					call = calls[0]
  1185  				}
  1186  
  1187  				found, typ, err = c.compileBoundIdentifier(*nextCall.Ident, callBinding, call)
  1188  				if found {
  1189  					if call != nil {
  1190  						return restCalls[2:], typ, err
  1191  					}
  1192  					return restCalls[1:], typ, err
  1193  				}
  1194  				return nil, types.Nil, errors.New("could not find call _." + (*nextCall.Ident))
  1195  			}
  1196  
  1197  			if nextCall.Accessor != nil {
  1198  				// turn accessor into a regular function and call that
  1199  				fCall := &parser.Call{Function: []*parser.Arg{{Value: nextCall.Accessor}}}
  1200  
  1201  				// accessors are always builtin functions
  1202  				h, _ := builtinFunction(callBinding.typ.Underlying(), "[]")
  1203  
  1204  				if h == nil {
  1205  					// this is the case when we deal with special resources that expand
  1206  					// this type of builtin function
  1207  					var bind *variable
  1208  					h, bind, err = c.compileImplicitBuiltin(callBinding.typ, "[]")
  1209  					if err != nil || h == nil {
  1210  						return nil, types.Nil, errors.New("cannot find '[]' function on type " + callBinding.typ.Label())
  1211  					}
  1212  					callBinding = bind
  1213  				}
  1214  
  1215  				typ, err = c.compileBuiltinFunction(h, "[]", callBinding, fCall)
  1216  				if err != nil {
  1217  					return nil, types.Nil, err
  1218  				}
  1219  
  1220  				if call != nil && len(calls) > 0 {
  1221  					calls = calls[1:]
  1222  				}
  1223  
  1224  				return restCalls[1:], typ, nil
  1225  			}
  1226  
  1227  			return nil, types.Nil, errors.New("not sure how to handle implicit calls around `_`")
  1228  		}
  1229  
  1230  		found, typ, err = c.compileBoundIdentifier(id, callBinding, call)
  1231  		if found {
  1232  			c.standalone = false
  1233  			return restCalls, typ, err
  1234  		}
  1235  	} // end bound functions
  1236  
  1237  	if id == "props" {
  1238  		return c.compileProps(call, restCalls, c.Result)
  1239  	}
  1240  
  1241  	f := operatorsCompilers[id]
  1242  	if f != nil {
  1243  		typ, err := f(c, id, call)
  1244  		return restCalls, typ, err
  1245  	}
  1246  
  1247  	variable, ok := c.vars.lookup(id)
  1248  	if ok {
  1249  		if variable.callback != nil {
  1250  			variable.callback()
  1251  		}
  1252  
  1253  		c.blockDeps = append(c.blockDeps, variable.ref)
  1254  		c.addChunk(&llx.Chunk{
  1255  			Call:      llx.Chunk_PRIMITIVE,
  1256  			Primitive: llx.RefPrimitiveV2(variable.ref),
  1257  		})
  1258  
  1259  		checksum := c.Result.CodeV2.Checksums[c.tailRef()]
  1260  		c.Result.Labels.Labels[checksum] = variable.name
  1261  		return restCalls, variable.typ, nil
  1262  	}
  1263  
  1264  	found, restCalls, typ, err = c.compileResource(id, calls)
  1265  	if found {
  1266  		return restCalls, typ, err
  1267  	}
  1268  
  1269  	// Support easy accessors for dicts and maps, e.g:
  1270  	// json.params { A.B.C } => json.params { _["A"]["B"]["C"] }
  1271  	if callBinding != nil && callBinding.typ == types.Dict {
  1272  		c.addChunk(&llx.Chunk{
  1273  			Call: llx.Chunk_FUNCTION,
  1274  			Id:   "[]",
  1275  			Function: &llx.Function{
  1276  				Type:    string(callBinding.typ),
  1277  				Binding: callBinding.ref,
  1278  				Args:    []*llx.Primitive{llx.StringPrimitive(id)},
  1279  			},
  1280  		})
  1281  		c.standalone = false
  1282  		return restCalls, callBinding.typ, err
  1283  	}
  1284  
  1285  	// suggestions
  1286  	if callBinding == nil {
  1287  		addResourceSuggestions(c.Schema, id, c.Result)
  1288  		return nil, types.Nil, errors.New("cannot find resource for identifier '" + id + "'")
  1289  	}
  1290  	addFieldSuggestions(availableFields(c, callBinding.typ), id, c.Result)
  1291  	return nil, types.Nil, errors.New("cannot find field or resource '" + id + "' in block for type '" + c.Binding.typ.Label() + "'")
  1292  }
  1293  
  1294  // compileProps handles built-in properties for this code
  1295  // we will use any properties defined at the compiler-level as type-indicators
  1296  func (c *compiler) compileProps(call *parser.Call, calls []*parser.Call, res *llx.CodeBundle) ([]*parser.Call, types.Type, error) {
  1297  	if call != nil && len(call.Function) != 0 {
  1298  		return nil, types.Nil, errors.New("'props' is not a function")
  1299  	}
  1300  
  1301  	if len(calls) == 0 {
  1302  		return nil, types.Nil, errors.New("called 'props' without a property, please provide the name you are trying to access")
  1303  	}
  1304  
  1305  	nextCall := calls[0]
  1306  	restCalls := calls[1:]
  1307  
  1308  	if nextCall.Ident == nil {
  1309  		return nil, types.Nil, errors.New("please call 'props' with the name of the property you are trying to access")
  1310  	}
  1311  
  1312  	name := *nextCall.Ident
  1313  	prim, ok := c.props[name]
  1314  	if !ok {
  1315  		keys := make(map[string]llx.Documentation, len(c.props))
  1316  		for key, prim := range c.props {
  1317  			keys[key] = llx.Documentation{
  1318  				Field: key,
  1319  				Title: key + " (" + types.Type(prim.Type).Label() + ")",
  1320  			}
  1321  		}
  1322  
  1323  		addFieldSuggestions(keys, name, res)
  1324  
  1325  		return nil, types.Nil, errors.New("cannot find property '" + name + "', please define it first")
  1326  	}
  1327  
  1328  	c.addChunk(&llx.Chunk{
  1329  		Call: llx.Chunk_PROPERTY,
  1330  		Id:   name,
  1331  		Primitive: &llx.Primitive{
  1332  			Type: prim.Type,
  1333  		},
  1334  	})
  1335  
  1336  	res.Props[name] = string(prim.Type)
  1337  
  1338  	return restCalls, types.Type(prim.Type), nil
  1339  }
  1340  
  1341  // compileValue takes an AST value and compiles it
  1342  func (c *compiler) compileValue(val *parser.Value) (*llx.Primitive, error) {
  1343  	if val.Bool != nil {
  1344  		return llx.BoolPrimitive(bool(*val.Bool)), nil
  1345  	}
  1346  
  1347  	if val.Int != nil {
  1348  		return llx.IntPrimitive(int64(*val.Int)), nil
  1349  	}
  1350  
  1351  	if val.Float != nil {
  1352  		return llx.FloatPrimitive(float64(*val.Float)), nil
  1353  	}
  1354  
  1355  	if val.String != nil {
  1356  		return llx.StringPrimitive(*val.String), nil
  1357  	}
  1358  
  1359  	if val.Regex != nil {
  1360  		re := string(*val.Regex)
  1361  		_, err := regexp.Compile(re)
  1362  		if err != nil {
  1363  			return nil, errors.New("failed to compile regular expression '" + re + "': " + err.Error())
  1364  		}
  1365  		return llx.RegexPrimitive(re), nil
  1366  	}
  1367  
  1368  	if val.Array != nil {
  1369  		arr := make([]*llx.Primitive, len(val.Array))
  1370  		var err error
  1371  		for i := range val.Array {
  1372  			e := val.Array[i]
  1373  			arr[i], err = c.compileExpression(e)
  1374  			if err != nil {
  1375  				return nil, err
  1376  			}
  1377  		}
  1378  
  1379  		return &llx.Primitive{
  1380  			Type:  string(llx.ArrayTypeV2(arr, c.Result.CodeV2)),
  1381  			Array: arr,
  1382  		}, nil
  1383  	}
  1384  
  1385  	if val.Map != nil {
  1386  		mapRes := make(map[string]*llx.Primitive, len(val.Map))
  1387  		var resType types.Type
  1388  
  1389  		for k, v := range val.Map {
  1390  			vv, err := c.compileExpression(v)
  1391  			if err != nil {
  1392  				return nil, err
  1393  			}
  1394  			if types.Type(vv.Type) != resType {
  1395  				if resType == "" {
  1396  					resType = types.Type(vv.Type)
  1397  				} else if resType != types.Any {
  1398  					resType = types.Any
  1399  				}
  1400  			}
  1401  			mapRes[k] = vv
  1402  		}
  1403  
  1404  		if resType == "" {
  1405  			resType = types.Unset
  1406  		}
  1407  
  1408  		return &llx.Primitive{
  1409  			Type: string(types.Map(types.String, resType)),
  1410  			Map:  mapRes,
  1411  		}, nil
  1412  	}
  1413  
  1414  	return llx.NilPrimitive, nil
  1415  }
  1416  
  1417  func (c *compiler) compileOperand(operand *parser.Operand) (*llx.Primitive, error) {
  1418  	var err error
  1419  	var res *llx.Primitive
  1420  	var typ types.Type
  1421  	var ref uint64
  1422  
  1423  	calls := operand.Calls
  1424  	c.comment = operand.Comments
  1425  
  1426  	// value:        bool | string | regex | number | array | map | ident
  1427  	// so all simple values are compiled into primitives and identifiers
  1428  	// into function calls
  1429  	if operand.Value.Ident == nil {
  1430  		res, err = c.compileValue(operand.Value)
  1431  		if err != nil {
  1432  			return nil, err
  1433  		}
  1434  		typ = types.Type(res.Type)
  1435  
  1436  		if len(calls) > 0 {
  1437  			c.addChunk(&llx.Chunk{
  1438  				Call: llx.Chunk_PRIMITIVE,
  1439  				// no ID for standalone
  1440  				Primitive: res,
  1441  			})
  1442  			ref = c.tailRef()
  1443  			res = llx.RefPrimitiveV2(ref)
  1444  		}
  1445  	} else {
  1446  		id := *operand.Value.Ident
  1447  		orgcalls := calls
  1448  		calls, typ, err = c.compileIdentifier(id, c.Binding, calls)
  1449  		if err != nil {
  1450  			return nil, err
  1451  		}
  1452  
  1453  		ref = c.tailRef()
  1454  		if id == "_" && len(orgcalls) == 0 {
  1455  			ref = c.Binding.ref
  1456  		}
  1457  
  1458  		res = llx.RefPrimitiveV2(ref)
  1459  	}
  1460  
  1461  	// operand:      value [ call | accessor | '.' ident ]+ [ block ]
  1462  	// dealing with all call types
  1463  	for len(calls) > 0 {
  1464  		call := calls[0]
  1465  		if call.Function != nil {
  1466  			return nil, errors.New("don't know how to compile chained functions just yet")
  1467  		}
  1468  
  1469  		if call.Comments != "" {
  1470  			c.comment = call.Comments
  1471  		}
  1472  
  1473  		if call.Accessor != nil {
  1474  			// turn accessor into a regular function and call that
  1475  			fCall := &parser.Call{Function: []*parser.Arg{{Value: call.Accessor}}}
  1476  			relBinding := &variable{typ: typ, ref: ref}
  1477  
  1478  			// accessors are always builtin functions
  1479  			h, _ := builtinFunction(typ.Underlying(), "[]")
  1480  
  1481  			if h == nil {
  1482  				// this is the case when we deal with special resources that expand
  1483  				// this type of builtin function
  1484  				var bind *variable
  1485  				h, bind, err = c.compileImplicitBuiltin(typ, "[]")
  1486  				if err != nil || h == nil {
  1487  					return nil, errors.New("cannot find '[]' function on type " + typ.Label())
  1488  				}
  1489  				relBinding = bind
  1490  			}
  1491  
  1492  			typ, err = c.compileBuiltinFunction(h, "[]", relBinding, fCall)
  1493  			if err != nil {
  1494  				return nil, err
  1495  			}
  1496  
  1497  			if call != nil && len(calls) > 0 {
  1498  				calls = calls[1:]
  1499  			}
  1500  			ref = c.tailRef()
  1501  			res = llx.RefPrimitiveV2(ref)
  1502  			continue
  1503  		}
  1504  
  1505  		if call.Ident != nil {
  1506  			var found bool
  1507  			var resType types.Type
  1508  			id := *call.Ident
  1509  
  1510  			if id == "." {
  1511  				// We get this from the parser if the user called the dot-accessor
  1512  				// but didn't provide any values at all. It equates a not found and
  1513  				// we can now just suggest all fields
  1514  				addFieldSuggestions(availableFields(c, typ), "", c.Result)
  1515  				return nil, errors.New("missing field name in accessing " + typ.Label())
  1516  			}
  1517  
  1518  			calls = calls[1:]
  1519  			call = nil
  1520  			if len(calls) > 0 && calls[0].Function != nil {
  1521  				call = calls[0]
  1522  			}
  1523  
  1524  			found, resType, err = c.compileBoundIdentifier(id, &variable{typ: typ, ref: ref}, call)
  1525  			if err != nil {
  1526  				return nil, err
  1527  			}
  1528  			if !found {
  1529  				if typ != types.Dict || !reAccessor.MatchString(id) {
  1530  					addFieldSuggestions(availableFields(c, typ), id, c.Result)
  1531  					return nil, errors.New("cannot find field '" + id + "' in " + typ.Label())
  1532  				}
  1533  
  1534  				// Support easy accessors for dicts and maps, e.g:
  1535  				// json.params.A.B.C => json.params["A"]["B"]["C"]
  1536  				c.addChunk(&llx.Chunk{
  1537  					Call: llx.Chunk_FUNCTION,
  1538  					Id:   "[]",
  1539  					Function: &llx.Function{
  1540  						Type:    string(typ),
  1541  						Binding: ref,
  1542  						Args:    []*llx.Primitive{llx.StringPrimitive(id)},
  1543  					},
  1544  				})
  1545  			} else {
  1546  				typ = resType
  1547  			}
  1548  
  1549  			if call != nil && len(calls) > 0 {
  1550  				calls = calls[1:]
  1551  			}
  1552  			ref = c.tailRef()
  1553  			res = llx.RefPrimitiveV2(ref)
  1554  
  1555  			continue
  1556  		}
  1557  
  1558  		return nil, errors.New("processed a call without any data")
  1559  	}
  1560  
  1561  	if operand.Block != nil {
  1562  		// for starters, we need the primitive to exist on the stack,
  1563  		// so add it if it's missing
  1564  		if x := c.tailRef(); (x & 0xFFFFFFFF) == 0 {
  1565  			val, err := c.compileValue(operand.Value)
  1566  			if err != nil {
  1567  				return nil, err
  1568  			}
  1569  			c.addChunk(&llx.Chunk{
  1570  				Call: llx.Chunk_PRIMITIVE,
  1571  				// no ID for standalone
  1572  				Primitive: val,
  1573  			})
  1574  			ref = c.tailRef()
  1575  		}
  1576  
  1577  		if typ == types.Nil {
  1578  			_, err = c.compileUnboundBlock(operand.Block, c.block.LastChunk())
  1579  		} else {
  1580  			_, err = c.compileBlock(operand.Block, typ, ref)
  1581  		}
  1582  		if err != nil {
  1583  			return nil, err
  1584  		}
  1585  		ref = c.tailRef()
  1586  		res = llx.RefPrimitiveV2(ref)
  1587  	}
  1588  
  1589  	return res, nil
  1590  }
  1591  
  1592  func (c *compiler) compileExpression(expression *parser.Expression) (*llx.Primitive, error) {
  1593  	if len(expression.Operations) > 0 {
  1594  		panic("ran into an expression that wasn't pre-compiled. It has more than 1 value attached to it")
  1595  	}
  1596  	return c.compileOperand(expression.Operand)
  1597  }
  1598  
  1599  func (c *compiler) compileAndAddExpression(expression *parser.Expression) (uint64, error) {
  1600  	valc, err := c.compileExpression(expression)
  1601  	if err != nil {
  1602  		return 0, err
  1603  	}
  1604  
  1605  	if types.Type(valc.Type) == types.Ref {
  1606  		ref, _ := valc.RefV2()
  1607  		return ref, nil
  1608  		// nothing to do, the last call was added to the compiled chain
  1609  	}
  1610  
  1611  	c.addChunk(&llx.Chunk{
  1612  		Call: llx.Chunk_PRIMITIVE,
  1613  		// no id for standalone values
  1614  		Primitive: valc,
  1615  	})
  1616  
  1617  	return c.tailRef(), nil
  1618  }
  1619  
  1620  func (c *compiler) compileExpressions(expressions []*parser.Expression) error {
  1621  	var err error
  1622  	code := c.Result.CodeV2
  1623  
  1624  	// we may have comment-only expressions
  1625  	expressions = filterEmptyExpressions(expressions)
  1626  
  1627  	for idx := range expressions {
  1628  		if err = expressions[idx].ProcessOperators(); err != nil {
  1629  			return err
  1630  		}
  1631  	}
  1632  
  1633  	var ident string
  1634  	var prev string
  1635  	for idx := range expressions {
  1636  		expression := expressions[idx]
  1637  		prev = ident
  1638  		ident = ""
  1639  		if expression.Operand != nil && expression.Operand.Value != nil && expression.Operand.Value.Ident != nil {
  1640  			ident = *expression.Operand.Value.Ident
  1641  		}
  1642  
  1643  		if prev == "else" && ident != "if" && c.block.SingleValue {
  1644  			// if the previous id is else and its single valued, the following
  1645  			// expressions cannot be executed
  1646  			return errors.New("single valued block followed by expressions")
  1647  		}
  1648  
  1649  		if prev == "if" && ident != "else" && c.block.SingleValue {
  1650  			// all following expressions need to be compiled in a block which is
  1651  			// conditional to this if-statement unless we're already doing
  1652  			// if-else chaining
  1653  
  1654  			c.prevID = "else"
  1655  			rest := expressions[idx:]
  1656  			_, err := c.compileUnboundBlock(rest, c.block.LastChunk())
  1657  			return err
  1658  		}
  1659  
  1660  		if ident == "return" {
  1661  			// A return statement can only be followed by max 1 more expression
  1662  			max := len(expressions)
  1663  			if idx+2 < max {
  1664  				return errors.New("return statement is followed by too many expressions")
  1665  			}
  1666  
  1667  			if idx+1 == max {
  1668  				// nothing else coming after this, return nil
  1669  			}
  1670  
  1671  			c.block.SingleValue = true
  1672  			continue
  1673  		}
  1674  
  1675  		// for all other expressions, just compile
  1676  		ref, err := c.compileAndAddExpression(expression)
  1677  		if err != nil {
  1678  			return err
  1679  		}
  1680  
  1681  		if prev == "return" {
  1682  			prevChunk := code.Chunk(ref)
  1683  
  1684  			c.addChunk(&llx.Chunk{
  1685  				Call: llx.Chunk_FUNCTION,
  1686  				Id:   "return",
  1687  				Function: &llx.Function{
  1688  					Type:    string(prevChunk.Type()),
  1689  					Binding: 0,
  1690  					Args: []*llx.Primitive{
  1691  						llx.RefPrimitiveV2(ref),
  1692  					},
  1693  				},
  1694  			})
  1695  
  1696  			c.block.Entrypoints = []uint64{c.block.TailRef(c.blockRef)}
  1697  			c.block.SingleValue = true
  1698  
  1699  			return nil
  1700  		}
  1701  
  1702  		l := len(c.block.Entrypoints)
  1703  		// if the last entrypoint already points to this ref, skip it
  1704  		if l != 0 && c.block.Entrypoints[l-1] == ref {
  1705  			continue
  1706  		}
  1707  
  1708  		c.block.Entrypoints = append(c.block.Entrypoints, ref)
  1709  
  1710  		if code.Checksums[ref] == "" {
  1711  			return errors.New("failed to compile expression, ref returned empty checksum ID for ref " + strconv.FormatInt(int64(ref), 10))
  1712  		}
  1713  	}
  1714  
  1715  	return nil
  1716  }
  1717  
  1718  func (c *compiler) postCompile() {
  1719  	code := c.Result.CodeV2
  1720  	for i := range code.Blocks {
  1721  		block := code.Blocks[i]
  1722  		eps := block.Entrypoints
  1723  
  1724  		for _, ref := range eps {
  1725  			chunk := code.Chunk(ref)
  1726  
  1727  			if chunk.Call != llx.Chunk_FUNCTION {
  1728  				continue
  1729  			}
  1730  
  1731  			chunk, typ, ref := c.expandListResource(chunk, ref)
  1732  			switch chunk.Id {
  1733  			case "$one", "$all", "$none", "$any":
  1734  				// default fields
  1735  				ref = chunk.Function.Binding
  1736  				chunk := code.Chunk(ref)
  1737  				typ = types.Type(chunk.Function.Type)
  1738  				expanded := c.expandResourceFields(chunk, typ, ref)
  1739  				// when no defaults are defined or query isn't about a resource, no block was added
  1740  				if expanded {
  1741  					block.Datapoints = append(block.Datapoints, block.TailRef(ref))
  1742  					c.addValueFieldChunks(ref)
  1743  				}
  1744  			default:
  1745  				c.expandResourceFields(chunk, typ, ref)
  1746  			}
  1747  		}
  1748  	}
  1749  }
  1750  
  1751  // addValueFieldChunks takes the value fields of the assessment and adds them to the
  1752  // block for the default fields
  1753  // This way, the actual data of the assessment automatically shows up in the output
  1754  // of the assessment that failed the assessment
  1755  func (c *compiler) addValueFieldChunks(ref uint64) {
  1756  	var whereChunk *llx.Chunk
  1757  
  1758  	// find chunk with where/whereNot function
  1759  	// it holds the reference to the block with the predicate(s) for the assessment
  1760  	for {
  1761  		chunk := c.Result.CodeV2.Chunk(ref)
  1762  		if chunk.Function == nil {
  1763  			// this is a safe guard for some cases
  1764  			// e.g. queries with .none() are totally valid and will not have a where block,
  1765  			// because they do not check a specific field
  1766  			log.Debug().Msg("failed to find where function for assessment, this can happen with empty assessments")
  1767  			return
  1768  		}
  1769  		if chunk.Id == "$whereNot" || chunk.Id == "where" {
  1770  			whereChunk = chunk
  1771  			break
  1772  		}
  1773  		ref = chunk.Function.Binding
  1774  	}
  1775  
  1776  	type fieldTreeNode struct {
  1777  		id       string
  1778  		chunk    *llx.Chunk
  1779  		chunkIdx int
  1780  		children map[string]*fieldTreeNode
  1781  	}
  1782  
  1783  	type fieldTree struct {
  1784  		nodes []*fieldTreeNode
  1785  	}
  1786  
  1787  	blockToFieldTree := func(block *llx.Block, filter func(chunkIdx int, chunk *llx.Chunk) bool) fieldTree {
  1788  		// This function assumes the chunks are topologically sorted such
  1789  		// that any dependency is always before the chunk that depends on it
  1790  		nodes := make([]*fieldTreeNode, len(block.Chunks))
  1791  		for i := range block.Chunks {
  1792  			chunk := block.Chunks[i]
  1793  			if !filter(i, chunk) {
  1794  				continue
  1795  			}
  1796  			nodes[i] = &fieldTreeNode{
  1797  				id:       chunk.Id,
  1798  				chunk:    chunk,
  1799  				chunkIdx: i + 1,
  1800  				children: map[string]*fieldTreeNode{},
  1801  			}
  1802  
  1803  			if chunk.Function != nil && chunk.Function.Binding != 0 {
  1804  				chunkIdx := llx.ChunkIndex(chunk.Function.Binding)
  1805  				parent := nodes[chunkIdx-1]
  1806  				if parent != nil {
  1807  					nodes[chunkIdx-1].children[chunk.Id] = nodes[i]
  1808  				}
  1809  			}
  1810  		}
  1811  
  1812  		return fieldTree{
  1813  			nodes: nodes,
  1814  		}
  1815  	}
  1816  
  1817  	addToTree := func(tree *fieldTree, parentPath []string, blockRef uint64, block *llx.Block, chunk *llx.Chunk) bool {
  1818  		// add a chunk to the tree. If the path already exists, do nothing
  1819  		// return true if the chunk was added, false if it already existed
  1820  		if len(tree.nodes) != len(block.Chunks) {
  1821  			panic("tree and block chunks do not match")
  1822  		}
  1823  
  1824  		parent := tree.nodes[0]
  1825  		for _, id := range parentPath[1:] {
  1826  			child := parent.children[id]
  1827  			parent = child
  1828  		}
  1829  
  1830  		if parent.children[chunk.Id] != nil {
  1831  			return false
  1832  		}
  1833  
  1834  		newChunk := chunk
  1835  		if chunk.Function != nil {
  1836  			newChunk = &llx.Chunk{
  1837  				Call: chunk.Call,
  1838  				Id:   chunk.Id,
  1839  				Function: &llx.Function{
  1840  					Binding: (blockRef & 0xFFFFFFFF00000000) | uint64(parent.chunkIdx),
  1841  					Type:    chunk.Function.Type,
  1842  					Args:    chunk.Function.Args,
  1843  				},
  1844  			}
  1845  		}
  1846  
  1847  		parent.children[chunk.Id] = &fieldTreeNode{
  1848  			id:       chunk.Id,
  1849  			chunk:    newChunk,
  1850  			chunkIdx: len(tree.nodes) + 1,
  1851  			children: map[string]*fieldTreeNode{},
  1852  		}
  1853  		tree.nodes = append(tree.nodes, parent.children[chunk.Id])
  1854  		block.AddChunk(c.Result.CodeV2, blockRef, newChunk)
  1855  
  1856  		return true
  1857  	}
  1858  
  1859  	var visitTreeNodes func(tree *fieldTree, node *fieldTreeNode, path []string, visit func(tree *fieldTree, node *fieldTreeNode, path []string))
  1860  	visitTreeNodes = func(tree *fieldTree, node *fieldTreeNode, path []string, visit func(tree *fieldTree, node *fieldTreeNode, path []string)) {
  1861  		if node == nil {
  1862  			return
  1863  		}
  1864  		path = append(path, node.id)
  1865  		keys := []string{}
  1866  		for k := range node.children {
  1867  			keys = append(keys, k)
  1868  		}
  1869  		sort.Strings(keys)
  1870  		for _, k := range keys {
  1871  			child := node.children[k]
  1872  			visit(tree, child, path)
  1873  			visitTreeNodes(tree, child, path, visit)
  1874  		}
  1875  	}
  1876  
  1877  	// This block holds all the data and function chunks used
  1878  	// for the predicate(s) of the .all()/.none()/... fucntion
  1879  	var assessmentBlock *llx.Block
  1880  	// find the referenced block for the where function
  1881  	for i := len(whereChunk.Function.Args) - 1; i >= 0; i-- {
  1882  		arg := whereChunk.Function.Args[i]
  1883  		if types.Type(arg.Type).Underlying() == types.FunctionLike {
  1884  			raw := arg.RawData()
  1885  			blockRef := raw.Value.(uint64)
  1886  			assessmentBlock = c.Result.CodeV2.Block(blockRef)
  1887  			break
  1888  		}
  1889  	}
  1890  	assessmentBlockTree := blockToFieldTree(assessmentBlock, func(chunkIdx int, chunk *llx.Chunk) bool {
  1891  		if chunk.Id == "$whereNot" || chunk.Id == "where" {
  1892  			return false
  1893  		} else if _, compareable := llx.ComparableLabel(chunk.Id); compareable {
  1894  			return false
  1895  		} else if chunk.Function != nil && len(chunk.Function.Args) > 0 {
  1896  			// filter out nested function block that require other blocks
  1897  			// This at least makes https://github.com/mondoohq/cnquery/issues/1339
  1898  			// not panic
  1899  			for _, arg := range chunk.Function.Args {
  1900  				if types.Type(arg.Type).Underlying() == types.Ref {
  1901  					return false
  1902  				}
  1903  			}
  1904  		}
  1905  		return true
  1906  	})
  1907  
  1908  	defaultFieldsBlock := c.Result.CodeV2.Blocks[len(c.Result.CodeV2.Blocks)-1]
  1909  	defaultFieldsRef := defaultFieldsBlock.HeadRef(c.Result.CodeV2.LastBlockRef())
  1910  	defaultFieldsBlockTree := blockToFieldTree(defaultFieldsBlock, func(chunkIdx int, chunk *llx.Chunk) bool {
  1911  		return true
  1912  	})
  1913  
  1914  	visitTreeNodes(&assessmentBlockTree, assessmentBlockTree.nodes[0], make([]string, 0, 16), func(tree *fieldTree, node *fieldTreeNode, path []string) {
  1915  		// add the node to the assessment block tree
  1916  		chunkAdded := addToTree(&defaultFieldsBlockTree, path, defaultFieldsRef, defaultFieldsBlock, node.chunk)
  1917  		if chunkAdded && node.chunk.Function != nil {
  1918  			defaultFieldsBlock.Entrypoints = append(defaultFieldsBlock.Entrypoints, (defaultFieldsRef&0xFFFFFFFF00000000)|uint64(len(defaultFieldsBlock.Chunks)))
  1919  		}
  1920  	})
  1921  }
  1922  
  1923  func (c *compiler) expandListResource(chunk *llx.Chunk, ref uint64) (*llx.Chunk, types.Type, uint64) {
  1924  	typ := chunk.Type()
  1925  	if !typ.IsResource() {
  1926  		return chunk, typ, ref
  1927  	}
  1928  
  1929  	info := c.Schema.Lookup(typ.ResourceName())
  1930  	if info == nil || info.ListType == "" {
  1931  		return chunk, typ, ref
  1932  	}
  1933  
  1934  	block := c.Result.CodeV2.Block(ref)
  1935  	newType := types.Array(types.Type(info.ListType))
  1936  	newChunk := &llx.Chunk{
  1937  		Call: llx.Chunk_FUNCTION,
  1938  		Id:   "list",
  1939  		Function: &llx.Function{
  1940  			Binding: ref,
  1941  			Type:    string(newType),
  1942  		},
  1943  	}
  1944  	block.AddChunk(c.Result.CodeV2, ref, newChunk)
  1945  	newRef := block.TailRef(ref)
  1946  	block.ReplaceEntrypoint(ref, newRef)
  1947  
  1948  	return newChunk, newType, newRef
  1949  }
  1950  
  1951  func (c *compiler) expandResourceFields(chunk *llx.Chunk, typ types.Type, ref uint64) bool {
  1952  	resultType := types.Block
  1953  	if typ.IsArray() {
  1954  		resultType = types.Array(types.Block)
  1955  		typ = typ.Child()
  1956  	}
  1957  	if !typ.IsResource() {
  1958  		return false
  1959  	}
  1960  
  1961  	info := c.Schema.Lookup(typ.ResourceName())
  1962  	if info == nil || info.Defaults == "" {
  1963  		return false
  1964  	}
  1965  
  1966  	ast, err := parser.Parse(info.Defaults)
  1967  	if ast == nil || len(ast.Expressions) == 0 {
  1968  		log.Error().Err(err).Msg("failed to parse defaults for " + info.Name)
  1969  		return false
  1970  	}
  1971  
  1972  	refs, err := c.blockOnResource(ast.Expressions, types.Resource(info.Name), ref)
  1973  	if err != nil {
  1974  		log.Error().Err(err).Msg("failed to compile default for " + info.Name)
  1975  	}
  1976  	if len(refs.deps) != 0 {
  1977  		log.Warn().Msg("defaults somehow included external dependencies for resource " + info.Name)
  1978  	}
  1979  
  1980  	args := []*llx.Primitive{llx.FunctionPrimitive(refs.block)}
  1981  	block := c.Result.CodeV2.Block(ref)
  1982  	block.AddChunk(c.Result.CodeV2, ref, &llx.Chunk{
  1983  		Call: llx.Chunk_FUNCTION,
  1984  		Id:   "{}",
  1985  		Function: &llx.Function{
  1986  			Type:    string(resultType),
  1987  			Binding: refs.binding,
  1988  			Args:    args,
  1989  		},
  1990  	})
  1991  	ep := block.TailRef(ref)
  1992  	block.ReplaceEntrypoint(ref, ep)
  1993  	ref = ep
  1994  
  1995  	c.Result.AutoExpand[c.Result.CodeV2.Checksums[ref]] = refs.block
  1996  	return true
  1997  }
  1998  
  1999  func (c *compiler) updateLabels() {
  2000  	for _, v := range c.vars.vars {
  2001  		if v.name == "" {
  2002  			continue
  2003  		}
  2004  
  2005  		c.Result.Vars[v.ref] = v.name
  2006  	}
  2007  }
  2008  
  2009  func (c *compiler) updateEntrypoints(collectRefDatapoints bool) {
  2010  	// BUG (jaym): collectRefDatapoints prevents us from collecting datapoints.
  2011  	// Collecting datapoints for blocks didn't work correctly until 6.7.0.
  2012  	// See https://gitlab.com/mondoolabs/mondoo/-/merge_requests/2639
  2013  	// We can fix this after some time has passed. If we fix it too soon
  2014  	// people will start having their queries fail if a falsy datapoint
  2015  	// is collected.
  2016  
  2017  	code := c.Result.CodeV2
  2018  
  2019  	// 1. efficiently remove variable definitions from entrypoints
  2020  	varsByRef := make(map[uint64]variable, c.vars.len())
  2021  	for name, v := range c.vars.vars {
  2022  		if name == "_" {
  2023  			// We need to filter this out. It wasn't an assignment declared by the
  2024  			// user. We will re-introduce it conceptually once we tackle context
  2025  			// information for blocks.
  2026  			continue
  2027  		}
  2028  		varsByRef[v.ref] = v
  2029  	}
  2030  
  2031  	max := len(c.block.Entrypoints)
  2032  	for i := 0; i < max; {
  2033  		ref := c.block.Entrypoints[i]
  2034  		if _, ok := varsByRef[ref]; ok {
  2035  			c.block.Entrypoints[i], c.block.Entrypoints[max-1] = c.block.Entrypoints[max-1], c.block.Entrypoints[i]
  2036  			max--
  2037  		} else {
  2038  			i++
  2039  		}
  2040  	}
  2041  	if max != len(c.block.Entrypoints) {
  2042  		c.block.Entrypoints = c.block.Entrypoints[:max]
  2043  	}
  2044  
  2045  	// 2. potentially clean up all inherited entrypoints
  2046  	// TODO: unclear if this is necessary because the condition may never be met
  2047  	entrypoints := map[uint64]struct{}{}
  2048  	for _, ref := range c.block.Entrypoints {
  2049  		entrypoints[ref] = struct{}{}
  2050  		chunk := code.Chunk(ref)
  2051  		if chunk.Function != nil {
  2052  			delete(entrypoints, chunk.Function.Binding)
  2053  		}
  2054  	}
  2055  
  2056  	if !collectRefDatapoints {
  2057  		return
  2058  	}
  2059  
  2060  	datapoints := map[uint64]struct{}{}
  2061  	// 3. resolve operators
  2062  	for ref := range entrypoints {
  2063  		dps := code.RefDatapoints(ref)
  2064  		if dps != nil {
  2065  			for i := range dps {
  2066  				datapoints[dps[i]] = struct{}{}
  2067  			}
  2068  		}
  2069  	}
  2070  
  2071  	// done
  2072  	res := make([]uint64, len(datapoints))
  2073  	var idx int
  2074  	for ref := range datapoints {
  2075  		res[idx] = ref
  2076  		idx++
  2077  	}
  2078  	sort.Slice(res, func(i, j int) bool {
  2079  		return res[i] < res[j]
  2080  	})
  2081  	c.block.Datapoints = append(c.block.Datapoints, res...)
  2082  	// E.g. in the case of .all(...)/.none(...)/... queries, we have two datapoints bound to the list of resources:
  2083  	// - one with the resource ids
  2084  	// - one with the default values
  2085  	// We only want to keep the datapoint for the default values.
  2086  	updatedDatapoints := make([]uint64, 0, len(c.block.Datapoints))
  2087  	for _, ref := range c.block.Datapoints {
  2088  		chunk := code.Chunk(ref)
  2089  		if chunk.Function != nil {
  2090  			found := false
  2091  			for i := range c.block.Datapoints {
  2092  				if c.block.Datapoints[i] == chunk.Function.Binding {
  2093  					found = true
  2094  					break
  2095  				}
  2096  			}
  2097  			if found {
  2098  				updatedDatapoints = append(updatedDatapoints, ref)
  2099  			}
  2100  		}
  2101  	}
  2102  	if len(updatedDatapoints) > 0 {
  2103  		c.block.Datapoints = updatedDatapoints
  2104  	}
  2105  }
  2106  
  2107  // CompileParsed AST into an executable structure
  2108  func (c *compiler) CompileParsed(ast *parser.AST) error {
  2109  	err := c.compileExpressions(ast.Expressions)
  2110  	if err != nil {
  2111  		return err
  2112  	}
  2113  
  2114  	c.postCompile()
  2115  	c.Result.CodeV2.UpdateID()
  2116  	c.updateEntrypoints(true)
  2117  	c.updateLabels()
  2118  
  2119  	return nil
  2120  }
  2121  
  2122  func getMinMondooVersion(schema llx.Schema, current string, resource string, field string) string {
  2123  	info := schema.Lookup(resource)
  2124  	if info == nil {
  2125  		return current
  2126  	}
  2127  
  2128  	min := info.MinMondooVersion
  2129  	if field != "" {
  2130  		if finfo, ok := info.Fields[field]; ok && finfo.MinMondooVersion != "" {
  2131  			min = finfo.MinMondooVersion
  2132  		}
  2133  	}
  2134  
  2135  	if current == "" {
  2136  		return min
  2137  	} else if min == "" {
  2138  		return current
  2139  	}
  2140  
  2141  	vMin, err1 := version.NewVersion(min)
  2142  	vCur, err2 := version.NewVersion(current)
  2143  	// if the current version requirement is higher than docs, we keep it,
  2144  	// otherwise docs wins
  2145  	if err1 == nil && err2 == nil && vMin.LessThan(vCur) {
  2146  		return current
  2147  	}
  2148  	return min
  2149  }
  2150  
  2151  // CompileAST with a schema into a chunky code
  2152  func CompileAST(ast *parser.AST, props map[string]*llx.Primitive, conf compilerConfig) (*llx.CodeBundle, error) {
  2153  	if conf.Schema == nil {
  2154  		return nil, errors.New("mqlc> please provide a schema to compile this code")
  2155  	}
  2156  
  2157  	if props == nil {
  2158  		props = map[string]*llx.Primitive{}
  2159  	}
  2160  
  2161  	codeBundle := &llx.CodeBundle{
  2162  		CodeV2: &llx.CodeV2{
  2163  			Checksums: map[uint64]string{},
  2164  			// we are initializing it with the first block, which is empty
  2165  			Blocks: []*llx.Block{{}},
  2166  		},
  2167  		Labels: &llx.Labels{
  2168  			Labels: map[string]string{},
  2169  		},
  2170  		Props:            map[string]string{},
  2171  		Version:          cnquery.APIVersion(),
  2172  		MinMondooVersion: "",
  2173  		AutoExpand:       map[string]uint64{},
  2174  		Vars:             map[uint64]string{},
  2175  	}
  2176  
  2177  	c := compiler{
  2178  		compilerConfig: conf,
  2179  		Result:         codeBundle,
  2180  		vars:           newvarmap(1<<32, nil),
  2181  		parent:         nil,
  2182  		blockRef:       1 << 32,
  2183  		block:          codeBundle.CodeV2.Blocks[0],
  2184  		props:          props,
  2185  		standalone:     true,
  2186  	}
  2187  
  2188  	return c.Result, c.CompileParsed(ast)
  2189  }
  2190  
  2191  // Compile a code piece against a schema into chunky code
  2192  func compile(input string, props map[string]*llx.Primitive, conf compilerConfig) (*llx.CodeBundle, error) {
  2193  	// remove leading whitespace; we are re-using this later on
  2194  	input = Dedent(input)
  2195  
  2196  	ast, err := parser.Parse(input)
  2197  	if ast == nil {
  2198  		return nil, err
  2199  	}
  2200  
  2201  	// Special handling for parser errors: We still try to compile it because
  2202  	// we want to get any compiler suggestions for auto-complete / fixing it.
  2203  	// That said, we must return an error either way.
  2204  	if err != nil {
  2205  		res, _ := CompileAST(ast, props, conf)
  2206  		return res, err
  2207  	}
  2208  
  2209  	res, err := CompileAST(ast, props, conf)
  2210  	if err != nil {
  2211  		return res, err
  2212  	}
  2213  
  2214  	err = UpdateLabels(res, conf.Schema)
  2215  	if err != nil {
  2216  		return res, err
  2217  	}
  2218  	if len(res.Labels.Labels) == 0 {
  2219  		res.Labels.Labels = nil
  2220  	}
  2221  
  2222  	err = UpdateAssertions(res)
  2223  	if err != nil {
  2224  		return res, err
  2225  	}
  2226  
  2227  	res.Source = input
  2228  	return res, nil
  2229  }
  2230  
  2231  func Compile(input string, props map[string]*llx.Primitive, conf compilerConfig) (*llx.CodeBundle, error) {
  2232  	// Note: we do not check the conf because it will get checked by the
  2233  	// first CompileAST call. Do not use it earlier or add a check.
  2234  
  2235  	res, err := compile(input, props, conf)
  2236  	if err != nil {
  2237  		return res, err
  2238  	}
  2239  
  2240  	if res.CodeV2 == nil || res.CodeV2.Id == "" {
  2241  		return res, errors.New("failed to compile: received an unspecified empty code structure")
  2242  	}
  2243  
  2244  	return res, nil
  2245  }