gitlab.com/Raven-IO/raven-delve@v1.22.4/pkg/proc/evalop/evalcompile.go (about)

     1  package evalop
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"go/ast"
     8  	"go/constant"
     9  	"go/parser"
    10  	"go/printer"
    11  	"go/scanner"
    12  	"go/token"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf/godwarf"
    17  	"gitlab.com/Raven-IO/raven-delve/pkg/dwarf/reader"
    18  )
    19  
    20  var (
    21  	ErrFuncCallNotAllowed = errors.New("function calls not allowed without using 'call'")
    22  )
    23  
    24  type compileCtx struct {
    25  	evalLookup
    26  	ops        []Op
    27  	allowCalls bool
    28  	curCall    int
    29  }
    30  
    31  type evalLookup interface {
    32  	FindTypeExpr(ast.Expr) (godwarf.Type, error)
    33  	HasLocal(string) bool
    34  	HasGlobal(string, string) bool
    35  	HasBuiltin(string) bool
    36  	LookupRegisterName(string) (int, bool)
    37  }
    38  
    39  // CompileAST compiles the expression t into a list of instructions.
    40  func CompileAST(lookup evalLookup, t ast.Expr) ([]Op, error) {
    41  	ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
    42  	err := ctx.compileAST(t)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	err = ctx.depthCheck(1)
    48  	if err != nil {
    49  		return ctx.ops, err
    50  	}
    51  	return ctx.ops, nil
    52  }
    53  
    54  // Compile compiles the expression expr into a list of instructions.
    55  // If canSet is true expressions like "x = y" are also accepted.
    56  func Compile(lookup evalLookup, expr string, canSet bool) ([]Op, error) {
    57  	t, err := parser.ParseExpr(expr)
    58  	if err != nil {
    59  		if canSet {
    60  			eqOff, isAs := isAssignment(err)
    61  			if isAs {
    62  				return CompileSet(lookup, expr[:eqOff], expr[eqOff+1:])
    63  			}
    64  		}
    65  		return nil, err
    66  	}
    67  	return CompileAST(lookup, t)
    68  }
    69  
    70  func isAssignment(err error) (int, bool) {
    71  	el, isScannerErr := err.(scanner.ErrorList)
    72  	if isScannerErr && el[0].Msg == "expected '==', found '='" {
    73  		return el[0].Pos.Offset, true
    74  	}
    75  	return 0, false
    76  }
    77  
    78  // CompileSet compiles the expression setting lhexpr to rhexpr into a list of
    79  // instructions.
    80  func CompileSet(lookup evalLookup, lhexpr, rhexpr string) ([]Op, error) {
    81  	lhe, err := parser.ParseExpr(lhexpr)
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	rhe, err := parser.ParseExpr(rhexpr)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	ctx := &compileCtx{evalLookup: lookup, allowCalls: true}
    91  	err = ctx.compileAST(rhe)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	if isStringLiteral(rhe) {
    97  		ctx.compileAllocLiteralString()
    98  	}
    99  
   100  	err = ctx.compileAST(lhe)
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  
   105  	ctx.pushOp(&SetValue{lhe: lhe, Rhe: rhe})
   106  
   107  	err = ctx.depthCheck(0)
   108  	if err != nil {
   109  		return ctx.ops, err
   110  	}
   111  	return ctx.ops, nil
   112  }
   113  
   114  func (ctx *compileCtx) compileAllocLiteralString() {
   115  	jmp := &Jump{When: JumpIfAllocStringChecksFail}
   116  	ctx.pushOp(jmp)
   117  
   118  	ctx.compileSpecialCall("runtime.mallocgc", []ast.Expr{
   119  		&ast.BasicLit{Kind: token.INT, Value: "0"},
   120  		&ast.Ident{Name: "nil"},
   121  		&ast.Ident{Name: "false"},
   122  	}, []Op{
   123  		&PushLen{},
   124  		&PushNil{},
   125  		&PushConst{constant.MakeBool(false)},
   126  	})
   127  
   128  	ctx.pushOp(&ConvertAllocToString{})
   129  	jmp.Target = len(ctx.ops)
   130  }
   131  
   132  func (ctx *compileCtx) compileSpecialCall(fnname string, argAst []ast.Expr, args []Op) {
   133  	id := ctx.curCall
   134  	ctx.curCall++
   135  	ctx.pushOp(&CallInjectionStartSpecial{
   136  		id:     id,
   137  		FnName: fnname,
   138  		ArgAst: argAst})
   139  	ctx.pushOp(&CallInjectionSetTarget{id: id})
   140  
   141  	for i := range args {
   142  		ctx.pushOp(args[i])
   143  		ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i})
   144  	}
   145  
   146  	ctx.pushOp(&CallInjectionComplete{id: id})
   147  }
   148  
   149  func (ctx *compileCtx) pushOp(op Op) {
   150  	ctx.ops = append(ctx.ops, op)
   151  }
   152  
   153  // depthCheck validates the list of instructions produced by Compile and
   154  // CompileSet by performing a stack depth check.
   155  // It calculates the depth of the stack at every instruction in ctx.ops and
   156  // checks that they have enough arguments to execute. For instructions that
   157  // can be reached through multiple paths (because of a jump) it checks that
   158  // all paths reach the instruction with the same stack depth.
   159  // Finally it checks that the stack depth after all instructions have
   160  // executed is equal to endDepth.
   161  func (ctx *compileCtx) depthCheck(endDepth int) error {
   162  	depth := make([]int, len(ctx.ops)+1) // depth[i] is the depth of the stack before i-th instruction
   163  	for i := range depth {
   164  		depth[i] = -1
   165  	}
   166  	depth[0] = 0
   167  
   168  	var err error
   169  	checkAndSet := func(j, d int) { // sets depth[j] to d after checking that we can
   170  		if depth[j] < 0 {
   171  			depth[j] = d
   172  		}
   173  		if d != depth[j] {
   174  			err = fmt.Errorf("internal debugger error: depth check error at instruction %d: expected depth %d have %d (jump target)\n%s", j, d, depth[j], Listing(depth, ctx.ops))
   175  		}
   176  	}
   177  
   178  	for i, op := range ctx.ops {
   179  		npop, npush := op.depthCheck()
   180  		if depth[i] < npop {
   181  			return fmt.Errorf("internal debugger error: depth check error at instruction %d: expected at least %d have %d\n%s", i, npop, depth[i], Listing(depth, ctx.ops))
   182  		}
   183  		d := depth[i] - npop + npush
   184  		checkAndSet(i+1, d)
   185  		if jmp, _ := op.(*Jump); jmp != nil {
   186  			checkAndSet(jmp.Target, d)
   187  		}
   188  		if err != nil {
   189  			return err
   190  		}
   191  	}
   192  
   193  	if depth[len(ctx.ops)] != endDepth {
   194  		return fmt.Errorf("internal debugger error: depth check failed: depth at the end is not %d (got %d)\n%s", depth[len(ctx.ops)], endDepth, Listing(depth, ctx.ops))
   195  	}
   196  	return nil
   197  }
   198  
   199  func (ctx *compileCtx) compileAST(t ast.Expr) error {
   200  	switch node := t.(type) {
   201  	case *ast.CallExpr:
   202  		return ctx.compileTypeCastOrFuncCall(node)
   203  
   204  	case *ast.Ident:
   205  		return ctx.compileIdent(node)
   206  
   207  	case *ast.ParenExpr:
   208  		// otherwise just eval recursively
   209  		return ctx.compileAST(node.X)
   210  
   211  	case *ast.SelectorExpr: // <expression>.<identifier>
   212  		switch x := node.X.(type) {
   213  		case *ast.Ident:
   214  			switch {
   215  			case x.Name == "runtime" && node.Sel.Name == "curg":
   216  				ctx.pushOp(&PushCurg{})
   217  
   218  			case x.Name == "runtime" && node.Sel.Name == "frameoff":
   219  				ctx.pushOp(&PushFrameoff{})
   220  
   221  			case x.Name == "runtime" && node.Sel.Name == "threadid":
   222  				ctx.pushOp(&PushThreadID{})
   223  
   224  			case ctx.HasLocal(x.Name):
   225  				ctx.pushOp(&PushLocal{Name: x.Name})
   226  				ctx.pushOp(&Select{node.Sel.Name})
   227  
   228  			case ctx.HasGlobal(x.Name, node.Sel.Name):
   229  				ctx.pushOp(&PushPackageVar{x.Name, node.Sel.Name})
   230  
   231  			default:
   232  				return ctx.compileUnary(node.X, &Select{node.Sel.Name})
   233  			}
   234  
   235  		case *ast.CallExpr:
   236  			ident, ok := x.Fun.(*ast.SelectorExpr)
   237  			if ok {
   238  				f, ok := ident.X.(*ast.Ident)
   239  				if ok && f.Name == "runtime" && ident.Sel.Name == "frame" {
   240  					switch arg := x.Args[0].(type) {
   241  					case *ast.BasicLit:
   242  						fr, err := strconv.ParseInt(arg.Value, 10, 8)
   243  						if err != nil {
   244  							return err
   245  						}
   246  						// Push local onto the stack to be evaluated in the new frame context.
   247  						ctx.pushOp(&PushLocal{Name: node.Sel.Name, Frame: fr})
   248  						return nil
   249  					default:
   250  						return fmt.Errorf("expected integer value for frame, got %v", arg)
   251  					}
   252  				}
   253  			}
   254  			return ctx.compileUnary(node.X, &Select{node.Sel.Name})
   255  
   256  		case *ast.BasicLit: // try to accept "package/path".varname syntax for package variables
   257  			s, err := strconv.Unquote(x.Value)
   258  			if err != nil {
   259  				return err
   260  			}
   261  			if ctx.HasGlobal(s, node.Sel.Name) {
   262  				ctx.pushOp(&PushPackageVar{s, node.Sel.Name})
   263  				return nil
   264  			}
   265  			return ctx.compileUnary(node.X, &Select{node.Sel.Name})
   266  
   267  		default:
   268  			return ctx.compileUnary(node.X, &Select{node.Sel.Name})
   269  
   270  		}
   271  
   272  	case *ast.TypeAssertExpr: // <expression>.(<type>)
   273  		return ctx.compileTypeAssert(node)
   274  
   275  	case *ast.IndexExpr:
   276  		return ctx.compileBinary(node.X, node.Index, nil, &Index{node})
   277  
   278  	case *ast.SliceExpr:
   279  		if node.Slice3 {
   280  			return fmt.Errorf("3-index slice expressions not supported")
   281  		}
   282  		return ctx.compileReslice(node)
   283  
   284  	case *ast.StarExpr:
   285  		// pointer dereferencing *<expression>
   286  		return ctx.compileUnary(node.X, &PointerDeref{node})
   287  
   288  	case *ast.UnaryExpr:
   289  		// The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr)
   290  		switch node.Op {
   291  		case token.AND:
   292  			return ctx.compileUnary(node.X, &AddrOf{node})
   293  		default:
   294  			return ctx.compileUnary(node.X, &Unary{node})
   295  		}
   296  
   297  	case *ast.BinaryExpr:
   298  		switch node.Op {
   299  		case token.INC, token.DEC, token.ARROW:
   300  			return fmt.Errorf("operator %s not supported", node.Op.String())
   301  		}
   302  		// short circuits logical operators
   303  		var sop *Jump
   304  		switch node.Op {
   305  		case token.LAND:
   306  			sop = &Jump{When: JumpIfFalse, Node: node.X}
   307  		case token.LOR:
   308  			sop = &Jump{When: JumpIfTrue, Node: node.X}
   309  		}
   310  		err := ctx.compileBinary(node.X, node.Y, sop, &Binary{node})
   311  		if err != nil {
   312  			return err
   313  		}
   314  		if sop != nil {
   315  			sop.Target = len(ctx.ops)
   316  			ctx.pushOp(&BoolToConst{})
   317  		}
   318  
   319  	case *ast.BasicLit:
   320  		ctx.pushOp(&PushConst{constant.MakeFromLiteral(node.Value, node.Kind, 0)})
   321  
   322  	default:
   323  		return fmt.Errorf("expression %T not implemented", t)
   324  	}
   325  	return nil
   326  }
   327  
   328  func (ctx *compileCtx) compileTypeCastOrFuncCall(node *ast.CallExpr) error {
   329  	if len(node.Args) != 1 {
   330  		// Things that have more or less than one argument are always function calls.
   331  		return ctx.compileFunctionCall(node)
   332  	}
   333  
   334  	ambiguous := func() error {
   335  		// Ambiguous, could be a function call or a type cast, if node.Fun can be
   336  		// evaluated then try to treat it as a function call, otherwise try the
   337  		// type cast.
   338  		ctx2 := &compileCtx{evalLookup: ctx.evalLookup}
   339  		err0 := ctx2.compileAST(node.Fun)
   340  		if err0 == nil {
   341  			return ctx.compileFunctionCall(node)
   342  		}
   343  		return ctx.compileTypeCast(node, err0)
   344  	}
   345  
   346  	fnnode := node.Fun
   347  	for {
   348  		fnnode = removeParen(fnnode)
   349  		n, _ := fnnode.(*ast.StarExpr)
   350  		if n == nil {
   351  			break
   352  		}
   353  		fnnode = n.X
   354  	}
   355  
   356  	switch n := fnnode.(type) {
   357  	case *ast.BasicLit:
   358  		// It can only be a ("type string")(x) type cast
   359  		return ctx.compileTypeCast(node, nil)
   360  	case *ast.ArrayType, *ast.StructType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.ChanType:
   361  		return ctx.compileTypeCast(node, nil)
   362  	case *ast.SelectorExpr:
   363  		if _, isident := n.X.(*ast.Ident); isident {
   364  			if typ, _ := ctx.FindTypeExpr(n); typ != nil {
   365  				return ctx.compileTypeCast(node, nil)
   366  			}
   367  			return ambiguous()
   368  		}
   369  		return ctx.compileFunctionCall(node)
   370  	case *ast.Ident:
   371  		if ctx.HasBuiltin(n.Name) {
   372  			return ctx.compileFunctionCall(node)
   373  		}
   374  		if ctx.HasGlobal("", n.Name) || ctx.HasLocal(n.Name) {
   375  			return ctx.compileFunctionCall(node)
   376  		}
   377  		return ctx.compileTypeCast(node, fmt.Errorf("could not find symbol value for %s", n.Name))
   378  	case *ast.IndexExpr:
   379  		// Ambiguous, could be a parametric type
   380  		switch n.X.(type) {
   381  		case *ast.Ident, *ast.SelectorExpr:
   382  			// Do the type-cast first since evaluating node.Fun could be expensive.
   383  			err := ctx.compileTypeCast(node, nil)
   384  			if err == nil || err != reader.ErrTypeNotFound {
   385  				return err
   386  			}
   387  			return ctx.compileFunctionCall(node)
   388  		default:
   389  			return ctx.compileFunctionCall(node)
   390  		}
   391  	case *ast.IndexListExpr:
   392  		return ctx.compileTypeCast(node, nil)
   393  	default:
   394  		// All other expressions must be function calls
   395  		return ctx.compileFunctionCall(node)
   396  	}
   397  }
   398  
   399  func (ctx *compileCtx) compileTypeCast(node *ast.CallExpr, ambiguousErr error) error {
   400  	err := ctx.compileAST(node.Args[0])
   401  	if err != nil {
   402  		return err
   403  	}
   404  
   405  	fnnode := node.Fun
   406  
   407  	// remove all enclosing parenthesis from the type name
   408  	fnnode = removeParen(fnnode)
   409  
   410  	targetTypeStr := exprToString(removeParen(node.Fun))
   411  	styp, err := ctx.FindTypeExpr(fnnode)
   412  	if err != nil {
   413  		switch targetTypeStr {
   414  		case "[]byte", "[]uint8":
   415  			styp = godwarf.FakeSliceType(godwarf.FakeBasicType("uint", 8))
   416  		case "[]int32", "[]rune":
   417  			styp = godwarf.FakeSliceType(godwarf.FakeBasicType("int", 32))
   418  		default:
   419  			if ambiguousErr != nil && err == reader.ErrTypeNotFound {
   420  				return fmt.Errorf("could not evaluate function or type %s: %v", exprToString(node.Fun), ambiguousErr)
   421  			}
   422  			return err
   423  		}
   424  	}
   425  
   426  	ctx.pushOp(&TypeCast{DwarfType: styp, Node: node})
   427  	return nil
   428  }
   429  
   430  func (ctx *compileCtx) compileBuiltinCall(builtin string, args []ast.Expr) error {
   431  	for _, arg := range args {
   432  		err := ctx.compileAST(arg)
   433  		if err != nil {
   434  			return err
   435  		}
   436  	}
   437  	ctx.pushOp(&BuiltinCall{builtin, args})
   438  	return nil
   439  }
   440  
   441  func (ctx *compileCtx) compileIdent(node *ast.Ident) error {
   442  	switch {
   443  	case ctx.HasLocal(node.Name):
   444  		ctx.pushOp(&PushLocal{Name: node.Name})
   445  	case ctx.HasGlobal("", node.Name):
   446  		ctx.pushOp(&PushPackageVar{"", node.Name})
   447  	case node.Name == "true" || node.Name == "false":
   448  		ctx.pushOp(&PushConst{constant.MakeBool(node.Name == "true")})
   449  	case node.Name == "nil":
   450  		ctx.pushOp(&PushNil{})
   451  	default:
   452  		found := false
   453  		if regnum, ok := ctx.LookupRegisterName(node.Name); ok {
   454  			ctx.pushOp(&PushRegister{regnum, node.Name})
   455  			found = true
   456  		}
   457  		if !found {
   458  			return fmt.Errorf("could not find symbol value for %s", node.Name)
   459  		}
   460  	}
   461  	return nil
   462  }
   463  
   464  func (ctx *compileCtx) compileUnary(expr ast.Expr, op Op) error {
   465  	err := ctx.compileAST(expr)
   466  	if err != nil {
   467  		return err
   468  	}
   469  	ctx.pushOp(op)
   470  	return nil
   471  }
   472  
   473  func (ctx *compileCtx) compileTypeAssert(node *ast.TypeAssertExpr) error {
   474  	err := ctx.compileAST(node.X)
   475  	if err != nil {
   476  		return err
   477  	}
   478  	// Accept .(data) as a type assertion that always succeeds, so that users
   479  	// can access the data field of an interface without actually having to
   480  	// type the concrete type.
   481  	if idtyp, isident := node.Type.(*ast.Ident); !isident || idtyp.Name != "data" {
   482  		typ, err := ctx.FindTypeExpr(node.Type)
   483  		if err != nil {
   484  			return err
   485  		}
   486  		ctx.pushOp(&TypeAssert{typ, node})
   487  		return nil
   488  	}
   489  	ctx.pushOp(&TypeAssert{nil, node})
   490  	return nil
   491  }
   492  
   493  func (ctx *compileCtx) compileBinary(a, b ast.Expr, sop *Jump, op Op) error {
   494  	err := ctx.compileAST(a)
   495  	if err != nil {
   496  		return err
   497  	}
   498  	if sop != nil {
   499  		ctx.pushOp(sop)
   500  	}
   501  	err = ctx.compileAST(b)
   502  	if err != nil {
   503  		return err
   504  	}
   505  	ctx.pushOp(op)
   506  	return nil
   507  }
   508  
   509  func (ctx *compileCtx) compileReslice(node *ast.SliceExpr) error {
   510  	err := ctx.compileAST(node.X)
   511  	if err != nil {
   512  		return err
   513  	}
   514  
   515  	trustLen := true
   516  	hasHigh := false
   517  	if node.High != nil {
   518  		hasHigh = true
   519  		err = ctx.compileAST(node.High)
   520  		if err != nil {
   521  			return err
   522  		}
   523  		_, isbasiclit := node.High.(*ast.BasicLit)
   524  		trustLen = trustLen && isbasiclit
   525  	} else {
   526  		trustLen = false
   527  	}
   528  
   529  	if node.Low != nil {
   530  		err = ctx.compileAST(node.Low)
   531  		if err != nil {
   532  			return err
   533  		}
   534  		_, isbasiclit := node.Low.(*ast.BasicLit)
   535  		trustLen = trustLen && isbasiclit
   536  	} else {
   537  		ctx.pushOp(&PushConst{constant.MakeInt64(0)})
   538  	}
   539  
   540  	ctx.pushOp(&Reslice{Node: node, HasHigh: hasHigh, TrustLen: trustLen})
   541  	return nil
   542  }
   543  
   544  func (ctx *compileCtx) compileFunctionCall(node *ast.CallExpr) error {
   545  	if fnnode, ok := node.Fun.(*ast.Ident); ok {
   546  		if ctx.HasBuiltin(fnnode.Name) {
   547  			return ctx.compileBuiltinCall(fnnode.Name, node.Args)
   548  		}
   549  	}
   550  	if !ctx.allowCalls {
   551  		return ErrFuncCallNotAllowed
   552  	}
   553  
   554  	id := ctx.curCall
   555  	ctx.curCall++
   556  
   557  	oldAllowCalls := ctx.allowCalls
   558  	oldOps := ctx.ops
   559  	ctx.allowCalls = false
   560  	err := ctx.compileAST(node.Fun)
   561  	ctx.allowCalls = oldAllowCalls
   562  	hasFunc := false
   563  	if err != nil {
   564  		ctx.ops = oldOps
   565  		if err != ErrFuncCallNotAllowed {
   566  			return err
   567  		}
   568  	} else {
   569  		hasFunc = true
   570  	}
   571  	ctx.pushOp(&CallInjectionStart{HasFunc: hasFunc, id: id, Node: node})
   572  
   573  	// CallInjectionStart pushes true on the stack if it needs the function argument re-evaluated
   574  	var jmpif *Jump
   575  	if hasFunc {
   576  		jmpif = &Jump{When: JumpIfFalse, Pop: true}
   577  		ctx.pushOp(jmpif)
   578  	}
   579  	ctx.pushOp(&Pop{})
   580  	err = ctx.compileAST(node.Fun)
   581  	if err != nil {
   582  		return err
   583  	}
   584  	if jmpif != nil {
   585  		jmpif.Target = len(ctx.ops)
   586  	}
   587  
   588  	ctx.pushOp(&CallInjectionSetTarget{id: id})
   589  
   590  	for i, arg := range node.Args {
   591  		err := ctx.compileAST(arg)
   592  		if err != nil {
   593  			return fmt.Errorf("error evaluating %q as argument %d in function %s: %v", exprToString(arg), i+1, exprToString(node.Fun), err)
   594  		}
   595  		if isStringLiteral(arg) {
   596  			ctx.compileAllocLiteralString()
   597  		}
   598  		ctx.pushOp(&CallInjectionCopyArg{id: id, ArgNum: i, ArgExpr: arg})
   599  	}
   600  
   601  	ctx.pushOp(&CallInjectionComplete{id: id})
   602  
   603  	return nil
   604  }
   605  
   606  func Listing(depth []int, ops []Op) string {
   607  	if depth == nil {
   608  		depth = make([]int, len(ops)+1)
   609  	}
   610  	buf := new(strings.Builder)
   611  	for i, op := range ops {
   612  		fmt.Fprintf(buf, " %3d  (%2d->%2d) %#v\n", i, depth[i], depth[i+1], op)
   613  	}
   614  	return buf.String()
   615  }
   616  
   617  func isStringLiteral(expr ast.Expr) bool {
   618  	switch expr := expr.(type) {
   619  	case *ast.BasicLit:
   620  		return expr.Kind == token.STRING
   621  	case *ast.BinaryExpr:
   622  		if expr.Op == token.ADD {
   623  			return isStringLiteral(expr.X) && isStringLiteral(expr.Y)
   624  		}
   625  	case *ast.ParenExpr:
   626  		return isStringLiteral(expr.X)
   627  	}
   628  	return false
   629  }
   630  
   631  func removeParen(n ast.Expr) ast.Expr {
   632  	for {
   633  		p, ok := n.(*ast.ParenExpr)
   634  		if !ok {
   635  			break
   636  		}
   637  		n = p.X
   638  	}
   639  	return n
   640  }
   641  
   642  func exprToString(t ast.Expr) string {
   643  	var buf bytes.Buffer
   644  	printer.Fprint(&buf, token.NewFileSet(), t)
   645  	return buf.String()
   646  }