1// Copyright 2024 The Go Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package dwarf 6 7import ( 8 "bytes" 9 "flag" 10 "fmt" 11 "go/ast" 12 "go/format" 13 "go/parser" 14 "go/printer" 15 "go/token" 16 "os" 17 "strconv" 18 "strings" 19 "testing" 20) 21 22const pvagenfile = "./putvarabbrevgen.go" 23 24var pvaDoGenerate bool 25 26func TestMain(m *testing.M) { 27 flag.BoolVar(&pvaDoGenerate, "generate", false, "regenerates "+pvagenfile) 28 flag.Parse() 29 os.Exit(m.Run()) 30 31} 32 33// TestPutVarAbbrevGenerator checks that putvarabbrevgen.go is kept in sync 34// with the contents of functions putvar and putAbstractVar. If test flag -generate 35// is specified the file is regenerated instead. 36// 37// The block of code in putvar and putAbstractVar that picks the correct 38// abbrev is also generated automatically by this function by looking at all 39// the possible paths in their CFG and the order in which putattr is called. 40// 41// There are some restrictions on how putattr can be used in putvar and 42// putAbstractVar: 43// 44// 1. it shouldn't appear inside a for or switch statements 45// 2. it can appear within any number of nested if/else statements but the 46// conditionals must not change after putvarAbbrev/putAbstractVarAbbrev 47// are called 48// 3. the form argument of putattr must be a compile time constant 49// 4. each putattr call must be followed by a comment containing the name of 50// the attribute it is setting 51// 52// TestPutVarAbbrevGenerator will fail if (1) or (4) are not respected and 53// the generated code will not compile if (3) is violated. Violating (2) 54// will result in code silently wrong code (which will usually be detected 55// by one of the tests that parse debug_info). 56func TestPutVarAbbrevGenerator(t *testing.T) { 57 spvagenfile := pvagenerate(t) 58 59 if pvaDoGenerate { 60 err := os.WriteFile(pvagenfile, []byte(spvagenfile), 0660) 61 if err != nil { 62 t.Fatal(err) 63 } 64 return 65 } 66 67 slurp := func(name string) string { 68 out, err := os.ReadFile(name) 69 if err != nil { 70 t.Fatal(err) 71 } 72 return string(out) 73 } 74 75 if spvagenfile != slurp(pvagenfile) { 76 t.Error(pvagenfile + " is out of date") 77 } 78 79} 80 81func pvagenerate(t *testing.T) string { 82 var fset token.FileSet 83 f, err := parser.ParseFile(&fset, "./dwarf.go", nil, parser.ParseComments) 84 if err != nil { 85 t.Fatal(err) 86 } 87 cm := ast.NewCommentMap(&fset, f, f.Comments) 88 abbrevs := make(map[string]int) 89 funcs := map[string]ast.Stmt{} 90 for _, decl := range f.Decls { 91 decl, ok := decl.(*ast.FuncDecl) 92 if !ok || decl.Body == nil { 93 continue 94 } 95 if decl.Name.Name == "putvar" || decl.Name.Name == "putAbstractVar" { 96 // construct the simplified CFG 97 pvagraph, _ := pvacfgbody(t, &fset, cm, decl.Body.List) 98 funcs[decl.Name.Name+"Abbrev"] = pvacfgvisit(pvagraph, abbrevs) 99 } 100 } 101 abbrevslice := make([]string, len(abbrevs)) 102 for abbrev, n := range abbrevs { 103 abbrevslice[n] = abbrev 104 } 105 106 buf := new(bytes.Buffer) 107 fmt.Fprint(buf, `// Code generated by TestPutVarAbbrevGenerator. DO NOT EDIT. 108// Regenerate using go test -run TestPutVarAbbrevGenerator -generate instead. 109 110package dwarf 111 112var putvarAbbrevs = []dwAbbrev{ 113`) 114 115 for _, abbrev := range abbrevslice { 116 fmt.Fprint(buf, abbrev+",\n") 117 } 118 119 fmt.Fprint(buf, "\n}\n\n") 120 121 fmt.Fprint(buf, "func putAbstractVarAbbrev(v *Var) int {\n") 122 format.Node(buf, &token.FileSet{}, funcs["putAbstractVarAbbrev"]) 123 fmt.Fprint(buf, "}\n\n") 124 125 fmt.Fprint(buf, "func putvarAbbrev(v *Var, concrete, withLoclist bool) int {\n") 126 format.Node(buf, &token.FileSet{}, funcs["putvarAbbrev"]) 127 fmt.Fprint(buf, "}\n") 128 129 out, err := format.Source(buf.Bytes()) 130 if err != nil { 131 t.Log(string(buf.Bytes())) 132 t.Fatal(err) 133 } 134 135 return string(out) 136} 137 138type pvacfgnode struct { 139 attr, form string 140 141 cond ast.Expr 142 then, els *pvacfgnode 143} 144 145// pvacfgbody generates a simplified CFG for a slice of statements, 146// containing only calls to putattr and the if statements affecting them. 147func pvacfgbody(t *testing.T, fset *token.FileSet, cm ast.CommentMap, body []ast.Stmt) (start, end *pvacfgnode) { 148 add := func(n *pvacfgnode) { 149 if start == nil || end == nil { 150 start = n 151 end = n 152 } else { 153 end.then = n 154 end = n 155 } 156 } 157 for _, stmt := range body { 158 switch stmt := stmt.(type) { 159 case *ast.ExprStmt: 160 if x, _ := stmt.X.(*ast.CallExpr); x != nil { 161 funstr := exprToString(x.Fun) 162 if funstr == "putattr" { 163 form, _ := x.Args[3].(*ast.Ident) 164 if form == nil { 165 t.Fatalf("%s invalid use of putattr", fset.Position(x.Pos())) 166 } 167 cmt := findLineComment(cm, stmt) 168 if cmt == nil { 169 t.Fatalf("%s invalid use of putattr (no comment containing the attribute name)", fset.Position(x.Pos())) 170 } 171 add(&pvacfgnode{attr: strings.TrimSpace(cmt.Text[2:]), form: form.Name}) 172 } 173 } 174 case *ast.IfStmt: 175 ifStart, ifEnd := pvacfgif(t, fset, cm, stmt) 176 if ifStart != nil { 177 add(ifStart) 178 end = ifEnd 179 } 180 default: 181 // check that nothing under this contains a putattr call 182 ast.Inspect(stmt, func(n ast.Node) bool { 183 if call, _ := n.(*ast.CallExpr); call != nil { 184 if exprToString(call.Fun) == "putattr" { 185 t.Fatalf("%s use of putattr in unsupported block", fset.Position(call.Pos())) 186 } 187 } 188 return true 189 }) 190 } 191 } 192 return start, end 193} 194 195func pvacfgif(t *testing.T, fset *token.FileSet, cm ast.CommentMap, ifstmt *ast.IfStmt) (start, end *pvacfgnode) { 196 thenStart, thenEnd := pvacfgbody(t, fset, cm, ifstmt.Body.List) 197 var elseStart, elseEnd *pvacfgnode 198 if ifstmt.Else != nil { 199 switch els := ifstmt.Else.(type) { 200 case *ast.IfStmt: 201 elseStart, elseEnd = pvacfgif(t, fset, cm, els) 202 case *ast.BlockStmt: 203 elseStart, elseEnd = pvacfgbody(t, fset, cm, els.List) 204 default: 205 t.Fatalf("%s: unexpected statement %T", fset.Position(els.Pos()), els) 206 } 207 } 208 209 if thenStart != nil && elseStart != nil && thenStart == thenEnd && elseStart == elseEnd && thenStart.form == elseStart.form && thenStart.attr == elseStart.attr { 210 return thenStart, thenEnd 211 } 212 213 if thenStart != nil || elseStart != nil { 214 start = &pvacfgnode{cond: ifstmt.Cond} 215 end = &pvacfgnode{} 216 if thenStart != nil { 217 start.then = thenStart 218 thenEnd.then = end 219 } else { 220 start.then = end 221 } 222 if elseStart != nil { 223 start.els = elseStart 224 elseEnd.then = end 225 } else { 226 start.els = end 227 } 228 } 229 return start, end 230} 231 232func exprToString(t ast.Expr) string { 233 var buf bytes.Buffer 234 printer.Fprint(&buf, token.NewFileSet(), t) 235 return buf.String() 236} 237 238// findLineComment finds the line comment for statement stmt. 239func findLineComment(cm ast.CommentMap, stmt *ast.ExprStmt) *ast.Comment { 240 var r *ast.Comment 241 for _, cmtg := range cm[stmt] { 242 for _, cmt := range cmtg.List { 243 if cmt.Slash > stmt.Pos() { 244 if r != nil { 245 return nil 246 } 247 r = cmt 248 } 249 } 250 } 251 return r 252} 253 254// pvacfgvisit visits the CFG depth first, populates abbrevs with all 255// possible dwAbbrev definitions and returns a tree of if/else statements 256// that picks the correct abbrev. 257func pvacfgvisit(pvacfg *pvacfgnode, abbrevs map[string]int) ast.Stmt { 258 r := &ast.IfStmt{Cond: &ast.BinaryExpr{ 259 Op: token.EQL, 260 X: &ast.SelectorExpr{X: &ast.Ident{Name: "v"}, Sel: &ast.Ident{Name: "Tag"}}, 261 Y: &ast.Ident{Name: "DW_TAG_variable"}}} 262 r.Body = &ast.BlockStmt{List: []ast.Stmt{ 263 pvacfgvisitnode(pvacfg, "DW_TAG_variable", []*pvacfgnode{}, abbrevs), 264 }} 265 r.Else = &ast.BlockStmt{List: []ast.Stmt{ 266 pvacfgvisitnode(pvacfg, "DW_TAG_formal_parameter", []*pvacfgnode{}, abbrevs), 267 }} 268 return r 269} 270 271func pvacfgvisitnode(pvacfg *pvacfgnode, tag string, path []*pvacfgnode, abbrevs map[string]int) ast.Stmt { 272 if pvacfg == nil { 273 abbrev := toabbrev(tag, path) 274 if _, ok := abbrevs[abbrev]; !ok { 275 abbrevs[abbrev] = len(abbrevs) 276 } 277 return &ast.ReturnStmt{ 278 Results: []ast.Expr{&ast.BinaryExpr{ 279 Op: token.ADD, 280 X: &ast.Ident{Name: "DW_ABRV_PUTVAR_START"}, 281 Y: &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(abbrevs[abbrev])}}}} 282 } 283 if pvacfg.attr != "" { 284 return pvacfgvisitnode(pvacfg.then, tag, append(path, pvacfg), abbrevs) 285 } else if pvacfg.cond != nil { 286 if bx, _ := pvacfg.cond.(*ast.BinaryExpr); bx != nil && bx.Op == token.EQL && exprToString(bx.X) == "v.Tag" { 287 // this condition is "v.Tag == Xxx", check the value of 'tag' 288 y := exprToString(bx.Y) 289 if y == tag { 290 return pvacfgvisitnode(pvacfg.then, tag, path, abbrevs) 291 } else { 292 return pvacfgvisitnode(pvacfg.els, tag, path, abbrevs) 293 } 294 } else { 295 r := &ast.IfStmt{Cond: pvacfg.cond} 296 r.Body = &ast.BlockStmt{List: []ast.Stmt{pvacfgvisitnode(pvacfg.then, tag, path, abbrevs)}} 297 r.Else = &ast.BlockStmt{List: []ast.Stmt{pvacfgvisitnode(pvacfg.els, tag, path, abbrevs)}} 298 return r 299 } 300 } else { 301 return pvacfgvisitnode(pvacfg.then, tag, path, abbrevs) 302 } 303} 304 305func toabbrev(tag string, path []*pvacfgnode) string { 306 buf := new(bytes.Buffer) 307 fmt.Fprintf(buf, "{\n%s,\nDW_CHILDREN_no,\n[]dwAttrForm{\n", tag) 308 for _, node := range path { 309 if node.cond == nil { 310 fmt.Fprintf(buf, "{%s, %s},\n", node.attr, node.form) 311 312 } 313 } 314 fmt.Fprint(buf, "},\n}") 315 return buf.String() 316} 317