From 143e4a45590f811d8ce6a68ca5e3ae41b1a8dff5 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Mon, 3 Oct 2022 17:50:09 +0200 Subject: [PATCH] interp: fix type assertion for wrapped empty interface Although empty interfaces are usually not wrapped, for compatibility with the runtime, we may have to wrap them sometime into `valueInterface` type. It allows to preserve interpreter type metadata for interface values exchanged with the runtime. It is necessary to resolve methods and receivers in the absence of reflect support. During type assertions on empty interfaces, we now handle a possible valueInterface and dereference the original value to pursue the type assertion. In the same change, we have improved the format of some panic messages at runtime to give location of offending source at interpreter level. This change will allow to fix traefik/traefik#9362. --- _test/cli7.go | 56 ++++++++++++++++++++++++++++++++ _test/type24.go | 6 ++-- interp/interp_consistent_test.go | 1 + interp/interp_file_test.go | 5 ++- interp/run.go | 24 ++++++++------ 5 files changed, 78 insertions(+), 14 deletions(-) create mode 100644 _test/cli7.go diff --git a/_test/cli7.go b/_test/cli7.go new file mode 100644 index 000000000..fbe2f19dc --- /dev/null +++ b/_test/cli7.go @@ -0,0 +1,56 @@ +package main + +import ( + "fmt" + "io" + "log" + "net/http" + "net/http/httptest" +) + +type T struct { + http.ResponseWriter +} + +type mw1 struct { +} + +var obj = map[string]interface{}{} + +func (m *mw1) ServeHTTP(rw http.ResponseWriter, rq *http.Request) { + t := &T{ + ResponseWriter: rw, + } + x := t.Header() + i := obj["m1"].(*mw1) + fmt.Fprint(rw, "Welcome to my website!", x, i) +} + +func main() { + m1 := &mw1{} + + obj["m1"] = m1 + + mux := http.NewServeMux() + mux.HandleFunc("/", m1.ServeHTTP) + + server := httptest.NewServer(mux) + defer server.Close() + + client(server.URL) +} + +func client(uri string) { + resp, err := http.Get(uri) + if err != nil { + log.Fatal(err) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + fmt.Println(string(body)) +} + +// Output: +// Welcome to my website!map[] &{} diff --git a/_test/type24.go b/_test/type24.go index 79449d666..7bcb94ba3 100644 --- a/_test/type24.go +++ b/_test/type24.go @@ -43,6 +43,6 @@ func assertValue() { } // Output: -// interface conversion: interface {} is int, not string -// interface conversion: interface {} is nil, not string -// interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push +// 22:10: interface conversion: interface {} is int, not string +// 32:10: interface conversion: interface {} is nil, not string +// 42:10: interface conversion: *httptest.ResponseRecorder is not http.Pusher: missing method Push diff --git a/interp/interp_consistent_test.go b/interp/interp_consistent_test.go index 0187b215b..d1c430c88 100644 --- a/interp/interp_consistent_test.go +++ b/interp/interp_consistent_test.go @@ -109,6 +109,7 @@ func TestInterpConsistencyBuild(t *testing.T) { file.Name() == "range9.go" || // expect error file.Name() == "unsafe6.go" || // needs go.mod to be 1.17 file.Name() == "unsafe7.go" || // needs go.mod to be 1.17 + file.Name() == "type24.go" || // expect error file.Name() == "type27.go" || // expect error file.Name() == "type28.go" || // expect error file.Name() == "type29.go" || // expect error diff --git a/interp/interp_file_test.go b/interp/interp_file_test.go index f29b117df..d9f1e208f 100644 --- a/interp/interp_file_test.go +++ b/interp/interp_file_test.go @@ -7,6 +7,7 @@ import ( "go/token" "os" "path/filepath" + "regexp" "strings" "testing" @@ -76,7 +77,9 @@ func runCheck(t *testing.T, p string) { t.Fatal(err) } - if res := strings.TrimSpace(stdout.String()); res != wanted { + // Remove path in output, to have results independent of location. + re := regexp.MustCompile(p + ":") + if res := re.ReplaceAllString(strings.TrimSpace(stdout.String()), ""); res != wanted { t.Errorf("\ngot: %q,\nwant: %q", res, wanted) } } diff --git a/interp/run.go b/interp/run.go index 7660d1df0..c592b606d 100644 --- a/interp/run.go +++ b/interp/run.go @@ -401,7 +401,7 @@ func typeAssert(n *node, withResult, withOk bool) { ok = v.IsValid() if !ok { if !withOk { - panic(fmt.Sprintf("interface conversion: interface {} is nil, not %s", rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) } return next } @@ -409,7 +409,7 @@ func typeAssert(n *node, withResult, withOk bool) { if !ok { if !withOk { method := firstMissingMethod(leftType, rtype) - panic(fmt.Sprintf("interface conversion: %s is not %s: missing method %s", leftType.String(), rtype.String(), method)) + panic(n.cfgErrorf("interface conversion: %s is not %s: missing method %s", leftType.String(), rtype.String(), method)) } return next } @@ -430,14 +430,18 @@ func typeAssert(n *node, withResult, withOk bool) { concrete := val.Interface() ctyp := reflect.TypeOf(concrete) + if vv, ok := concrete.(valueInterface); ok { + ctyp = vv.value.Type() + concrete = vv.value.Interface() + } ok = canAssertTypes(ctyp, rtype) if !ok { if !withOk { // TODO(mpl): think about whether this should ever happen. if ctyp == nil { - panic(fmt.Sprintf("interface conversion: interface {} is nil, not %s", rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) } - panic(fmt.Sprintf("interface conversion: interface {} is %s, not %s", ctyp.String(), rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is %s, not %s", ctyp.String(), rtype.String())) } return next } @@ -462,7 +466,7 @@ func typeAssert(n *node, withResult, withOk bool) { } if !ok { if !withOk { - panic(fmt.Sprintf("interface conversion: interface {} is nil, not %s", rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) } return next } @@ -475,7 +479,7 @@ func typeAssert(n *node, withResult, withOk bool) { if !ok { if !withOk { method := firstMissingMethod(v.Type(), rtype) - panic(fmt.Sprintf("interface conversion: %s is not %s: missing method %s", v.Type().String(), rtype.String(), method)) + panic(n.cfgErrorf("interface conversion: %s is not %s: missing method %s", v.Type().String(), rtype.String(), method)) } return next } @@ -495,7 +499,7 @@ func typeAssert(n *node, withResult, withOk bool) { if !ok || !v.value.IsValid() { ok = false if !withOk { - panic(fmt.Sprintf("interface conversion: interface {} is nil, not %s", rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is nil, not %s", rtype.String())) } return next } @@ -503,7 +507,7 @@ func typeAssert(n *node, withResult, withOk bool) { ok = canAssertTypes(v.value.Type(), rtype) if !ok { if !withOk { - panic(fmt.Sprintf("interface conversion: interface {} is %s, not %s", v.value.Type().String(), rtype.String())) + panic(n.cfgErrorf("interface conversion: interface {} is %s, not %s", v.value.Type().String(), rtype.String())) } return next } @@ -567,7 +571,7 @@ func convert(n *node) { n.exec = func(f *frame) bltn { n, ok := value(f).Interface().(*node) if !ok || !n.typ.convertibleTo(c.typ) { - panic("cannot convert") + panic(n.cfgErrorf("cannot convert to %s", c.typ.id())) } n1 := *n n1.typ = c.typ @@ -3611,7 +3615,7 @@ func convertConstantValue(n *node) { case constant.Int: i, x := constant.Int64Val(c) if !x { - panic(fmt.Sprintf("constant %s overflows int64", c.ExactString())) + panic(n.cfgErrorf("constant %s overflows int64", c.ExactString())) } v = reflect.ValueOf(int(i)) case constant.Float: