Skip to content

Commit 6346593

Browse files
authored
pkg/proc: return better error attempting to call nonexistent function (#4062)
Fixes #4051
1 parent af34831 commit 6346593

File tree

4 files changed

+108
-2
lines changed

4 files changed

+108
-2
lines changed

_fixtures/issue4051.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"runtime"
6+
)
7+
8+
var os = func() func() {
9+
a := 1
10+
return func() {
11+
fmt.Println(a)
12+
a++
13+
}
14+
}()
15+
16+
func Hello(name string) string {
17+
msg := "Hello, " + name
18+
fmt.Println(msg)
19+
return msg
20+
}
21+
22+
func f(i int) func() {
23+
return func() {
24+
fmt.Println("Function f called with:", i)
25+
}
26+
}
27+
28+
func main() {
29+
fmt.Println("Program started")
30+
fmt.Println("Ready for Delve call")
31+
runtime.Breakpoint()
32+
33+
type m struct {
34+
Hello string
35+
}
36+
main := m{
37+
Hello: "World",
38+
}
39+
fmt.Println(main.Hello)
40+
fn := f(42)
41+
runtime.Breakpoint()
42+
fn()
43+
44+
os()
45+
}

pkg/proc/proc_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5479,7 +5479,7 @@ func TestReadClosure(t *testing.T) {
54795479
}
54805480
withTestProcess("closurecontents", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
54815481
avalues := []int64{0, 3, 9, 27}
5482-
for i := 0; i < 4; i++ {
5482+
for i := range 4 {
54835483
assertNoError(grp.Continue(), t, "Continue()")
54845484
accV := evalVariable(p, t, "acc")
54855485
t.Log(api.ConvertVar(accV).MultilineString("", ""))

pkg/proc/variables.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,12 +1136,21 @@ func (v *Variable) structMember(memberName string) (*Variable, error) {
11361136
v = &v.Children[0]
11371137
}
11381138
case reflect.Func:
1139+
fn := v.bi.PCToFunc(v.Base)
11391140
v.loadFunctionPtr(0, LoadConfig{MaxVariableRecurse: -1})
11401141
if v.Unreadable != nil {
1142+
cst := fn.extra(v.bi).closureStructType
1143+
if cst == nil || cst.ByteSize == 0 {
1144+
// Not a closure, normal function
1145+
if _, ok := v.bi.PackageMap[vname]; ok {
1146+
return nil, fmt.Errorf("package %s has no function %s", vname, memberName)
1147+
}
1148+
return nil, fmt.Errorf("%s has no member %s", vname, memberName)
1149+
}
11411150
return nil, v.Unreadable
11421151
}
11431152
if v.closureAddr != 0 {
1144-
fn := v.bi.PCToFunc(v.Base)
1153+
fn = v.bi.PCToFunc(v.Base)
11451154
if fn != nil {
11461155
cst := fn.extra(v.bi).closureStructType
11471156
v = v.newVariable(v.Name, v.closureAddr, cst, v.mem)

pkg/proc/variables_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,6 +1575,58 @@ func testCallFunctionIntl(t *testing.T, grp *proc.TargetGroup, p *proc.Target, t
15751575
}
15761576
}
15771577

1578+
func TestIssue4051(t *testing.T) {
1579+
protest.MustSupportFunctionCalls(t, testBackend)
1580+
protest.AllowRecording(t)
1581+
withTestProcess("issue4051", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {
1582+
err := grp.Continue()
1583+
assertNoError(err, t, "initial continue to breakpoint failed")
1584+
1585+
err = proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), `main.Hello("world")`, pnormalLoadConfig, true)
1586+
if err == nil {
1587+
t.Fatal("expected error, got nil")
1588+
}
1589+
expectedError := "package main has no function Hello"
1590+
if err.Error() != expectedError {
1591+
t.Fatalf("expected error %q, got %q", expectedError, err)
1592+
}
1593+
1594+
err = grp.Continue()
1595+
assertNoError(err, t, "initial continue to breakpoint failed")
1596+
1597+
err = proc.EvalExpressionWithCalls(grp, p.SelectedGoroutine(), `main.Hello("world")`, pnormalLoadConfig, true)
1598+
if err == nil {
1599+
t.Fatal("expected error, got nil")
1600+
}
1601+
expectedError = `expression "main.Hello" is not a function`
1602+
if err.Error() != expectedError {
1603+
t.Fatalf("expected error %q, got %q", expectedError, err)
1604+
}
1605+
1606+
v, err := evalVariableWithCfg(p, "main.Hello", pshortLoadConfig)
1607+
assertNoError(err, t, "eval of main.Hello returned an error")
1608+
assertVariable(t, v, varTest{"main.Hello", true, `"World"`, ``, `string`, nil})
1609+
1610+
v, err = evalVariableWithCfg(p, "os.a", pshortLoadConfig)
1611+
assertNoError(err, t, "eval of os.a returned an error")
1612+
assertVariable(t, v, varTest{"os.a", true, "1", ``, `int`, nil})
1613+
1614+
// TODO(deparker): we *should* get an error here, but the one we expect in this test
1615+
// is not the ideal error. We should really improve type checking in the evaluator.
1616+
v, err = evalVariableWithCfg(p, "main.f.func1.i", pshortLoadConfig)
1617+
expectedError = `main.f has no member func1`
1618+
if err.Error() != expectedError {
1619+
t.Fatalf("expected error %q, got %q", expectedError, err)
1620+
}
1621+
1622+
_, err = evalVariableWithCfg(p, "main.f.func1.somethingthatdoesntexist", pshortLoadConfig)
1623+
expectedError = `main.f has no member func1`
1624+
if err.Error() != expectedError {
1625+
t.Fatalf("expected error %q, got %q", expectedError, err)
1626+
}
1627+
})
1628+
}
1629+
15781630
func TestIssue1531(t *testing.T) {
15791631
// Go 1.12 introduced a change to the map representation where empty cells can be marked with 1 instead of just 0.
15801632
withTestProcess("issue1531", t, func(p *proc.Target, grp *proc.TargetGroup, fixture protest.Fixture) {

0 commit comments

Comments
 (0)