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 }