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 }