Source file src/cmd/internal/dwarf/putvarabbrevgen_test.go

     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  
     5  package dwarf
     6  
     7  import (
     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  
    22  const pvagenfile = "./putvarabbrevgen.go"
    23  
    24  var pvaDoGenerate bool
    25  
    26  func 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).
    56  func 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  
    81  func 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  
   110  package dwarf
   111  
   112  var 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  
   138  type 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.
   147  func 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  
   195  func 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  
   232  func 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.
   239  func 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.
   257  func 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  
   271  func 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  
   305  func 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  

View as plain text