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