From f74d1ea6d84493ea606a3cf9b46b36bd564fff53 Mon Sep 17 00:00:00 2001 From: Marc Vertes Date: Wed, 4 May 2022 18:51:09 +0200 Subject: [PATCH] interp: detect invalid uses of _ as value We now detect the use of special identifier _ (blank) during parsing in order to abort compiling early. It allows to not panic later during execution. We must catch all the cases where blank is used as a value, but still preserve the cases where it is assigned, used as a struct field or for import side effects. Fixes #1386. --- interp/cfg.go | 60 ++++++++++++++++++++++++++++++++++++++ interp/gta.go | 7 +++++ interp/interp_eval_test.go | 25 ++++++++++++++++ interp/typecheck.go | 15 ++++++++++ 4 files changed, 107 insertions(+) diff --git a/interp/cfg.go b/interp/cfg.go index 960c0a18a..be6edcf8c 100644 --- a/interp/cfg.go +++ b/interp/cfg.go @@ -319,6 +319,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } // Propagate type to children, to handle implicit types for _, c := range child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return false + } switch c.kind { case binaryExpr, unaryExpr, compositeLitExpr: // Do not attempt to propagate composite type to operator expressions, @@ -478,6 +482,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string switch n.kind { case addressExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } wireChild(n) err = check.addressExpr(n) @@ -513,6 +521,11 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string updateSym := false var sym *symbol var level int + + if isBlank(src) { + err = n.cfgErrorf("cannot use _ as value") + break + } if n.kind == defineStmt || (n.kind == assignStmt && dest.ident == "_") { if atyp != nil { dest.typ = atyp @@ -645,6 +658,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } case incDecStmt: + err = check.unaryExpr(n) + if err != nil { + break + } wireChild(n) n.findex = n.child[0].findex n.level = n.child[0].level @@ -763,6 +780,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } case indexExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } wireChild(n) t := n.child[0].typ switch t.cat { @@ -877,6 +898,12 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string gotoLabel(n.sym) case callExpr: + for _, c := range n.child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return + } + } wireChild(n) switch { case isBuiltinCall(n, sc): @@ -1392,9 +1419,17 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string sc = sc.pop() case keyValueExpr: + if isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } wireChild(n) case landExpr: + if isBlank(n.child[0]) || isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } n.start = n.child[0].start n.child[0].tnext = n.child[1].start setFNext(n.child[0], n) @@ -1406,6 +1441,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } case lorExpr: + if isBlank(n.child[0]) || isBlank(n.child[1]) { + err = n.cfgErrorf("cannot use _ as value") + break + } n.start = n.child[0].start n.child[0].tnext = n setFNext(n.child[0], n.child[1].start) @@ -1451,6 +1490,12 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string err = n.cfgErrorf("too many arguments to return") break } + for _, c := range n.child { + if isBlank(c) { + err = n.cfgErrorf("cannot use _ as value") + return + } + } returnSig := sc.def.child[2] if mustReturnValue(returnSig) { nret := len(n.child) @@ -1742,6 +1787,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string } case starExpr: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + break + } switch { case n.anc.kind == defineStmt && len(n.anc.child) == 3 && n.anc.child[1] == n: // pointer type expression in a var definition @@ -1887,6 +1936,10 @@ func (interp *Interpreter) cfg(root *node, sc *scope, importPath, pkgName string wireChild(n) c0, c1 := n.child[0], n.child[1] + if isBlank(c0) || isBlank(c1) { + err = n.cfgErrorf("cannot use _ as value") + break + } if c1.typ == nil { if c1.typ, err = nodeType(interp, sc, c1); err != nil { return @@ -2735,3 +2788,10 @@ func isBoolAction(n *node) bool { } return false } + +func isBlank(n *node) bool { + if n.kind == parenExpr && len(n.child) > 0 { + return isBlank(n.child[0]) + } + return n.ident == "_" +} diff --git a/interp/gta.go b/interp/gta.go index 640426b29..c0f191813 100644 --- a/interp/gta.go +++ b/interp/gta.go @@ -59,6 +59,9 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ for i := 0; i < n.nleft; i++ { dest, src := n.child[i], n.child[sbase+i] + if isBlank(src) { + err = n.cfgErrorf("cannot use _ as value") + } val := src.rval if n.anc.kind == constDecl { if _, err2 := interp.cfg(n, sc, importPath, pkgName); err2 != nil { @@ -274,6 +277,10 @@ func (interp *Interpreter) gta(root *node, rpath, importPath, pkgName string) ([ } case typeSpec, typeSpecAssign: + if isBlank(n.child[0]) { + err = n.cfgErrorf("cannot use _ as value") + return false + } typeName := n.child[0].ident var typ *itype if typ, err = nodeType(interp, sc, n.child[1]); err != nil { diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index 7cde405ad..b4edc4428 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -128,6 +128,10 @@ func TestEvalAssign(t *testing.T) { {src: "i := 1; j := &i; (*j) = 2", res: "2"}, {src: "i64 := testpkg.val; i64 == 11", res: "true"}, {pre: func() { eval(t, i, "k := 1") }, src: `k := "Hello world"`, res: "Hello world"}, // allow reassignment in subsequent evaluations + {src: "_ = _", err: "1:28: cannot use _ as value"}, + {src: "j := true || _", err: "1:33: cannot use _ as value"}, + {src: "j := true && _", err: "1:33: cannot use _ as value"}, + {src: "j := interface{}(int(1)); j.(_)", err: "1:54: cannot use _ as value"}, }) } @@ -178,6 +182,7 @@ func TestEvalBuiltin(t *testing.T) { {src: `t := map[int]int{}; t[123]--; t`, res: "map[123:-1]"}, {src: `t := map[int]int{}; t[123] += 1; t`, res: "map[123:1]"}, {src: `t := map[int]int{}; t[123] -= 1; t`, res: "map[123:-1]"}, + {src: `println("hello", _)`, err: "1:28: cannot use _ as value"}, }) } @@ -202,6 +207,14 @@ func TestEvalDeclWithExpr(t *testing.T) { }) } +func TestEvalTypeSpec(t *testing.T) { + i := interp.New(interp.Options{}) + runTests(t, i, []testCase{ + {src: `type _ struct{}`, err: "1:19: cannot use _ as value"}, + {src: `a := struct{a, _ int}{32, 0}`, res: "{32 0}"}, + }) +} + func TestEvalFunc(t *testing.T) { i := interp.New(interp.Options{}) runTests(t, i, []testCase{ @@ -210,6 +223,8 @@ func TestEvalFunc(t *testing.T) { {src: `(func () int {f := func() (a, b int) {a, b = 3, 4; return}; x, y := f(); return x+y})()`, res: "7"}, {src: `(func () int {f := func() (a int, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"}, {src: `(func () int {f := func() (a, b, c int) {a, b, c = 3, 4, 5; return}; x, y, z := f(); return x+y+z})()`, res: "12"}, + {src: `func f() int { return _ }`, err: "1:29: cannot use _ as value"}, + {src: `(func (x int) {})(_)`, err: "1:28: cannot use _ as value"}, }) } @@ -432,6 +447,8 @@ func TestEvalComparison(t *testing.T) { `, err: "7:13: invalid operation: mismatched types main.Foo and main.Bar", }, + {src: `1 > _`, err: "1:28: cannot use _ as value"}, + {src: `(_) > 1`, err: "1:28: cannot use _ as value"}, }) } @@ -477,6 +494,8 @@ func TestEvalCompositeStruct(t *testing.T) { {src: `a := struct{A,B,C int}{A:1,A:2,C:3}`, err: "1:55: duplicate field name A in struct literal"}, {src: `a := struct{A,B,C int}{A:1,B:2.2,C:3}`, err: "1:57: 11/5 truncated to int"}, {src: `a := struct{A,B,C int}{A:1,2,C:3}`, err: "1:55: mixture of field:value and value elements in struct literal"}, + {src: `a := struct{A,B,C int}{1,2,_}`, err: "1:33: cannot use _ as value"}, + {src: `a := struct{A,B,C int}{B: _}`, err: "1:51: cannot use _ as value"}, }) } @@ -501,6 +520,8 @@ func TestEvalSliceExpression(t *testing.T) { {src: `a := []int{0,1,2,3}[1:3:]`, err: "1:51: 3rd index required in 3-index slice"}, {src: `a := []int{0,1,2}[3:1]`, err: "invalid index values, must be low <= high <= max"}, {pre: func() { eval(t, i, `type Str = string; var r Str = "truc"`) }, src: `r[1]`, res: "114"}, + {src: `_[12]`, err: "1:28: cannot use _ as value"}, + {src: `b := []int{0,1,2}[_:4]`, err: "1:33: cannot use _ as value"}, }) } @@ -511,6 +532,7 @@ func TestEvalConversion(t *testing.T) { {src: `i := 1.1; a := uint64(i)`, res: "1"}, {src: `b := string(49)`, res: "1"}, {src: `c := uint64(1.1)`, err: "1:40: cannot convert expression of type untyped float to type uint64"}, + {src: `int(_)`, err: "1:28: cannot use _ as value"}, }) } @@ -520,6 +542,9 @@ func TestEvalUnary(t *testing.T) { {src: "a := -1", res: "-1"}, {src: "b := +1", res: "1", skip: "BUG"}, {src: "c := !false", res: "true"}, + {src: "_ = 2; _++", err: "1:35: cannot use _ as value"}, + {src: "_ = false; !_ == true", err: "1:39: cannot use _ as value"}, + {src: "!((((_))))", err: "1:28: cannot use _ as value"}, }) } diff --git a/interp/typecheck.go b/interp/typecheck.go index 831d5e5f8..4a238981d 100644 --- a/interp/typecheck.go +++ b/interp/typecheck.go @@ -125,6 +125,8 @@ func (check typecheck) starExpr(n *node) error { } var unaryOpPredicates = opPredicates{ + aInc: isNumber, + aDec: isNumber, aPos: isNumber, aNeg: isNumber, aBitNot: isInt, @@ -134,6 +136,9 @@ var unaryOpPredicates = opPredicates{ // unaryExpr type checks a unary expression. func (check typecheck) unaryExpr(n *node) error { c0 := n.child[0] + if isBlank(c0) { + return n.cfgErrorf("cannot use _ as value") + } t0 := c0.typ.TypeOf() if n.action == aRecv { @@ -222,6 +227,10 @@ var binaryOpPredicates = opPredicates{ func (check typecheck) binaryExpr(n *node) error { c0, c1 := n.child[0], n.child[1] + if isBlank(c0) || isBlank(c1) { + return n.cfgErrorf("cannot use _ as value") + } + a := n.action if isAssignAction(a) { a-- @@ -477,6 +486,12 @@ func (check typecheck) structBinLitExpr(child []*node, typ reflect.Type) error { // sliceExpr type checks a slice expression. func (check typecheck) sliceExpr(n *node) error { + for _, c := range n.child { + if isBlank(c) { + return n.cfgErrorf("cannot use _ as value") + } + } + c, child := n.child[0], n.child[1:] t := c.typ.TypeOf()