2023-10-17 18:21:59 +00:00
package evalop
import (
"bytes"
"errors"
"fmt"
"go/ast"
"go/constant"
2023-10-23 19:29:04 +00:00
"go/parser"
2023-10-17 18:21:59 +00:00
"go/printer"
2023-10-23 19:29:04 +00:00
"go/scanner"
2023-10-17 18:21:59 +00:00
"go/token"
"strconv"
"strings"
"github.com/go-delve/delve/pkg/dwarf/godwarf"
"github.com/go-delve/delve/pkg/dwarf/reader"
)
var (
2024-10-04 17:44:57 +00:00
ErrFuncCallNotAllowed = errors . New ( "function calls not allowed without using 'call'" )
DebugPinnerFunctionName = "runtime.debugPinnerV1"
2023-10-17 18:21:59 +00:00
)
type compileCtx struct {
evalLookup
ops [ ] Op
allowCalls bool
curCall int
2024-10-04 17:44:57 +00:00
flags Flags
firstCall bool
2023-10-17 18:21:59 +00:00
}
type evalLookup interface {
FindTypeExpr ( ast . Expr ) ( godwarf . Type , error )
HasBuiltin ( string ) bool
}
2024-10-04 17:44:57 +00:00
// Flags describes flags used to control Compile and CompileAST
type Flags uint8
const (
CanSet Flags = 1 << iota // Assignment is allowed
HasDebugPinner // runtime.debugPinner is available
)
2023-10-23 19:29:04 +00:00
// CompileAST compiles the expression t into a list of instructions.
2024-10-04 17:44:57 +00:00
func CompileAST ( lookup evalLookup , t ast . Expr , flags Flags ) ( [ ] Op , error ) {
ctx := & compileCtx { evalLookup : lookup , allowCalls : true , flags : flags , firstCall : true }
2023-10-17 18:21:59 +00:00
err := ctx . compileAST ( t )
if err != nil {
return nil , err
}
2024-10-04 17:44:57 +00:00
ctx . compileDebugUnpin ( )
2023-10-17 18:21:59 +00:00
err = ctx . depthCheck ( 1 )
if err != nil {
return ctx . ops , err
}
return ctx . ops , nil
}
2023-10-23 19:29:04 +00:00
// Compile compiles the expression expr into a list of instructions.
// If canSet is true expressions like "x = y" are also accepted.
2024-10-04 17:44:57 +00:00
func Compile ( lookup evalLookup , expr string , flags Flags ) ( [ ] Op , error ) {
2023-10-23 19:29:04 +00:00
t , err := parser . ParseExpr ( expr )
if err != nil {
2024-10-04 17:44:57 +00:00
if flags & CanSet != 0 {
2023-10-23 19:29:04 +00:00
eqOff , isAs := isAssignment ( err )
if isAs {
2024-10-04 17:44:57 +00:00
return CompileSet ( lookup , expr [ : eqOff ] , expr [ eqOff + 1 : ] , flags )
2023-10-23 19:29:04 +00:00
}
}
return nil , err
}
2024-10-04 17:44:57 +00:00
return CompileAST ( lookup , t , flags )
2023-10-23 19:29:04 +00:00
}
func isAssignment ( err error ) ( int , bool ) {
el , isScannerErr := err . ( scanner . ErrorList )
if isScannerErr && el [ 0 ] . Msg == "expected '==', found '='" {
return el [ 0 ] . Pos . Offset , true
}
return 0 , false
}
// CompileSet compiles the expression setting lhexpr to rhexpr into a list of
2023-10-17 18:21:59 +00:00
// instructions.
2024-10-04 17:44:57 +00:00
func CompileSet ( lookup evalLookup , lhexpr , rhexpr string , flags Flags ) ( [ ] Op , error ) {
2023-10-23 19:29:04 +00:00
lhe , err := parser . ParseExpr ( lhexpr )
if err != nil {
return nil , err
}
rhe , err := parser . ParseExpr ( rhexpr )
if err != nil {
return nil , err
}
2024-10-04 17:44:57 +00:00
ctx := & compileCtx { evalLookup : lookup , allowCalls : true , flags : flags , firstCall : true }
2023-10-23 19:29:04 +00:00
err = ctx . compileAST ( rhe )
2023-10-17 18:21:59 +00:00
if err != nil {
return nil , err
}
if isStringLiteral ( rhe ) {
ctx . compileAllocLiteralString ( )
}
err = ctx . compileAST ( lhe )
if err != nil {
return nil , err
}
ctx . pushOp ( & SetValue { lhe : lhe , Rhe : rhe } )
err = ctx . depthCheck ( 0 )
if err != nil {
return ctx . ops , err
}
return ctx . ops , nil
}
func ( ctx * compileCtx ) compileAllocLiteralString ( ) {
2024-04-19 15:44:47 +00:00
jmp := & Jump { When : JumpIfAllocStringChecksFail }
ctx . pushOp ( jmp )
ctx . compileSpecialCall ( "runtime.mallocgc" , [ ] ast . Expr {
& ast . BasicLit { Kind : token . INT , Value : "0" } ,
& ast . Ident { Name : "nil" } ,
& ast . Ident { Name : "false" } ,
} , [ ] Op {
& PushLen { } ,
& PushNil { } ,
& PushConst { constant . MakeBool ( false ) } ,
2024-10-04 17:44:57 +00:00
} , true )
2024-04-19 15:44:47 +00:00
ctx . pushOp ( & ConvertAllocToString { } )
jmp . Target = len ( ctx . ops )
}
2024-10-04 17:44:57 +00:00
func ( ctx * compileCtx ) compileSpecialCall ( fnname string , argAst [ ] ast . Expr , args [ ] Op , doPinning bool ) {
if doPinning {
ctx . compileGetDebugPinner ( )
}
2024-04-19 15:44:47 +00:00
id := ctx . curCall
ctx . curCall ++
ctx . pushOp ( & CallInjectionStartSpecial {
id : id ,
FnName : fnname ,
ArgAst : argAst } )
ctx . pushOp ( & CallInjectionSetTarget { id : id } )
for i := range args {
2024-10-04 17:44:57 +00:00
if args [ i ] != nil {
ctx . pushOp ( args [ i ] )
}
2024-04-19 15:44:47 +00:00
ctx . pushOp ( & CallInjectionCopyArg { id : id , ArgNum : i } )
}
2024-10-04 17:44:57 +00:00
doPinning = doPinning && ( ctx . flags & HasDebugPinner != 0 )
ctx . pushOp ( & CallInjectionComplete { id : id , DoPinning : doPinning } )
if doPinning {
ctx . compilePinningLoop ( id )
}
}
func ( ctx * compileCtx ) compileGetDebugPinner ( ) {
if ctx . firstCall && ctx . flags & HasDebugPinner != 0 {
ctx . compileSpecialCall ( DebugPinnerFunctionName , [ ] ast . Expr { } , [ ] Op { } , false )
ctx . pushOp ( & SetDebugPinner { } )
ctx . firstCall = false
}
}
func ( ctx * compileCtx ) compileDebugUnpin ( ) {
if ! ctx . firstCall && ctx . flags & HasDebugPinner != 0 {
ctx . compileSpecialCall ( "runtime.(*Pinner).Unpin" , [ ] ast . Expr {
& ast . Ident { Name : "debugPinner" } ,
} , [ ] Op {
& PushDebugPinner { } ,
} , false )
ctx . pushOp ( & Pop { } )
ctx . pushOp ( & PushNil { } )
ctx . pushOp ( & SetDebugPinner { } )
}
2023-10-17 18:21:59 +00:00
}
func ( ctx * compileCtx ) pushOp ( op Op ) {
ctx . ops = append ( ctx . ops , op )
}
// depthCheck validates the list of instructions produced by Compile and
2023-10-19 18:04:31 +00:00
// CompileSet by performing a stack depth check.
2023-10-17 18:21:59 +00:00
// It calculates the depth of the stack at every instruction in ctx.ops and
// checks that they have enough arguments to execute. For instructions that
// can be reached through multiple paths (because of a jump) it checks that
// all paths reach the instruction with the same stack depth.
// Finally it checks that the stack depth after all instructions have
// executed is equal to endDepth.
func ( ctx * compileCtx ) depthCheck ( endDepth int ) error {
depth := make ( [ ] int , len ( ctx . ops ) + 1 ) // depth[i] is the depth of the stack before i-th instruction
for i := range depth {
depth [ i ] = - 1
}
depth [ 0 ] = 0
var err error
checkAndSet := func ( j , d int ) { // sets depth[j] to d after checking that we can
if depth [ j ] < 0 {
depth [ j ] = d
}
if d != depth [ j ] {
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 ) )
}
}
2024-10-04 17:44:57 +00:00
debugPinnerSeen := false
2023-10-17 18:21:59 +00:00
for i , op := range ctx . ops {
npop , npush := op . depthCheck ( )
if depth [ i ] < npop {
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 ) )
}
d := depth [ i ] - npop + npush
checkAndSet ( i + 1 , d )
2024-10-04 17:44:57 +00:00
switch op := op . ( type ) {
case * Jump :
checkAndSet ( op . Target , d )
case * CallInjectionStartSpecial :
debugPinnerSeen = true
case * CallInjectionComplete :
if op . DoPinning && ! debugPinnerSeen {
2024-11-06 14:15:48 +00:00
err = fmt . Errorf ( "internal debugger error: pinning call injection seen before call to %s at instruction %d" , DebugPinnerFunctionName , i )
2024-10-04 17:44:57 +00:00
}
2023-10-17 18:21:59 +00:00
}
if err != nil {
return err
}
}
if depth [ len ( ctx . ops ) ] != endDepth {
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 ) )
}
return nil
}
func ( ctx * compileCtx ) compileAST ( t ast . Expr ) error {
switch node := t . ( type ) {
case * ast . CallExpr :
return ctx . compileTypeCastOrFuncCall ( node )
case * ast . Ident :
return ctx . compileIdent ( node )
case * ast . ParenExpr :
// otherwise just eval recursively
return ctx . compileAST ( node . X )
case * ast . SelectorExpr : // <expression>.<identifier>
switch x := node . X . ( type ) {
case * ast . Ident :
switch {
case x . Name == "runtime" && node . Sel . Name == "curg" :
ctx . pushOp ( & PushCurg { } )
case x . Name == "runtime" && node . Sel . Name == "frameoff" :
ctx . pushOp ( & PushFrameoff { } )
case x . Name == "runtime" && node . Sel . Name == "threadid" :
ctx . pushOp ( & PushThreadID { } )
2024-07-11 17:26:38 +00:00
case x . Name == "runtime" && node . Sel . Name == "rangeParentOffset" :
ctx . pushOp ( & PushRangeParentOffset { } )
2023-10-17 18:21:59 +00:00
default :
2024-06-14 21:36:11 +00:00
ctx . pushOp ( & PushPackageVarOrSelect { Name : x . Name , Sel : node . Sel . Name } )
2023-10-17 18:21:59 +00:00
}
2023-10-24 16:57:39 +00:00
case * ast . CallExpr :
ident , ok := x . Fun . ( * ast . SelectorExpr )
if ok {
f , ok := ident . X . ( * ast . Ident )
if ok && f . Name == "runtime" && ident . Sel . Name == "frame" {
switch arg := x . Args [ 0 ] . ( type ) {
case * ast . BasicLit :
fr , err := strconv . ParseInt ( arg . Value , 10 , 8 )
if err != nil {
return err
}
// Push local onto the stack to be evaluated in the new frame context.
ctx . pushOp ( & PushLocal { Name : node . Sel . Name , Frame : fr } )
return nil
default :
return fmt . Errorf ( "expected integer value for frame, got %v" , arg )
}
}
}
return ctx . compileUnary ( node . X , & Select { node . Sel . Name } )
2023-10-17 18:21:59 +00:00
case * ast . BasicLit : // try to accept "package/path".varname syntax for package variables
s , err := strconv . Unquote ( x . Value )
if err != nil {
return err
}
2024-06-14 21:36:11 +00:00
ctx . pushOp ( & PushPackageVarOrSelect { Name : s , Sel : node . Sel . Name , NameIsString : true } )
2023-10-17 18:21:59 +00:00
default :
return ctx . compileUnary ( node . X , & Select { node . Sel . Name } )
}
case * ast . TypeAssertExpr : // <expression>.(<type>)
return ctx . compileTypeAssert ( node )
case * ast . IndexExpr :
return ctx . compileBinary ( node . X , node . Index , nil , & Index { node } )
case * ast . SliceExpr :
if node . Slice3 {
2024-06-20 19:50:18 +00:00
return errors . New ( "3-index slice expressions not supported" )
2023-10-17 18:21:59 +00:00
}
return ctx . compileReslice ( node )
case * ast . StarExpr :
// pointer dereferencing *<expression>
return ctx . compileUnary ( node . X , & PointerDeref { node } )
case * ast . UnaryExpr :
// The unary operators we support are +, - and & (note that unary * is parsed as ast.StarExpr)
switch node . Op {
case token . AND :
return ctx . compileUnary ( node . X , & AddrOf { node } )
default :
return ctx . compileUnary ( node . X , & Unary { node } )
}
case * ast . BinaryExpr :
switch node . Op {
case token . INC , token . DEC , token . ARROW :
return fmt . Errorf ( "operator %s not supported" , node . Op . String ( ) )
}
// short circuits logical operators
var sop * Jump
switch node . Op {
case token . LAND :
sop = & Jump { When : JumpIfFalse , Node : node . X }
case token . LOR :
sop = & Jump { When : JumpIfTrue , Node : node . X }
}
err := ctx . compileBinary ( node . X , node . Y , sop , & Binary { node } )
if err != nil {
return err
}
if sop != nil {
sop . Target = len ( ctx . ops )
ctx . pushOp ( & BoolToConst { } )
}
case * ast . BasicLit :
ctx . pushOp ( & PushConst { constant . MakeFromLiteral ( node . Value , node . Kind , 0 ) } )
default :
return fmt . Errorf ( "expression %T not implemented" , t )
}
return nil
}
func ( ctx * compileCtx ) compileTypeCastOrFuncCall ( node * ast . CallExpr ) error {
if len ( node . Args ) != 1 {
// Things that have more or less than one argument are always function calls.
return ctx . compileFunctionCall ( node )
}
ambiguous := func ( ) error {
// Ambiguous, could be a function call or a type cast, if node.Fun can be
// evaluated then try to treat it as a function call, otherwise try the
// type cast.
ctx2 := & compileCtx { evalLookup : ctx . evalLookup }
err0 := ctx2 . compileAST ( node . Fun )
if err0 == nil {
return ctx . compileFunctionCall ( node )
}
return ctx . compileTypeCast ( node , err0 )
}
fnnode := node . Fun
for {
fnnode = removeParen ( fnnode )
n , _ := fnnode . ( * ast . StarExpr )
if n == nil {
break
}
fnnode = n . X
}
switch n := fnnode . ( type ) {
case * ast . BasicLit :
// It can only be a ("type string")(x) type cast
return ctx . compileTypeCast ( node , nil )
case * ast . ArrayType , * ast . StructType , * ast . FuncType , * ast . InterfaceType , * ast . MapType , * ast . ChanType :
return ctx . compileTypeCast ( node , nil )
case * ast . SelectorExpr :
if _ , isident := n . X . ( * ast . Ident ) ; isident {
if typ , _ := ctx . FindTypeExpr ( n ) ; typ != nil {
return ctx . compileTypeCast ( node , nil )
}
return ambiguous ( )
}
return ctx . compileFunctionCall ( node )
case * ast . Ident :
2024-06-14 21:36:11 +00:00
if typ , _ := ctx . FindTypeExpr ( n ) ; typ != nil {
return ctx . compileTypeCast ( node , fmt . Errorf ( "could not find symbol value for %s" , n . Name ) )
2023-10-17 18:21:59 +00:00
}
2024-06-14 21:36:11 +00:00
return ctx . compileFunctionCall ( node )
2023-10-17 18:21:59 +00:00
case * ast . IndexExpr :
// Ambiguous, could be a parametric type
switch n . X . ( type ) {
case * ast . Ident , * ast . SelectorExpr :
// Do the type-cast first since evaluating node.Fun could be expensive.
err := ctx . compileTypeCast ( node , nil )
if err == nil || err != reader . ErrTypeNotFound {
return err
}
return ctx . compileFunctionCall ( node )
default :
return ctx . compileFunctionCall ( node )
}
2024-03-04 17:15:00 +00:00
case * ast . IndexListExpr :
2023-10-17 18:21:59 +00:00
return ctx . compileTypeCast ( node , nil )
default :
// All other expressions must be function calls
return ctx . compileFunctionCall ( node )
}
}
func ( ctx * compileCtx ) compileTypeCast ( node * ast . CallExpr , ambiguousErr error ) error {
err := ctx . compileAST ( node . Args [ 0 ] )
if err != nil {
return err
}
fnnode := node . Fun
// remove all enclosing parenthesis from the type name
fnnode = removeParen ( fnnode )
targetTypeStr := exprToString ( removeParen ( node . Fun ) )
styp , err := ctx . FindTypeExpr ( fnnode )
if err != nil {
switch targetTypeStr {
case "[]byte" , "[]uint8" :
styp = godwarf . FakeSliceType ( godwarf . FakeBasicType ( "uint" , 8 ) )
case "[]int32" , "[]rune" :
styp = godwarf . FakeSliceType ( godwarf . FakeBasicType ( "int" , 32 ) )
default :
if ambiguousErr != nil && err == reader . ErrTypeNotFound {
return fmt . Errorf ( "could not evaluate function or type %s: %v" , exprToString ( node . Fun ) , ambiguousErr )
}
return err
}
}
ctx . pushOp ( & TypeCast { DwarfType : styp , Node : node } )
return nil
}
func ( ctx * compileCtx ) compileBuiltinCall ( builtin string , args [ ] ast . Expr ) error {
for _ , arg := range args {
err := ctx . compileAST ( arg )
if err != nil {
return err
}
}
ctx . pushOp ( & BuiltinCall { builtin , args } )
return nil
}
func ( ctx * compileCtx ) compileIdent ( node * ast . Ident ) error {
2024-06-14 21:36:11 +00:00
ctx . pushOp ( & PushIdent { node . Name } )
2023-10-17 18:21:59 +00:00
return nil
}
func ( ctx * compileCtx ) compileUnary ( expr ast . Expr , op Op ) error {
err := ctx . compileAST ( expr )
if err != nil {
return err
}
ctx . pushOp ( op )
return nil
}
func ( ctx * compileCtx ) compileTypeAssert ( node * ast . TypeAssertExpr ) error {
err := ctx . compileAST ( node . X )
if err != nil {
return err
}
// Accept .(data) as a type assertion that always succeeds, so that users
// can access the data field of an interface without actually having to
// type the concrete type.
if idtyp , isident := node . Type . ( * ast . Ident ) ; ! isident || idtyp . Name != "data" {
typ , err := ctx . FindTypeExpr ( node . Type )
if err != nil {
return err
}
ctx . pushOp ( & TypeAssert { typ , node } )
return nil
}
ctx . pushOp ( & TypeAssert { nil , node } )
return nil
}
func ( ctx * compileCtx ) compileBinary ( a , b ast . Expr , sop * Jump , op Op ) error {
err := ctx . compileAST ( a )
if err != nil {
return err
}
if sop != nil {
ctx . pushOp ( sop )
}
err = ctx . compileAST ( b )
if err != nil {
return err
}
ctx . pushOp ( op )
return nil
}
func ( ctx * compileCtx ) compileReslice ( node * ast . SliceExpr ) error {
err := ctx . compileAST ( node . X )
if err != nil {
return err
}
2024-01-24 17:21:59 +00:00
trustLen := true
2023-10-17 18:21:59 +00:00
hasHigh := false
if node . High != nil {
hasHigh = true
err = ctx . compileAST ( node . High )
if err != nil {
return err
}
2024-01-24 17:21:59 +00:00
_ , isbasiclit := node . High . ( * ast . BasicLit )
trustLen = trustLen && isbasiclit
} else {
trustLen = false
2023-10-17 18:21:59 +00:00
}
if node . Low != nil {
err = ctx . compileAST ( node . Low )
if err != nil {
return err
}
2024-01-24 17:21:59 +00:00
_ , isbasiclit := node . Low . ( * ast . BasicLit )
trustLen = trustLen && isbasiclit
2023-10-17 18:21:59 +00:00
} else {
ctx . pushOp ( & PushConst { constant . MakeInt64 ( 0 ) } )
}
2024-01-24 17:21:59 +00:00
ctx . pushOp ( & Reslice { Node : node , HasHigh : hasHigh , TrustLen : trustLen } )
2023-10-17 18:21:59 +00:00
return nil
}
func ( ctx * compileCtx ) compileFunctionCall ( node * ast . CallExpr ) error {
if fnnode , ok := node . Fun . ( * ast . Ident ) ; ok {
if ctx . HasBuiltin ( fnnode . Name ) {
return ctx . compileBuiltinCall ( fnnode . Name , node . Args )
}
}
if ! ctx . allowCalls {
return ErrFuncCallNotAllowed
}
id := ctx . curCall
ctx . curCall ++
2024-10-04 17:44:57 +00:00
if ctx . flags & HasDebugPinner != 0 {
return ctx . compileFunctionCallWithPinning ( node , id )
}
return ctx . compileFunctionCallNoPinning ( node , id )
}
// compileFunctionCallNoPinning compiles a function call when runtime.debugPinner is
// not available in the target.
func ( ctx * compileCtx ) compileFunctionCallNoPinning ( node * ast . CallExpr , id int ) error {
2023-10-17 18:21:59 +00:00
oldAllowCalls := ctx . allowCalls
oldOps := ctx . ops
ctx . allowCalls = false
err := ctx . compileAST ( node . Fun )
ctx . allowCalls = oldAllowCalls
hasFunc := false
if err != nil {
ctx . ops = oldOps
if err != ErrFuncCallNotAllowed {
return err
}
} else {
hasFunc = true
}
ctx . pushOp ( & CallInjectionStart { HasFunc : hasFunc , id : id , Node : node } )
// CallInjectionStart pushes true on the stack if it needs the function argument re-evaluated
var jmpif * Jump
if hasFunc {
jmpif = & Jump { When : JumpIfFalse , Pop : true }
ctx . pushOp ( jmpif )
}
ctx . pushOp ( & Pop { } )
err = ctx . compileAST ( node . Fun )
if err != nil {
return err
}
if jmpif != nil {
jmpif . Target = len ( ctx . ops )
}
ctx . pushOp ( & CallInjectionSetTarget { id : id } )
for i , arg := range node . Args {
err := ctx . compileAST ( arg )
if err != nil {
return fmt . Errorf ( "error evaluating %q as argument %d in function %s: %v" , exprToString ( arg ) , i + 1 , exprToString ( node . Fun ) , err )
}
if isStringLiteral ( arg ) {
ctx . compileAllocLiteralString ( )
}
ctx . pushOp ( & CallInjectionCopyArg { id : id , ArgNum : i , ArgExpr : arg } )
}
ctx . pushOp ( & CallInjectionComplete { id : id } )
return nil
}
2024-10-04 17:44:57 +00:00
// compileFunctionCallWithPinning compiles a function call when runtime.debugPinner
// is available in the target.
func ( ctx * compileCtx ) compileFunctionCallWithPinning ( node * ast . CallExpr , id int ) error {
ctx . compileGetDebugPinner ( )
err := ctx . compileAST ( node . Fun )
if err != nil {
return err
}
for i , arg := range node . Args {
err := ctx . compileAST ( arg )
if isStringLiteral ( arg ) {
ctx . compileAllocLiteralString ( )
}
if err != nil {
return fmt . Errorf ( "error evaluating %q as argument %d in function %s: %v" , exprToString ( arg ) , i + 1 , exprToString ( node . Fun ) , err )
}
}
ctx . pushOp ( & Roll { len ( node . Args ) } )
ctx . pushOp ( & CallInjectionStart { HasFunc : true , id : id , Node : node } )
ctx . pushOp ( & Pop { } )
ctx . pushOp ( & CallInjectionSetTarget { id : id } )
for i := len ( node . Args ) - 1 ; i >= 0 ; i -- {
arg := node . Args [ i ]
ctx . pushOp ( & CallInjectionCopyArg { id : id , ArgNum : i , ArgExpr : arg } )
}
ctx . pushOp ( & CallInjectionComplete { id : id , DoPinning : true } )
ctx . compilePinningLoop ( id )
return nil
}
func ( ctx * compileCtx ) compilePinningLoop ( id int ) {
loopStart := len ( ctx . ops )
jmp := & Jump { When : JumpIfPinningDone }
ctx . pushOp ( jmp )
ctx . pushOp ( & PushPinAddress { } )
ctx . compileSpecialCall ( "runtime.(*Pinner).Pin" , [ ] ast . Expr {
& ast . Ident { Name : "debugPinner" } ,
& ast . Ident { Name : "pinAddress" } ,
} , [ ] Op {
& PushDebugPinner { } ,
nil ,
} , false )
ctx . pushOp ( & Pop { } )
ctx . pushOp ( & Jump { When : JumpAlways , Target : loopStart } )
jmp . Target = len ( ctx . ops )
ctx . pushOp ( & CallInjectionComplete2 { id : id } )
}
2023-10-17 18:21:59 +00:00
func Listing ( depth [ ] int , ops [ ] Op ) string {
if depth == nil {
depth = make ( [ ] int , len ( ops ) + 1 )
}
buf := new ( strings . Builder )
for i , op := range ops {
fmt . Fprintf ( buf , " %3d (%2d->%2d) %#v\n" , i , depth [ i ] , depth [ i + 1 ] , op )
}
return buf . String ( )
}
func isStringLiteral ( expr ast . Expr ) bool {
switch expr := expr . ( type ) {
case * ast . BasicLit :
return expr . Kind == token . STRING
case * ast . BinaryExpr :
if expr . Op == token . ADD {
return isStringLiteral ( expr . X ) && isStringLiteral ( expr . Y )
}
case * ast . ParenExpr :
return isStringLiteral ( expr . X )
}
return false
}
func removeParen ( n ast . Expr ) ast . Expr {
for {
p , ok := n . ( * ast . ParenExpr )
if ! ok {
break
}
n = p . X
}
return n
}
func exprToString ( t ast . Expr ) string {
var buf bytes . Buffer
printer . Fprint ( & buf , token . NewFileSet ( ) , t )
return buf . String ( )
}