github.com/undoio/delve@v1.9.0/pkg/proc/scope_test.go (about) 1 package proc_test 2 3 import ( 4 "fmt" 5 "go/constant" 6 "go/parser" 7 "go/token" 8 "math" 9 "path/filepath" 10 "reflect" 11 "runtime" 12 "strconv" 13 "strings" 14 "testing" 15 16 "github.com/undoio/delve/pkg/goversion" 17 "github.com/undoio/delve/pkg/proc" 18 protest "github.com/undoio/delve/pkg/proc/test" 19 ) 20 21 func TestScopeWithEscapedVariable(t *testing.T) { 22 if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1, Beta: 3}) { 23 return 24 } 25 26 protest.AllowRecording(t) 27 withTestProcess("scopeescapevareval", t, func(p *proc.Target, fixture protest.Fixture) { 28 assertNoError(p.Continue(), t, "Continue") 29 30 // On the breakpoint there are two 'a' variables in scope, the one that 31 // isn't shadowed is a variable that escapes to the heap and figures in 32 // debug_info as '&a'. Evaluating 'a' should yield the escaped variable. 33 34 avar := evalVariable(p, t, "a") 35 if aval, _ := constant.Int64Val(avar.Value); aval != 3 { 36 t.Errorf("wrong value for variable a: %d", aval) 37 } 38 39 if avar.Flags&proc.VariableEscaped == 0 { 40 t.Errorf("variable a isn't escaped to the heap") 41 } 42 }) 43 } 44 45 // TestScope will: 46 // - run _fixtures/scopetest.go 47 // - set a breakpoint on all lines containing a comment 48 // - continue until the program ends 49 // - every time a breakpoint is hit it will check that 50 // scope.FunctionArguments+scope.LocalVariables and scope.EvalExpression 51 // return what the corresponding comment describes they should return and 52 // removes the breakpoint. 53 // 54 // Each comment is a comma separated list of variable declarations, with 55 // each variable declaration having the following format: 56 // 57 // name type = initialvalue 58 // 59 // the = and the initial value are optional and can only be specified if the 60 // type is an integer type, float32, float64 or bool. 61 // 62 // If multiple variables with the same name are specified: 63 // 1. LocalVariables+FunctionArguments should return them in the same order and 64 // every variable except the last one should be marked as shadowed 65 // 2. EvalExpression should return the last one. 66 func TestScope(t *testing.T) { 67 if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{Major: 1, Minor: 9, Rev: -1}) { 68 return 69 } 70 71 fixturesDir := protest.FindFixturesDir() 72 scopetestPath := filepath.Join(fixturesDir, "scopetest.go") 73 74 scopeChecks := getScopeChecks(scopetestPath, t) 75 76 protest.AllowRecording(t) 77 withTestProcess("scopetest", t, func(p *proc.Target, fixture protest.Fixture) { 78 for i := range scopeChecks { 79 setFileBreakpoint(p, t, fixture.Source, scopeChecks[i].line) 80 } 81 82 t.Logf("%d breakpoints set", len(scopeChecks)) 83 84 for { 85 if err := p.Continue(); err != nil { 86 if _, exited := err.(proc.ErrProcessExited); exited { 87 break 88 } 89 assertNoError(err, t, "Continue()") 90 } 91 bp := p.CurrentThread().Breakpoint() 92 93 scopeCheck := findScopeCheck(scopeChecks, bp.Line) 94 if scopeCheck == nil { 95 t.Errorf("unknown stop position %s:%d %#x", bp.File, bp.Line, bp.Addr) 96 } 97 98 scope, _ := scopeCheck.checkLocalsAndArgs(p, t) 99 100 for i := range scopeCheck.varChecks { 101 vc := &scopeCheck.varChecks[i] 102 if vc.shdw { 103 continue 104 } 105 vc.checkInScope(scopeCheck.line, scope, t) 106 } 107 108 scopeCheck.ok = true 109 err := p.ClearBreakpoint(bp.Addr) 110 assertNoError(err, t, "ClearBreakpoint") 111 } 112 }) 113 114 for i := range scopeChecks { 115 if !scopeChecks[i].ok { 116 t.Errorf("breakpoint at line %d not hit", scopeChecks[i].line) 117 } 118 } 119 120 } 121 122 type scopeCheck struct { 123 line int 124 varChecks []varCheck 125 ok bool // this scope check was passed 126 } 127 128 type varCheck struct { 129 name string 130 typ string 131 kind reflect.Kind 132 shdw bool // this variable should be shadowed 133 hasVal bool 134 intVal int64 135 uintVal uint64 136 floatVal float64 137 boolVal bool 138 139 ok bool // this variable check was passed 140 } 141 142 func getScopeChecks(path string, t *testing.T) []scopeCheck { 143 var fset token.FileSet 144 root, err := parser.ParseFile(&fset, path, nil, parser.ParseComments) 145 if err != nil { 146 t.Fatalf("could not parse %s: %v", path, err) 147 } 148 149 scopeChecks := []scopeCheck{} 150 151 for _, cmtg := range root.Comments { 152 for _, cmt := range cmtg.List { 153 pos := fset.Position(cmt.Slash) 154 155 scopeChecks = append(scopeChecks, scopeCheck{line: pos.Line}) 156 scopeChecks[len(scopeChecks)-1].Parse(cmt.Text[2:], t) 157 } 158 } 159 160 return scopeChecks 161 } 162 163 func findScopeCheck(scopeChecks []scopeCheck, line int) *scopeCheck { 164 for i := range scopeChecks { 165 if scopeChecks[i].line == line { 166 return &scopeChecks[i] 167 } 168 } 169 return nil 170 } 171 172 func (check *scopeCheck) Parse(descr string, t *testing.T) { 173 decls := strings.Split(descr, ",") 174 check.varChecks = make([]varCheck, len(decls)) 175 for i, decl := range decls { 176 varcheck := &check.varChecks[i] 177 value := "" 178 if equal := strings.Index(decl, "="); equal >= 0 { 179 value = strings.TrimSpace(decl[equal+1:]) 180 decl = strings.TrimSpace(decl[:equal]) 181 varcheck.hasVal = true 182 } else { 183 decl = strings.TrimSpace(decl) 184 } 185 186 space := strings.Index(decl, " ") 187 if space < 0 { 188 t.Fatalf("could not parse scope comment %q (%q)", descr, decl) 189 } 190 varcheck.name = strings.TrimSpace(decl[:space]) 191 varcheck.typ = strings.TrimSpace(decl[space+1:]) 192 if strings.Index(varcheck.typ, " ") >= 0 { 193 t.Fatalf("could not parse scope comment %q (%q)", descr, decl) 194 } 195 196 if !varcheck.hasVal { 197 continue 198 } 199 200 switch varcheck.typ { 201 case "int", "int8", "int16", "int32", "int64": 202 var err error 203 varcheck.kind = reflect.Int 204 varcheck.intVal, err = strconv.ParseInt(value, 10, 64) 205 if err != nil { 206 t.Fatalf("could not parse scope comment %q: %v", descr, err) 207 } 208 209 case "uint", "uint8", "uint16", "uint32", "uint64", "uintptr": 210 var err error 211 varcheck.kind = reflect.Uint 212 varcheck.uintVal, err = strconv.ParseUint(value, 10, 64) 213 if err != nil { 214 t.Fatalf("could not parse scope comment %q: %v", descr, err) 215 } 216 217 case "float32", "float64": 218 var err error 219 varcheck.kind = reflect.Float64 220 varcheck.floatVal, err = strconv.ParseFloat(value, 64) 221 if err != nil { 222 t.Fatalf("could not parse scope comment %q: %v", descr, err) 223 } 224 225 case "bool": 226 var err error 227 varcheck.kind = reflect.Bool 228 varcheck.boolVal, err = strconv.ParseBool(value) 229 if err != nil { 230 t.Fatalf("could not parse scope comment %q: %v", descr, err) 231 } 232 } 233 } 234 235 for i := 1; i < len(check.varChecks); i++ { 236 if check.varChecks[i-1].name == check.varChecks[i].name { 237 check.varChecks[i-1].shdw = true 238 } 239 } 240 } 241 242 func (scopeCheck *scopeCheck) checkLocalsAndArgs(p *proc.Target, t *testing.T) (*proc.EvalScope, bool) { 243 scope, err := proc.GoroutineScope(p, p.CurrentThread()) 244 assertNoError(err, t, "GoroutineScope()") 245 246 ok := true 247 248 args, err := scope.FunctionArguments(normalLoadConfig) 249 assertNoError(err, t, "FunctionArguments()") 250 locals, err := scope.LocalVariables(normalLoadConfig) 251 assertNoError(err, t, "LocalVariables()") 252 253 for _, arg := range args { 254 scopeCheck.checkVar(arg, t) 255 } 256 257 for _, local := range locals { 258 scopeCheck.checkVar(local, t) 259 } 260 261 for i := range scopeCheck.varChecks { 262 if !scopeCheck.varChecks[i].ok { 263 t.Errorf("%d: variable %s not found", scopeCheck.line, scopeCheck.varChecks[i].name) 264 ok = false 265 } 266 } 267 268 return scope, ok 269 } 270 271 func (check *scopeCheck) checkVar(v *proc.Variable, t *testing.T) { 272 var varCheck *varCheck 273 for i := range check.varChecks { 274 if !check.varChecks[i].ok && (check.varChecks[i].name == v.Name) { 275 varCheck = &check.varChecks[i] 276 break 277 } 278 } 279 280 if varCheck == nil { 281 t.Errorf("%d: unexpected variable %s", check.line, v.Name) 282 return 283 } 284 285 varCheck.check(check.line, v, t, "FunctionArguments+LocalVariables") 286 varCheck.ok = true 287 } 288 289 func (varCheck *varCheck) checkInScope(line int, scope *proc.EvalScope, t *testing.T) { 290 v, err := scope.EvalExpression(varCheck.name, normalLoadConfig) 291 assertNoError(err, t, fmt.Sprintf("EvalVariable(%s)", varCheck.name)) 292 varCheck.check(line, v, t, "EvalExpression") 293 294 } 295 296 func (varCheck *varCheck) check(line int, v *proc.Variable, t *testing.T, ctxt string) { 297 typ := v.DwarfType.String() 298 typ = strings.Replace(typ, " ", "", -1) 299 if typ != varCheck.typ { 300 t.Errorf("%d: wrong type for %s (%s), got %s, expected %s", line, v.Name, ctxt, typ, varCheck.typ) 301 } 302 303 if varCheck.shdw && v.Flags&proc.VariableShadowed == 0 { 304 t.Errorf("%d: expected shadowed %s variable", line, v.Name) 305 } 306 307 if !varCheck.hasVal { 308 return 309 } 310 311 switch varCheck.kind { 312 case reflect.Int: 313 if vv, _ := constant.Int64Val(v.Value); vv != varCheck.intVal { 314 t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.intVal) 315 } 316 case reflect.Uint: 317 if vv, _ := constant.Uint64Val(v.Value); vv != varCheck.uintVal { 318 t.Errorf("%d: wrong value for %s (%s), got %d expected %d", line, v.Name, ctxt, vv, varCheck.uintVal) 319 } 320 case reflect.Float64: 321 if vv, _ := constant.Float64Val(v.Value); math.Abs(vv-varCheck.floatVal) > 0.001 { 322 t.Errorf("%d: wrong value for %s (%s), got %g expected %g", line, v.Name, ctxt, vv, varCheck.floatVal) 323 } 324 case reflect.Bool: 325 if vv := constant.BoolVal(v.Value); vv != varCheck.boolVal { 326 t.Errorf("%d: wrong value for %s (%s), got %v expected %v", line, v.Name, ctxt, vv, varCheck.boolVal) 327 } 328 } 329 }