fix: check the number of argument passed and returned to functions

This commit is contained in:
Marc Vertes
2020-05-19 14:56:04 +02:00
committed by GitHub
parent 6f4643ff19
commit 4f8e1de267
6 changed files with 89 additions and 4 deletions

12
_test/fun21.go Normal file
View File

@@ -0,0 +1,12 @@
package main
func Bar() string {
return
}
func main() {
println(Bar())
}
// Error:
// 4:2: not enough arguments to return

10
_test/fun22.go Normal file
View File

@@ -0,0 +1,10 @@
package main
import "time"
func main() {
time.Date()
}
// Error:
// 6:2: not enough arguments in call to time.Date

View File

@@ -206,6 +206,7 @@ const (
aGreaterEqual
aGetFunc
aGetIndex
aGetMethod
aGetSym
aInc
aLand
@@ -265,6 +266,7 @@ var actions = [...]string{
aGreater: ">",
aGetFunc: "getFunc",
aGetIndex: "getIndex",
aGetMethod: "getMethod",
aGetSym: ".",
aInc: "++",
aLand: "&&",

View File

@@ -812,7 +812,23 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case isBinCall(n):
n.gen = callBin
if typ := n.child[0].typ.rtype; typ.NumOut() > 0 {
typ := n.child[0].typ.rtype
numIn := len(n.child) - 1
tni := typ.NumIn()
if numIn == 1 && n.child[1].action == aCall {
numIn = n.child[1].typ.numOut()
}
if n.child[0].action == aGetMethod {
tni-- // The first argument is the method receiver.
}
if typ.IsVariadic() {
tni-- // The last argument could be empty.
}
if numIn < tni {
err = n.cfgErrorf("not enough arguments in call to %v", n.child[0].name())
break
}
if typ.NumOut() > 0 {
if funcType := n.child[0].typ.val; funcType != nil {
// Use the original unwrapped function type, to allow future field and
// methods resolutions, otherwise impossible on the opaque bin type.
@@ -1196,6 +1212,16 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
}
case returnStmt:
if mustReturnValue(sc.def.child[2]) {
nret := len(n.child)
if nret == 1 && n.child[0].action == aCall {
nret = n.child[0].child[0].typ.numOut()
}
if nret < sc.def.typ.numOut() {
err = n.cfgErrorf("not enough arguments to return")
break
}
}
wireChild(n)
n.tnext = nil
n.val = sc.def
@@ -1232,6 +1258,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
case ok:
n.val = method.Index
n.gen = getIndexBinMethod
n.action = aGetMethod
n.recv = &receiver{node: n.child[0]}
n.typ = &itype{cat: valueT, rtype: method.Type, isBinMethod: true}
case n.typ.rtype.Kind() == reflect.Ptr:
@@ -1255,6 +1282,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.gen = getIndexBinPtrMethod
n.typ = &itype{cat: valueT, rtype: m2.Type}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else {
err = n.cfgErrorf("undefined field or method: %s", n.child[1].ident)
}
@@ -1269,11 +1297,13 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
n.gen = getIndexBinMethod
n.action = aGetMethod
} else if method, ok := reflect.PtrTo(n.typ.val.rtype).MethodByName(n.child[1].ident); ok {
n.val = method.Index
n.gen = getIndexBinMethod
n.typ = &itype{cat: valueT, rtype: method.Type}
n.recv = &receiver{node: n.child[0]}
n.action = aGetMethod
} else if field, ok := n.typ.val.rtype.FieldByName(n.child[1].ident); ok {
n.typ = &itype{cat: valueT, rtype: field.Type}
n.val = field.Index
@@ -1312,6 +1342,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
err = n.cfgErrorf("undefined selector: %s.%s", pkg, name)
}
} else if m, lind := n.typ.lookupMethod(n.child[1].ident); m != nil {
n.action = aGetMethod
if n.child[0].isType(sc) {
// Handle method as a function with receiver in 1st argument
n.val = m
@@ -1328,6 +1359,7 @@ func (interp *Interpreter) cfg(root *node, pkgID string) ([]*node, error) {
n.recv = &receiver{node: n.child[0], index: lind}
}
} else if m, lind, isPtr, ok := n.typ.lookupBinMethod(n.child[1].ident); ok {
n.action = aGetMethod
if isPtr && n.typ.fieldSeq(lind).cat != ptrT {
n.gen = getIndexSeqPtrMethod
} else {
@@ -1985,6 +2017,18 @@ func isBinCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == valueT && n.child[0].typ.rtype.Kind() == reflect.Func
}
func mustReturnValue(n *node) bool {
if len(n.child) < 2 {
return false
}
for _, f := range n.child[1].child {
if len(f.child) > 1 {
return false
}
}
return true
}
func isRegularCall(n *node) bool {
return n.kind == callExpr && n.child[0].typ.cat == funcT
}

View File

@@ -39,6 +39,8 @@ func TestInterpConsistencyBuild(t *testing.T) {
file.Name() == "export1.go" || // non-main package
file.Name() == "export0.go" || // non-main package
file.Name() == "for7.go" || // expect error
file.Name() == "fun21.go" || // expect error
file.Name() == "fun22.go" || // expect error
file.Name() == "if2.go" || // expect error
file.Name() == "import6.go" || // expect error
file.Name() == "io0.go" || // use random number
@@ -166,6 +168,16 @@ func TestInterpErrorConsistency(t *testing.T) {
expectedInterp: "4:14: non-bool used as for condition",
expectedExec: "4:2: non-bool i (type int) used as for condition",
},
{
fileName: "fun21.go",
expectedInterp: "4:2: not enough arguments to return",
expectedExec: "4:2: not enough arguments to return",
},
{
fileName: "fun22.go",
expectedInterp: "6:2: not enough arguments in call to time.Date",
expectedExec: "6:11: not enough arguments in call to time.Date",
},
{
fileName: "op1.go",
expectedInterp: "5:2: illegal operand types for '+=' operator",

View File

@@ -706,10 +706,15 @@ func (t *itype) referTo(name string, seen map[*itype]bool) bool {
}
func (t *itype) numOut() int {
if t.cat == valueT {
switch t.cat {
case funcT:
return len(t.ret)
case valueT:
if t.rtype.Kind() == reflect.Func {
return t.rtype.NumOut()
}
return len(t.ret)
}
return 1
}
func (t *itype) concrete() *itype {