go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/mqlc/labels.go (about) 1 // Copyright (c) Mondoo, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package mqlc 5 6 import ( 7 "errors" 8 "regexp" 9 "strconv" 10 11 "go.mondoo.com/cnquery/llx" 12 "go.mondoo.com/cnquery/types" 13 "golang.org/x/text/transform" 14 "golang.org/x/text/unicode/norm" 15 ) 16 17 func createLabel(res *llx.CodeBundle, ref uint64, schema llx.Schema) (string, error) { 18 code := res.CodeV2 19 chunk := code.Chunk(ref) 20 21 if chunk.Call == llx.Chunk_PRIMITIVE { 22 // In the case of refs, we want to check for the name of the variable, 23 // which is what every final ref should lead to 24 if chunk.Primitive.Type == string(types.Ref) { 25 if deref, ok := chunk.Primitive.RefV2(); ok { 26 ref = deref 27 } 28 } 29 30 // TODO: better labels if we don't have it as a var 31 label := res.Vars[ref] 32 33 return label, nil 34 } 35 36 id := chunk.Id 37 if chunk.Function == nil { 38 return id, nil 39 } 40 41 // TODO: workaround to get past the builtin global call 42 // this needs proper handling for global calls 43 if chunk.Function.Binding == 0 && id != "if" && id != "createResource" { 44 return id, nil 45 } 46 47 var parentLabel string 48 var err error 49 if id == "createResource" { 50 if ref, ok := chunk.Function.Args[0].RefV2(); ok { 51 parentLabel, err = createLabel(res, ref, schema) 52 if err != nil { 53 return "", err 54 } 55 } 56 } else if chunk.Function.Binding != 0 { 57 parentLabel, err = createLabel(res, chunk.Function.Binding, schema) 58 if err != nil { 59 return "", err 60 } 61 } 62 63 var label string 64 switch id { 65 case "[]": 66 if len(chunk.Function.Args) != 1 { 67 panic("don't know how to extract label data from array access without args") 68 } 69 70 arg := chunk.Function.Args[0].RawData() 71 idx := arg.Value 72 73 switch arg.Type { 74 case types.Int: 75 label = "[" + strconv.FormatInt(idx.(int64), 10) + "]" 76 case types.String: 77 if chunk.Function.Type == string(types.Dict) && isAccessor(idx.(string)) { 78 label = idx.(string) 79 } else { 80 label = "[" + idx.(string) + "]" 81 } 82 default: 83 panic("cannot label array index of type " + arg.Type.Label()) 84 } 85 if parentLabel != "" { 86 if label != "" && label[0] == '[' { 87 label = parentLabel + label 88 } else { 89 label = parentLabel + "." + label 90 } 91 } 92 case "{}", "${}": 93 label = parentLabel 94 case "createResource": 95 label = parentLabel + "." + string(chunk.Type()) 96 case "if": 97 label = "if" 98 default: 99 if x, ok := llx.ComparableLabel(id); ok { 100 arg := chunk.Function.Args[0].LabelV2(code) 101 label = parentLabel + " " + x + " " + arg 102 } else if parentLabel == "" { 103 label = id 104 } else { 105 label = parentLabel + "." + id 106 } 107 } 108 109 // TODO: figure out why this string includes control characters in the first place 110 return stripCtlAndExtFromUnicode(label), nil 111 } 112 113 var reAccessor = regexp.MustCompile(`^[\p{L}\d_]+$`) 114 115 func isAccessor(s string) bool { 116 return reAccessor.MatchString(s) 117 } 118 119 // Unicode normalization and filtering, see http://blog.golang.org/normalization and 120 // http://godoc.org/golang.org/x/text/unicode/norm for more details. 121 func stripCtlAndExtFromUnicode(str string) string { 122 isOk := func(r rune) bool { 123 return r < 32 || r >= 127 124 } 125 // The isOk filter is such that there is no need to chain to norm.NFC 126 t := transform.Chain(norm.NFKD, transform.RemoveFunc(isOk)) 127 str, _, _ = transform.String(t, str) 128 return str 129 } 130 131 // UpdateLabels for the given code under the schema 132 func UpdateLabels(res *llx.CodeBundle, schema llx.Schema) error { 133 if res == nil || res.CodeV2 == nil { 134 return errors.New("cannot create labels without code") 135 } 136 137 for i := range res.CodeV2.Blocks { 138 err := updateLabels(res, res.CodeV2.Blocks[i], schema) 139 if err != nil { 140 return err 141 } 142 } 143 144 // not needed anymore since we have all the info in labels now 145 res.Vars = nil 146 147 return nil 148 } 149 150 func updateLabels(res *llx.CodeBundle, block *llx.Block, schema llx.Schema) error { 151 datapoints := block.Datapoints 152 code := res.CodeV2 153 labels := res.Labels.Labels 154 155 // We don't want assertions to become labels. Their data should not be printed 156 // regularly but instead be processed through the assertion itself 157 if code.Assertions != nil { 158 assertionPoints := map[uint64]struct{}{} 159 for _, assertion := range code.Assertions { 160 for j := range assertion.Refs { 161 assertionPoints[assertion.Refs[j]] = struct{}{} 162 } 163 } 164 165 filtered := []uint64{} 166 for i := range datapoints { 167 ref := datapoints[i] 168 if _, ok := assertionPoints[ref]; ok { 169 continue 170 } 171 filtered = append(filtered, ref) 172 } 173 datapoints = filtered 174 } 175 176 labelrefs := append(block.Entrypoints, datapoints...) 177 178 var err error 179 for _, entrypoint := range labelrefs { 180 checksum, ok := code.Checksums[entrypoint] 181 if !ok { 182 return errors.New("failed to create labels, cannot find checksum for this entrypoint " + strconv.FormatUint(uint64(entrypoint), 10)) 183 } 184 185 if _, ok := labels[checksum]; ok { 186 continue 187 } 188 189 labels[checksum], err = createLabel(res, entrypoint, schema) 190 191 if err != nil { 192 return err 193 } 194 } 195 196 // any more checksums that might have been set need to be removed, since we don't need them 197 // TODO: there must be a way to do this without having to create the label first 198 if code.Assertions != nil { 199 for _, assertion := range code.Assertions { 200 if !assertion.DecodeBlock { 201 continue 202 } 203 for i := 0; i < len(assertion.Checksums); i++ { 204 delete(labels, assertion.Checksums[i]) 205 } 206 } 207 } 208 209 return nil 210 }