go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/llx/code.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package llx 5 6 import ( 7 "bytes" 8 "sort" 9 10 "go.mondoo.com/cnquery/checksums" 11 "go.mondoo.com/cnquery/types" 12 ) 13 14 func (b *Block) ChunkIndex() uint32 { 15 return uint32(len(b.Chunks)) 16 } 17 18 func ChunkIndex(ref uint64) uint32 { 19 return uint32(ref & 0xFFFFFFFF) 20 } 21 22 func absRef(blockRef uint64, relRef uint32) uint64 { 23 return (blockRef & 0xFFFFFFFF00000000) | uint64(relRef) 24 } 25 26 // TailRef returns the reference to the last chunk of the block 27 func (b *Block) TailRef(blockRef uint64) uint64 { 28 return absRef(blockRef, b.ChunkIndex()) 29 } 30 31 // HeadRef returns the reference to the first chunk of the block 32 func (b *Block) HeadRef(blockRef uint64) uint64 { 33 return absRef(blockRef, 1) 34 } 35 36 func (b *Block) ReplaceEntrypoint(old uint64, nu uint64) { 37 for i := range b.Entrypoints { 38 if b.Entrypoints[i] == old { 39 b.Entrypoints[i] = nu 40 return 41 } 42 } 43 } 44 45 // LastChunk is the last chunk in the list or nil 46 func (b *Block) LastChunk() *Chunk { 47 max := len(b.Chunks) 48 if max == 0 { 49 return nil 50 } 51 return b.Chunks[max-1] 52 } 53 54 // AddChunk to the list of chunks 55 func (b *Block) AddChunk(code *CodeV2, blockRef uint64, c *Chunk) { 56 nuRef := b.TailRef(blockRef) + 1 57 code.Checksums[nuRef] = c.ChecksumV2(blockRef, code) 58 b.Chunks = append(b.Chunks, c) 59 } 60 61 func (b *Block) AddArgumentPlaceholder(code *CodeV2, blockRef uint64, typ types.Type, checksum string) { 62 b.AddChunk(code, blockRef, &Chunk{ 63 Call: Chunk_PRIMITIVE, 64 Primitive: &Primitive{Type: string(typ)}, // placeholder 65 }) 66 code.Checksums[b.TailRef(blockRef)] = checksum 67 b.Parameters++ 68 } 69 70 // PopChunk removes the last chunk from the block and returns it 71 func (b *Block) PopChunk(code *CodeV2, blockRef uint64) (prev *Chunk, isEntrypoint bool, isDatapoint bool) { 72 prev = nil 73 isEntrypoint = false 74 isDatapoint = false 75 76 if len(b.Chunks) == 0 { 77 return nil, false, false 78 } 79 80 tailRef := b.TailRef(blockRef) 81 delete(code.Checksums, tailRef) 82 83 if len(b.Entrypoints) > 0 && b.Entrypoints[len(b.Entrypoints)-1] == tailRef { 84 isEntrypoint = true 85 b.Entrypoints = b.Entrypoints[:len(b.Entrypoints)-1] 86 } 87 88 if len(b.Datapoints) > 0 && b.Datapoints[len(b.Datapoints)-1] == tailRef { 89 isDatapoint = true 90 b.Datapoints = b.Datapoints[:len(b.Datapoints)-1] 91 } 92 93 max := len(b.Chunks) 94 last := b.Chunks[max-1] 95 b.Chunks = b.Chunks[:max-1] 96 return last, isEntrypoint, isDatapoint 97 } 98 99 // ChunkIndex is the index of the last chunk that was added 100 func (l *CodeV2) TailRef(blockRef uint64) uint64 { 101 return l.Block(blockRef).TailRef(blockRef) 102 } 103 104 // Retrieve a chunk for the given ref 105 func (l *CodeV2) Chunk(ref uint64) *Chunk { 106 return l.Block(ref).Chunks[uint32(ref)-1] 107 } 108 109 // Retrieve a block for the given ref 110 func (l *CodeV2) Block(ref uint64) *Block { 111 return l.Blocks[uint32(ref>>32)-1] 112 } 113 114 // LastBlockRef retrieves the ref for the last block in the code 115 func (l *CodeV2) LastBlockRef() uint64 { 116 return uint64(len(l.Blocks) << 32) 117 } 118 119 // AddBlock adds a new block at the end of this code and returns its ref 120 func (l *CodeV2) AddBlock() (*Block, uint64) { 121 block := &Block{} 122 l.Blocks = append(l.Blocks, block) 123 return block, uint64(len(l.Blocks)) << 32 124 } 125 126 func (c *CodeV2) Entrypoints() []uint64 { 127 if len(c.Blocks) == 0 { 128 return []uint64{} 129 } 130 131 return c.Blocks[0].Entrypoints 132 } 133 134 func (c *CodeV2) Datapoints() []uint64 { 135 if len(c.Blocks) == 0 { 136 return []uint64{} 137 } 138 139 return c.Blocks[0].Datapoints 140 } 141 142 // DereferencedBlockType returns the type of a block, which is a specific 143 // type if it is a single-value block 144 func (l *CodeV2) DereferencedBlockType(b *Block) types.Type { 145 if len(b.Entrypoints) != 1 { 146 return types.Block 147 } 148 149 ep := b.Entrypoints[0] 150 chunk := b.Chunks[(ep-1)&0xFFFFFFFF] 151 return chunk.DereferencedTypeV2(l) 152 } 153 154 func (block *Block) checksum(l *CodeV2) string { 155 c := checksums.New 156 for i := range block.Entrypoints { 157 c = c.Add(l.Checksums[block.Entrypoints[i]]) 158 } 159 return c.String() 160 } 161 162 // checksum from this code 163 func (l *CodeV2) checksum() string { 164 checksum := checksums.New 165 166 for i := range l.Blocks { 167 checksum = checksum.Add(l.Blocks[i].checksum(l)) 168 } 169 170 assertionRefs := make([]uint64, 0, len(l.Assertions)) 171 for k := range l.Assertions { 172 assertionRefs = append(assertionRefs, k) 173 } 174 sort.Slice(assertionRefs, func(i, j int) bool { return assertionRefs[i] < assertionRefs[j] }) 175 for _, ref := range assertionRefs { 176 checksum = checksum.Add(l.Checksums[ref]) 177 } 178 179 if len(l.Blocks) == 0 || len(l.Blocks[0].Entrypoints) == 0 { 180 // Why do we even handle this case? Because at this point we still have 181 // all raw entrypoints, which may get shuffled around in the step after this. 182 // This also means entrypoints aren't sanitized. We may not have any. 183 // 184 // TODO: review this behavior! 185 // We may want to do the entrypoint handling earlier. 186 //panic("received a code without any entrypoints") 187 } 188 189 return checksum.String() 190 } 191 192 // UpdateID of the piece of code 193 func (l *CodeV2) UpdateID() { 194 l.Id = l.checksum() 195 } 196 197 // RefDatapoints returns the additional datapoints that inform a ref. 198 // Typically used when writing tests and providing additional data when the test fails. 199 func (l *CodeV2) RefDatapoints(ref uint64) []uint64 { 200 if assertion, ok := l.Assertions[ref]; ok { 201 return assertion.Refs 202 } 203 204 chunk := l.Chunk(ref) 205 206 if chunk.Id == "if" && chunk.Function != nil && len(chunk.Function.Args) != 0 { 207 var ok bool 208 ref, ok = chunk.Function.Args[0].RefV2() 209 if !ok { 210 return nil 211 } 212 chunk = l.Chunk(ref) 213 } 214 215 if chunk.Id == "" { 216 return nil 217 } 218 219 // nothing to do for primitives (unclear if we need to investigate refs here) 220 if chunk.Call != Chunk_FUNCTION || chunk.Function == nil { 221 return nil 222 } 223 224 switch chunk.Id { 225 case "$all", "$one", "$any", "$none": 226 return []uint64{ref - 1} 227 } 228 229 if _, ok := ComparableLabel(chunk.Id); !ok { 230 return nil 231 } 232 233 var res []uint64 234 235 // at this point we have a comparable 236 // so 2 jobs: check the left, check the right. if it's static, ignore. if not, add 237 left := chunk.Function.Binding 238 if left != 0 { 239 leftChunk := l.Chunk(left) 240 if leftChunk != nil && !leftChunk.isStatic() { 241 res = append(res, left) 242 } 243 } 244 245 if len(chunk.Function.Args) != 0 { 246 rightPrim := chunk.Function.Args[0] 247 if rightPrim != nil && types.Type(rightPrim.Type) == types.Ref { 248 right, ok := rightPrim.RefV2() 249 if ok { 250 res = append(res, right) 251 } 252 } 253 } 254 255 return res 256 } 257 258 func (l *CodeV2) refValues(bundle *CodeBundle, ref uint64, lookup func(s string) (*RawResult, bool)) []*RawResult { 259 checksum := l.Checksums[ref] 260 checksumRes, ok := lookup(checksum) 261 if ok { 262 return []*RawResult{checksumRes} 263 } 264 265 chunk := l.Chunk(ref) 266 267 if chunk.Id == "if" && chunk.Function != nil && len(chunk.Function.Args) != 0 { 268 // FIXME: we should be checking for the result of the if-condition and then proceed 269 // with whatever result is applicable; not poke at possible results 270 271 // function arguments are functions refs to: 272 // [1] = the first condition, [2] = the second condition 273 fref, ok := chunk.Function.Args[1].RefV2() 274 if ok { 275 if part, ok := lookup(l.Checksums[fref]); ok { 276 return []*RawResult{part} 277 } 278 } 279 280 fref, ok = chunk.Function.Args[2].RefV2() 281 if ok { 282 if part, ok := lookup(l.Checksums[fref]); ok { 283 return []*RawResult{part} 284 } 285 } 286 } 287 288 return nil 289 } 290 291 func (l *CodeV2) returnValues(bundle *CodeBundle, lookup func(s string) (*RawResult, bool)) []*RawResult { 292 var res []*RawResult 293 294 if len(l.Blocks) == 0 { 295 return res 296 } 297 block := l.Blocks[0] 298 299 for i := range block.Entrypoints { 300 ep := block.Entrypoints[i] 301 cur := l.refValues(bundle, ep, lookup) 302 if cur != nil { 303 res = append(res, cur...) 304 } 305 } 306 307 return res 308 } 309 310 func (l *CodeV2) entrypoint2assessment(bundle *CodeBundle, ref uint64, lookup func(s string) (*RawResult, bool)) *AssessmentItem { 311 code := bundle.CodeV2 312 checksum := code.Checksums[ref] 313 314 checksumRes, ok := lookup(checksum) 315 if !ok { 316 return nil 317 } 318 319 truthy, _ := checksumRes.Data.IsTruthy() 320 321 res := AssessmentItem{ 322 Checksum: checksum, 323 Entrypoint: ref, 324 Success: truthy, 325 } 326 327 if checksumRes.Data.Error != nil { 328 res.Error = checksumRes.Data.Error.Error() 329 } 330 331 // explicit assessments 332 if assertion, ok := bundle.Assertions[checksum]; ok { 333 res.IsAssertion = true 334 335 if assertion.DecodeBlock { 336 sum := assertion.Checksums[0] 337 raw, ok := lookup(sum) 338 if !ok { 339 res.Error = "cannot find required data block for assessment" 340 return &res 341 } 342 343 x := raw.Result().Data 344 if x == nil { 345 res.Error = "required data block for assessment is nil" 346 return &res 347 } 348 349 dataMap := map[string]*Primitive(x.Map) 350 351 cnt := len(assertion.Checksums) - 1 352 res.Data = make([]*Primitive, cnt) 353 for i := 0; i < cnt; i++ { 354 sum = assertion.Checksums[i+1] 355 res.Data[i], ok = dataMap[sum] 356 if !ok { 357 res.Error = "required data field is not in block for assessment" 358 } 359 } 360 361 res.Template = assertion.Template 362 return &res 363 } 364 365 data := make([]*Primitive, len(assertion.Checksums)) 366 for j := range assertion.Checksums { 367 sum := assertion.Checksums[j] 368 369 raw, ok := lookup(sum) 370 if !ok { 371 res.Error = "cannot find required data" 372 return &res 373 } 374 375 data[j] = raw.Result().Data 376 } 377 378 res.Data = data 379 res.Template = assertion.Template 380 return &res 381 } 382 383 chunk := l.Chunk(ref) 384 385 if chunk.Id == "if" { 386 // Our current assessment structure cannot handle nesting very well 387 // We return nil here for now. Our result printing has good enough 388 // information to convey this nesting and what exactly went wrong 389 return nil 390 } 391 392 if chunk.Call == Chunk_PRIMITIVE { 393 res.Actual = chunk.Primitive 394 return &res 395 } 396 397 if chunk.Call != Chunk_FUNCTION { 398 res.Error = "unknown type of chunk" 399 return &res 400 } 401 402 if chunk.Function == nil { 403 // this only happens when we have a call chain that resembles a resource 404 // which is used without any init arguments 405 chunk.Function = &Function{Type: string(types.Resource(chunk.Id))} 406 } 407 408 if chunk.Id == "" { 409 res.Error = "chunk has unknown identifier" 410 return &res 411 } 412 413 switch chunk.Id { 414 case "$one", "$all", "$none", "$any": 415 res.IsAssertion = true 416 res.Operation = chunk.Id[1:] 417 418 if !truthy { 419 listRef := chunk.Function.Binding 420 // Find the datapoint linked to this listRef 421 // For .all(...) queries and alike, all is bound to a list. 422 // This list only has the resource ids as datpoints. 423 // But earlier on, we also bound a datapoint for the default fields to the list. 424 // We need to find this datapoint and use it as the listRef. 425 OUTER: 426 for i := range code.Blocks { 427 refIsEntrypoint := false 428 for j := range code.Blocks[i].Entrypoints { 429 if code.Blocks[i].Entrypoints[j] == ref { 430 refIsEntrypoint = true 431 break 432 } 433 } 434 if !refIsEntrypoint { 435 continue 436 } 437 for j := len(code.Blocks[i].Datapoints) - 1; j >= 0; j-- { 438 // skip the resource ids datapoint 439 if code.Blocks[i].Datapoints[j] == listRef { 440 continue 441 } 442 chunk := code.Chunk(code.Blocks[i].Datapoints[j]) 443 // this contains the default values 444 if chunk.Function.Binding == listRef { 445 listRef = code.Blocks[i].Datapoints[j] 446 break OUTER 447 } 448 } 449 } 450 listChecksum := code.Checksums[listRef] 451 list, ok := lookup(listChecksum) 452 if !ok { 453 res.Error = "cannot find value for assessment (" + res.Operation + ")" 454 return &res 455 } 456 457 res.Actual = list.Result().Data 458 } else { 459 res.Actual = BoolPrimitive(true) 460 } 461 462 return &res 463 } 464 465 // FIXME: support child operations inside of block calls "{}" / "${}" 466 467 if label, found := ComparableLabel(chunk.Id); found { 468 res.Operation = label 469 } else { 470 cRes := checksumRes.Result() 471 472 if checksumRes.Data.Type != types.Bool { 473 res.Actual = cRes.Data 474 } else { 475 res.Operation = "==" 476 res.Expected = BoolPrimitive(true) 477 res.Actual = cRes.Data 478 res.IsAssertion = true 479 } 480 return &res 481 } 482 483 res.IsAssertion = true 484 485 // at this point we have a comparable 486 // so 2 jobs: check the left, check the right. if it's static, ignore. if not, add 487 left := chunk.Function.Binding 488 if left != 0 { 489 leftChunk := l.Chunk(left) 490 if leftChunk == nil { 491 res.Actual = &Primitive{ 492 Type: string(types.Any), 493 Value: []byte("< unknown expected value >"), 494 } 495 } 496 497 if leftChunk.isStatic() { 498 res.Actual = leftChunk.Primitive 499 } else { 500 leftSum := code.Checksums[left] 501 leftRes, ok := lookup(leftSum) 502 if !ok { 503 res.Actual = nil 504 } else { 505 res.Actual = leftRes.Result().Data 506 } 507 } 508 } 509 510 if len(chunk.Function.Args) == 0 { 511 return &res 512 } 513 514 rightPrim := chunk.Function.Args[0] 515 if rightPrim == nil { 516 res.Expected = &Primitive{ 517 Type: string(types.Any), 518 Value: []byte("< unknown actual value >"), 519 } 520 } 521 522 if types.Type(rightPrim.Type) != types.Ref { 523 res.Expected = rightPrim 524 } else { 525 right, ok := rightPrim.RefV2() 526 if !ok { 527 res.Expected = &Primitive{ 528 Type: string(types.Any), 529 Value: []byte("< unknown actual value >"), 530 } 531 } else { 532 rightSum := code.Checksums[right] 533 rightRes, ok := lookup(rightSum) 534 if !ok { 535 res.Expected = nil 536 } else { 537 res.Expected = rightRes.Result().Data 538 } 539 } 540 } 541 542 return &res 543 } 544 545 // ComparableLabel takes any arbitrary label and returns the 546 // operation as a printable string and true if it is a comparable, otherwise "" and false. 547 func ComparableLabel(label string) (string, bool) { 548 if label == "" { 549 return "", false 550 } 551 552 start := 0 553 for bytes.IndexByte(comparableIndicators, label[start]) == -1 { 554 start++ 555 if start >= len(label) { 556 return "", false 557 } 558 } 559 560 x := label[start : start+1] 561 if _, ok := comparableOperations[x]; ok { 562 return x, true 563 } 564 if len(label) == 1 { 565 return "", false 566 } 567 568 x = label[start : start+2] 569 if _, ok := comparableOperations[x]; ok { 570 return x, true 571 } 572 573 return "", false 574 } 575 576 var comparableIndicators = []byte{'=', '!', '>', '<', '&', '|'} 577 578 var comparableOperations = map[string]struct{}{ 579 "==": {}, 580 "!=": {}, 581 ">": {}, 582 "<": {}, 583 ">=": {}, 584 "<=": {}, 585 "&&": {}, 586 "||": {}, 587 } 588 589 func (c *Chunk) isStatic() bool { 590 if c.Call != Chunk_PRIMITIVE { 591 return false 592 } 593 594 if types.Type(c.Primitive.Type) == types.Ref { 595 return false 596 } 597 598 return true 599 }