Skip to content

Commit

Permalink
interp: detect invalid uses of _ as value
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mvertes committed May 4, 2022
1 parent 606b4c3 commit f74d1ea
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 0 deletions.
60 changes: 60 additions & 0 deletions interp/cfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 == "_"
}
7 changes: 7 additions & 0 deletions interp/gta.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
25 changes: 25 additions & 0 deletions interp/interp_eval_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
})
}

Expand Down Expand Up @@ -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"},
})
}

Expand All @@ -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{
Expand All @@ -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"},
})
}

Expand Down Expand Up @@ -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"},
})
}

Expand Down Expand Up @@ -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"},
})
}

Expand All @@ -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"},
})
}

Expand All @@ -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"},
})
}

Expand All @@ -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"},
})
}

Expand Down
15 changes: 15 additions & 0 deletions interp/typecheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ func (check typecheck) starExpr(n *node) error {
}

var unaryOpPredicates = opPredicates{
aInc: isNumber,
aDec: isNumber,
aPos: isNumber,
aNeg: isNumber,
aBitNot: isInt,
Expand All @@ -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 {
Expand Down Expand Up @@ -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--
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit f74d1ea

Please sign in to comment.