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

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package mqlc
     5  
     6  import (
     7  	"errors"
     8  
     9  	"github.com/rs/zerolog/log"
    10  	"go.mondoo.com/cnquery/llx"
    11  	"go.mondoo.com/cnquery/mqlc/parser"
    12  	"go.mondoo.com/cnquery/types"
    13  )
    14  
    15  type fieldCompiler func(*compiler, string, *parser.Call) (types.Type, error)
    16  
    17  var operatorsCompilers map[string]fieldCompiler
    18  
    19  func init() {
    20  	operatorsCompilers = map[string]fieldCompiler{
    21  		"==":     compileComparable,
    22  		"=~":     compileComparable,
    23  		"!=":     compileComparable,
    24  		"!~":     compileComparable,
    25  		">=":     compileComparable,
    26  		">":      compileComparable,
    27  		"<=":     compileComparable,
    28  		"<":      compileComparable,
    29  		"+":      compileTransformation,
    30  		"-":      compileTransformation,
    31  		"*":      compileTransformation,
    32  		"/":      compileTransformation,
    33  		"%":      nil,
    34  		"=":      compileAssignment,
    35  		"||":     compileComparable,
    36  		"&&":     compileComparable,
    37  		"{}":     compileBlock,
    38  		"if":     compileIf,
    39  		"else":   compileElse,
    40  		"expect": compileExpect,
    41  		"score":  compileScore,
    42  		"typeof": compileTypeof,
    43  		"switch": compileSwitch,
    44  		"Never":  compileNever,
    45  		"empty":  compileEmpty,
    46  	}
    47  }
    48  
    49  func compileEmpty(c *compiler, id string, call *parser.Call) (types.Type, error) {
    50  	c.addChunk(&llx.Chunk{
    51  		Call:      llx.Chunk_PRIMITIVE,
    52  		Primitive: llx.EmptyPrimitive,
    53  	})
    54  
    55  	return types.Time, nil
    56  }
    57  
    58  // compile the operation between two operands A and B
    59  // examples: A && B, A - B, ...
    60  func compileABOperation(c *compiler, id string, call *parser.Call) (uint64, *llx.Chunk, *llx.Primitive, *llx.AssertionMessage, error) {
    61  	if call == nil {
    62  		return 0, nil, nil, nil, errors.New("operation needs a function call")
    63  	}
    64  
    65  	if call.Function == nil {
    66  		return 0, nil, nil, nil, errors.New("operation needs a function call")
    67  	}
    68  	if len(call.Function) != 2 {
    69  		if len(call.Function) < 2 {
    70  			return 0, nil, nil, nil, errors.New("missing arguments")
    71  		}
    72  		return 0, nil, nil, nil, errors.New("too many arguments")
    73  	}
    74  
    75  	a := call.Function[0]
    76  	b := call.Function[1]
    77  	if a.Name != "" || b.Name != "" {
    78  		return 0, nil, nil, nil, errors.New("calling operations with named arguments is not supported")
    79  	}
    80  
    81  	leftRef, err := c.compileAndAddExpression(a.Value)
    82  	if err != nil {
    83  		return 0, nil, nil, nil, err
    84  	}
    85  	left := c.Result.CodeV2.Chunk(leftRef)
    86  
    87  	right, err := c.compileExpression(b.Value)
    88  	if err != nil {
    89  		return 0, nil, nil, nil, err
    90  	}
    91  
    92  	if left == nil {
    93  		log.Fatal().Msgf("left is nil: %d", leftRef)
    94  	}
    95  
    96  	comments := extractComments(a.Value) + "\n" + extractComments(b.Value)
    97  	msg := extractMsgTag(comments)
    98  	if msg == "" {
    99  		return leftRef, left, right, nil, nil
   100  	}
   101  
   102  	// if the right-hand argument is directly provided as a primitive, we don't have a way to
   103  	// ref to it in the chunk stack. Since the message tag **may** end up using it,
   104  	// we have to provide it ref'able. So... bit the bullet (for now... seriously if
   105  	// we could do this simpler that'd be great)
   106  	rightRef, ok := right.RefV2()
   107  	if !ok {
   108  		c.addChunk(&llx.Chunk{
   109  			Call:      llx.Chunk_PRIMITIVE,
   110  			Primitive: right,
   111  		})
   112  		rightRef = c.tailRef()
   113  	}
   114  
   115  	// these variables are accessible only to comments
   116  	c.vars.add("$expected", variable{ref: rightRef, typ: types.Type(right.Type)})
   117  	c.vars.add("$actual", variable{ref: leftRef, typ: left.Type()})
   118  	if c.Binding != nil {
   119  		c.vars.add("$binding", variable{ref: c.Binding.ref, typ: c.Binding.typ})
   120  	}
   121  
   122  	assertionMsg, err := compileAssertionMsg(msg, c)
   123  	if err != nil {
   124  		return 0, nil, nil, nil, err
   125  	}
   126  	return leftRef, left, right, assertionMsg, nil
   127  }
   128  
   129  func compileAssignment(c *compiler, id string, call *parser.Call) (types.Type, error) {
   130  	if call == nil {
   131  		return types.Nil, errors.New("assignment needs a function call")
   132  	}
   133  
   134  	if call.Function == nil {
   135  		return types.Nil, errors.New("assignment needs a function call")
   136  	}
   137  	if len(call.Function) != 2 {
   138  		if len(call.Function) < 2 {
   139  			return types.Nil, errors.New("missing arguments")
   140  		}
   141  		return types.Nil, errors.New("too many arguments")
   142  	}
   143  
   144  	varIdent := call.Function[0]
   145  	varValue := call.Function[1]
   146  	if varIdent.Name != "" || varValue.Name != "" {
   147  		return types.Nil, errors.New("calling operations with named arguments is not supported")
   148  	}
   149  
   150  	if varIdent.Value == nil || varIdent.Value.Operand == nil || varIdent.Value.Operand.Value == nil ||
   151  		varIdent.Value.Operand.Value.Ident == nil {
   152  		return types.Nil, errors.New("variable name is not defined")
   153  	}
   154  
   155  	name := *varIdent.Value.Operand.Value.Ident
   156  	if name == "" {
   157  		return types.Nil, errors.New("cannot assign to empty variable name")
   158  	}
   159  	if name[0] == '$' {
   160  		return types.Nil, errors.New("illegal character in variable assignment '$'")
   161  	}
   162  
   163  	ref, err := c.compileAndAddExpression(varValue.Value)
   164  	if err != nil {
   165  		return types.Nil, err
   166  	}
   167  
   168  	c.vars.add(name, variable{
   169  		name: name,
   170  		ref:  ref,
   171  		typ:  c.Result.CodeV2.Chunk(ref).Type(),
   172  	})
   173  
   174  	return types.Nil, nil
   175  }
   176  
   177  func compileComparable(c *compiler, id string, call *parser.Call) (types.Type, error) {
   178  	leftRef, left, right, assertionMsg, err := compileABOperation(c, id, call)
   179  	if err != nil {
   180  		return types.Nil, errors.New("failed to compile: " + err.Error())
   181  	}
   182  
   183  	for left.Type() == types.Ref {
   184  		ref, ok := left.Primitive.RefV2()
   185  		if !ok {
   186  			return types.Nil, errors.New("failed to get reference entry of left operand to " + id + ", this should not happen")
   187  		}
   188  		left = c.Result.CodeV2.Chunk(ref)
   189  	}
   190  
   191  	// find specialized or generalized builtin function
   192  	lt := left.DereferencedTypeV2(c.Result.CodeV2)
   193  	rt := (&llx.Chunk{Primitive: right}).DereferencedTypeV2(c.Result.CodeV2)
   194  
   195  	name := id + string(rt)
   196  	h, err := llx.BuiltinFunctionV2(lt, name)
   197  	if err != nil {
   198  		h, err = llx.BuiltinFunctionV2(lt, id)
   199  	}
   200  	if err != nil {
   201  		name = id + string(rt.Underlying())
   202  		h, err = llx.BuiltinFunctionV2(lt, name)
   203  	}
   204  	if err != nil {
   205  		return types.Nil, errors.New("cannot find operator handler: " + lt.Label() + " " + id + " " + types.Type(right.Type).Label())
   206  	}
   207  
   208  	if h.Compiler != nil {
   209  		name, err = h.Compiler(lt, rt)
   210  		if err != nil {
   211  			return types.Nil, err
   212  		}
   213  	}
   214  
   215  	c.addChunk(&llx.Chunk{
   216  		Call: llx.Chunk_FUNCTION,
   217  		Id:   name,
   218  		Function: &llx.Function{
   219  			Type:    string(types.Bool),
   220  			Binding: leftRef,
   221  			Args:    []*llx.Primitive{right},
   222  		},
   223  	})
   224  
   225  	if assertionMsg != nil {
   226  		if c.Result.CodeV2.Assertions == nil {
   227  			c.Result.CodeV2.Assertions = map[uint64]*llx.AssertionMessage{}
   228  		}
   229  		c.Result.CodeV2.Assertions[c.tailRef()] = assertionMsg
   230  	}
   231  
   232  	return types.Bool, nil
   233  }
   234  
   235  func compileTransformation(c *compiler, id string, call *parser.Call) (types.Type, error) {
   236  	leftRef, left, right, _, err := compileABOperation(c, id, call)
   237  	if err != nil {
   238  		return types.Nil, err
   239  	}
   240  
   241  	// find specialized or generalized builtin function
   242  	lt := left.DereferencedTypeV2(c.Result.CodeV2)
   243  	rt := (&llx.Chunk{Primitive: right}).DereferencedTypeV2(c.Result.CodeV2)
   244  
   245  	name := id + string(rt)
   246  	h, err := llx.BuiltinFunctionV2(lt, name)
   247  	if err != nil {
   248  		h, err = llx.BuiltinFunctionV2(lt, id)
   249  	}
   250  	if err != nil {
   251  		name = id + string(rt.Underlying())
   252  		h, err = llx.BuiltinFunctionV2(lt, name)
   253  	}
   254  	if err != nil {
   255  		return types.Nil, errors.New("cannot find operator handler: " + lt.Label() + " " + id + " " + types.Type(right.Type).Label())
   256  	}
   257  
   258  	if h.Compiler != nil {
   259  		name, err = h.Compiler(lt, rt)
   260  		if err != nil {
   261  			return types.Nil, err
   262  		}
   263  	}
   264  
   265  	returnType := h.Typ
   266  	if returnType == types.NoType {
   267  		returnType = lt
   268  	}
   269  
   270  	c.addChunk(&llx.Chunk{
   271  		Call: llx.Chunk_FUNCTION,
   272  		Id:   name,
   273  		Function: &llx.Function{
   274  			Type:    string(returnType),
   275  			Binding: leftRef,
   276  			Args:    []*llx.Primitive{right},
   277  		},
   278  	})
   279  
   280  	return lt, nil
   281  }
   282  
   283  func (c *compiler) generateEntrypoints(arg *llx.Primitive) error {
   284  	code := c.Result.CodeV2
   285  
   286  	ref, ok := arg.RefV2()
   287  	if !ok {
   288  		return nil
   289  	}
   290  
   291  	refobj := code.Chunk(ref)
   292  	if refobj == nil {
   293  		return errors.New("Failed to get code reference on expect call, this shouldn't happen")
   294  	}
   295  
   296  	reffunc := refobj.Function
   297  	if reffunc == nil {
   298  		return nil
   299  	}
   300  
   301  	// if the left argument is not a primitive but a calculated value
   302  	bind := code.Chunk(reffunc.Binding)
   303  	if bind.Primitive == nil {
   304  		c.block.Entrypoints = append(c.block.Entrypoints, reffunc.Binding)
   305  	}
   306  
   307  	for i := range reffunc.Args {
   308  		arg := reffunc.Args[i]
   309  		i, ok := arg.RefV2()
   310  		if ok {
   311  			c.block.Entrypoints = append(c.block.Entrypoints, i)
   312  		}
   313  	}
   314  	return nil
   315  }
   316  
   317  func compileBlock(c *compiler, id string, call *parser.Call) (types.Type, error) {
   318  	c.addChunk(&llx.Chunk{
   319  		Call: llx.Chunk_FUNCTION,
   320  		Id:   id,
   321  		Function: &llx.Function{
   322  			Type: string(types.Unset),
   323  			Args: []*llx.Primitive{},
   324  		},
   325  	})
   326  	return types.Unset, nil
   327  }
   328  
   329  func compileIf(c *compiler, id string, call *parser.Call) (types.Type, error) {
   330  	if call == nil {
   331  		return types.Nil, errors.New("need conditional arguments for if-clause")
   332  	}
   333  	if len(call.Function) < 1 {
   334  		return types.Nil, errors.New("missing parameters for if-clause, it requires 1")
   335  	}
   336  	arg := call.Function[0]
   337  	if arg.Name != "" {
   338  		return types.Nil, errors.New("called if-clause with a named argument, which is not supported")
   339  	}
   340  
   341  	// if we are in a chained if-else call (needs previous if-call)
   342  	if c.prevID == "else" && len(c.block.Chunks) != 0 {
   343  		maxRef := len(c.block.Chunks) - 1
   344  		prev := c.block.Chunks[maxRef]
   345  		if prev.Id == "if" {
   346  			// we need to pop off the last "if" chunk as the new condition needs to
   347  			// be added in front of it
   348  			c.popChunk()
   349  
   350  			argValue, err := c.compileExpression(arg.Value)
   351  			if err != nil {
   352  				return types.Nil, err
   353  			}
   354  
   355  			// now add back the last chunk and append the newly compiled condition
   356  			c.addChunk(prev)
   357  			// We do not need to add it back as an entrypoint here. It happens below
   358  			// outside this block
   359  
   360  			prev.Function.Args = append(prev.Function.Args, argValue)
   361  
   362  			c.prevID = "if"
   363  			return types.Nil, nil
   364  		}
   365  	}
   366  
   367  	argValue, err := c.compileExpression(arg.Value)
   368  	if err != nil {
   369  		return types.Nil, err
   370  	}
   371  
   372  	c.addChunk(&llx.Chunk{
   373  		Call: llx.Chunk_FUNCTION,
   374  		Id:   id,
   375  		Function: &llx.Function{
   376  			Type: string(types.Unset),
   377  			Args: []*llx.Primitive{argValue},
   378  		},
   379  	})
   380  	c.block.Entrypoints = append(c.block.Entrypoints, c.tailRef())
   381  	c.prevID = "if"
   382  
   383  	return types.Nil, nil
   384  }
   385  
   386  func compileElse(c *compiler, id string, call *parser.Call) (types.Type, error) {
   387  	if call != nil {
   388  		return types.Nil, errors.New("cannot have conditional arguments for else-clause, use another if-statement")
   389  	}
   390  
   391  	if len(c.block.Chunks) == 0 {
   392  		return types.Nil, errors.New("can only use else-statement after a preceding if-statement")
   393  	}
   394  
   395  	prev := c.block.Chunks[len(c.block.Chunks)-1]
   396  	if prev.Id != "if" {
   397  		return types.Nil, errors.New("can only use else-statement after a preceding if-statement")
   398  	}
   399  
   400  	if c.prevID != "if" {
   401  		return types.Nil, errors.New("can only use else-statement after a preceding if-statement (internal reference is wrong)")
   402  	}
   403  
   404  	c.prevID = "else"
   405  
   406  	return types.Nil, nil
   407  }
   408  
   409  func compileExpect(c *compiler, id string, call *parser.Call) (types.Type, error) {
   410  	if call == nil || len(call.Function) < 1 {
   411  		return types.Nil, errors.New("missing parameter for '" + id + "', it requires 1")
   412  	}
   413  	if len(call.Function) > 1 {
   414  		return types.Nil, errors.New("called '" + id + "' with too many arguments, it requires 1")
   415  	}
   416  
   417  	arg := call.Function[0]
   418  	if arg.Name != "" {
   419  		return types.Nil, errors.New("called '" + id + "' with a named argument, which is not supported")
   420  	}
   421  
   422  	argValue, err := c.compileExpression(arg.Value)
   423  	if err != nil {
   424  		return types.Nil, err
   425  	}
   426  
   427  	if err = c.generateEntrypoints(argValue); err != nil {
   428  		return types.Nil, err
   429  	}
   430  
   431  	typ := types.Bool
   432  	c.addChunk(&llx.Chunk{
   433  		Call: llx.Chunk_FUNCTION,
   434  		Id:   id,
   435  		Function: &llx.Function{
   436  			Type: string(typ),
   437  			Args: []*llx.Primitive{argValue},
   438  		},
   439  	})
   440  	c.block.Entrypoints = append(c.block.Entrypoints, c.tailRef())
   441  
   442  	return typ, nil
   443  }
   444  
   445  func compileScore(c *compiler, id string, call *parser.Call) (types.Type, error) {
   446  	if call == nil || len(call.Function) < 1 {
   447  		return types.Nil, errors.New("missing parameter for '" + id + "', it requires 1")
   448  	}
   449  
   450  	arg := call.Function[0]
   451  	if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil {
   452  		return types.Nil, errors.New("failed to get parameter for '" + id + "'")
   453  	}
   454  
   455  	argValue, err := c.compileExpression(arg.Value)
   456  	if err != nil {
   457  		return types.Nil, err
   458  	}
   459  
   460  	c.addChunk(&llx.Chunk{
   461  		Call: llx.Chunk_FUNCTION,
   462  		Id:   "score",
   463  		Function: &llx.Function{
   464  			Type: string(types.Score),
   465  			Args: []*llx.Primitive{argValue},
   466  		},
   467  	})
   468  
   469  	return types.Score, nil
   470  }
   471  
   472  func compileTypeof(c *compiler, id string, call *parser.Call) (types.Type, error) {
   473  	if call == nil || len(call.Function) < 1 {
   474  		return types.Nil, errors.New("missing parameter for '" + id + "', it requires 1")
   475  	}
   476  
   477  	arg := call.Function[0]
   478  	if arg == nil || arg.Value == nil || arg.Value.Operand == nil || arg.Value.Operand.Value == nil {
   479  		return types.Nil, errors.New("failed to get parameter for '" + id + "'")
   480  	}
   481  
   482  	argValue, err := c.compileExpression(arg.Value)
   483  	if err != nil {
   484  		return types.Nil, err
   485  	}
   486  
   487  	c.addChunk(&llx.Chunk{
   488  		Call: llx.Chunk_FUNCTION,
   489  		Id:   "typeof",
   490  		Function: &llx.Function{
   491  			Type: string(types.String),
   492  			Args: []*llx.Primitive{argValue},
   493  		},
   494  	})
   495  
   496  	return types.String, nil
   497  }
   498  
   499  func compileSwitch(c *compiler, id string, call *parser.Call) (types.Type, error) {
   500  	var ref *llx.Primitive
   501  
   502  	if call != nil && len(call.Function) != 0 {
   503  		arg := call.Function[0]
   504  		if arg.Name != "" {
   505  			return types.Nil, errors.New("called `" + id + "` with a named argument, which is not supported")
   506  		}
   507  
   508  		argValue, err := c.compileExpression(arg.Value)
   509  		if err != nil {
   510  			return types.Nil, err
   511  		}
   512  
   513  		ref = argValue
   514  	} else {
   515  		ref = &llx.Primitive{Type: string(types.Unset)}
   516  	}
   517  
   518  	c.addChunk(&llx.Chunk{
   519  		Call: llx.Chunk_FUNCTION,
   520  		Id:   id,
   521  		Function: &llx.Function{
   522  			Type: string(types.Unset),
   523  			Args: []*llx.Primitive{ref},
   524  		},
   525  	})
   526  	c.prevID = "switch"
   527  
   528  	return types.Nil, nil
   529  }
   530  
   531  func compileNever(c *compiler, id string, call *parser.Call) (types.Type, error) {
   532  	c.addChunk(&llx.Chunk{
   533  		Call:      llx.Chunk_PRIMITIVE,
   534  		Primitive: llx.NeverFuturePrimitive,
   535  	})
   536  
   537  	return types.Time, nil
   538  }