proc: Flag shadowed arguments as shadowed

Fixes #951
This commit is contained in:
aarzilli 2018-04-15 14:11:27 +02:00 committed by Derek Parker
parent 21be59469a
commit 7fd47749ef
6 changed files with 112 additions and 22 deletions

21
_fixtures/issue951.go Normal file

@ -0,0 +1,21 @@
package main
import (
"fmt"
"runtime"
)
func main() {
shadow(42)
}
func shadow(i int) {
for i := 0; i < 5; i++ {
for i := 20; i < 25; i++ {
runtime.Breakpoint()
fmt.Println("another shadow", i)
}
fmt.Println("shadow", i)
}
fmt.Println("arg", i)
}

@ -2,7 +2,6 @@ package proc
import (
"bytes"
"debug/dwarf"
"encoding/binary"
"errors"
"fmt"
@ -564,7 +563,7 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
return nilVariable, nil
}
vars, err := scope.variablesByTag(dwarf.TagVariable, nil)
vars, err := scope.Locals()
if err != nil {
return nil, err
}
@ -573,15 +572,6 @@ func (scope *EvalScope) evalIdent(node *ast.Ident) (*Variable, error) {
return vars[i], nil
}
}
args, err := scope.variablesByTag(dwarf.TagFormalParameter, nil)
if err != nil {
return nil, err
}
for i := range args {
if args[i].Name == node.Name {
return args[i], nil
}
}
// if it's not a local variable then it could be a package variable w/o explicit package name
if scope.Fn != nil {

@ -524,7 +524,7 @@ func FrameToScope(bi *BinaryInfo, thread MemoryReadWriter, g *G, frames ...Stack
// Creates a cacheMem that will preload the entire stack frame the first
// time any local variable is read.
// Remember that the stack grows from downward in memory.
// Remember that the stack grows downward in memory.
minaddr := frames[0].Regs.SP()
var maxaddr uint64
if len(frames) > 1 && frames[0].SystemStack == frames[1].SystemStack {

@ -3665,3 +3665,35 @@ func TestInlineStepOut(t *testing.T) {
{contStepout, 18},
})
}
func TestIssue951(t *testing.T) {
if ver, _ := goversion.Parse(runtime.Version()); ver.Major >= 0 && !ver.AfterOrEqual(goversion.GoVersion{1, 9, -1, 0, 0, ""}) {
t.Skip("scopes not implemented in <=go1.8")
}
withTestProcess("issue951", t, func(p proc.Process, fixture protest.Fixture) {
assertNoError(proc.Continue(p), t, "Continue()")
scope, err := proc.GoroutineScope(p.CurrentThread())
assertNoError(err, t, "GoroutineScope")
args, err := scope.FunctionArguments(normalLoadConfig)
assertNoError(err, t, "FunctionArguments")
t.Logf("%#v", args[0])
if args[0].Flags&proc.VariableShadowed == 0 {
t.Error("argument is not shadowed")
}
vars, err := scope.LocalVariables(normalLoadConfig)
assertNoError(err, t, "LocalVariables")
shadowed, notShadowed := 0, 0
for i := range vars {
t.Logf("var %d: %#v\n", i, vars[i])
if vars[i].Flags&proc.VariableShadowed != 0 {
shadowed++
} else {
notShadowed++
}
}
if shadowed != 1 || notShadowed != 1 {
t.Errorf("Wrong number of shadowed/non-shadowed local variables: %d %d", shadowed, notShadowed)
}
})
}

@ -58,6 +58,10 @@ const (
VariableShadowed
// VariableConstant means this variable is a constant value
VariableConstant
// VariableArgument means this variable is a function argument
VariableArgument
// VariableReturnArgument means this variable is a function return value
VariableReturnArgument
)
// Variable represents a variable. It contains the address, name,
@ -616,12 +620,38 @@ func (scope *EvalScope) SetVariable(name, value string) error {
// LocalVariables returns all local variables from the current function scope.
func (scope *EvalScope) LocalVariables(cfg LoadConfig) ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagVariable, &cfg)
vars, err := scope.Locals()
if err != nil {
return nil, err
}
vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & (VariableArgument | VariableReturnArgument)) == 0
})
loadValues(vars, cfg)
return vars, nil
}
// FunctionArguments returns the name, value, and type of all current function arguments.
func (scope *EvalScope) FunctionArguments(cfg LoadConfig) ([]*Variable, error) {
return scope.variablesByTag(dwarf.TagFormalParameter, &cfg)
vars, err := scope.Locals()
if err != nil {
return nil, err
}
vars = filterVariables(vars, func(v *Variable) bool {
return (v.Flags & (VariableArgument | VariableReturnArgument)) != 0
})
loadValues(vars, cfg)
return vars, nil
}
func filterVariables(vars []*Variable, pred func(v *Variable) bool) []*Variable {
r := make([]*Variable, 0, len(vars))
for i := range vars {
if pred(vars[i]) {
r = append(r, vars[i])
}
}
return r
}
// PackageVariables returns the name, value, and type of all package variables in the application.
@ -825,6 +855,12 @@ func (v *Variable) maybeDereference() *Variable {
}
}
func loadValues(vars []*Variable, cfg LoadConfig) {
for i := range vars {
vars[i].loadValueInternal(0, cfg)
}
}
// Extracts the value of the variable at the given address.
func (v *Variable) loadValue(cfg LoadConfig) {
v.loadValueInternal(0, cfg)
@ -1876,20 +1912,17 @@ func (v *variablesByDepth) Swap(i int, j int) {
}
// Fetches all variables of a specific type in the current function scope
func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Variable, error) {
func (scope *EvalScope) Locals() ([]*Variable, error) {
if scope.Fn == nil {
return nil, errors.New("unable to find function context")
}
var vars []*Variable
var depths []int
varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, scope.PC, scope.Line, tag == dwarf.TagVariable)
varReader := reader.Variables(scope.BinInfo.dwarf, scope.Fn.offset, scope.PC, scope.Line, true)
hasScopes := false
for varReader.Next() {
entry := varReader.Entry()
if entry.Tag != tag {
continue
}
val, err := scope.extractVarInfoFromEntry(entry)
if err != nil {
// skip variables that we can't parse yet
@ -1897,6 +1930,17 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Varia
}
vars = append(vars, val)
depth := varReader.Depth()
if entry.Tag == dwarf.TagFormalParameter {
if depth <= 1 {
depth = 0
}
isret, _ := entry.Val(dwarf.AttrVarParam).(bool)
if isret {
val.Flags |= VariableReturnArgument
} else {
val.Flags |= VariableArgument
}
}
depths = append(depths, depth)
if depth > 1 {
hasScopes = true
@ -1933,9 +1977,6 @@ func (scope *EvalScope) variablesByTag(tag dwarf.Tag, cfg *LoadConfig) ([]*Varia
}
lvn[v.Name] = v
}
if cfg != nil {
v.loadValue(*cfg)
}
}
return vars, nil

@ -170,6 +170,12 @@ const (
// VariableConstant means this variable is a constant value
VariableConstant
// VariableArgument means this variable is a function argument
VariableArgument
// VariableReturnArgument means this variable is a function return value
VariableReturnArgument
)
// Variable describes a variable.