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  }