Repository: benhoyt/goawk
Pull request #178 (2023-05-15 08:16:30)

Do two resolver passes to ensure we set unknown param types
As in f1's A parameter in this example, found by fuzzing:

```
function f1(A) {}  function f2(x, A) { x[0]; f1(a); f2(a) }
```


Patch:
diff --git a/goawk.go b/goawk.go
index 14d5df2..baacb27 100644
--- a/goawk.go
+++ b/goawk.go
@@ -47,7 +47,7 @@ import (
 )
 
 const (
-	version    = "v1.23.0"
+	version    = "v1.23.1"
 	copyright  = "GoAWK " + version + " - Copyright (c) 2022 Ben Hoyt"
 	shortUsage = "usage: goawk [-F fs] [-v var=value] [-f progfile | 'prog'] [file ...]"
 	longUsage  = `Standard AWK arguments:
diff --git a/internal/resolver/resolve.go b/internal/resolver/resolve.go
index da8a32e..5970b06 100644
--- a/internal/resolver/resolve.go
+++ b/internal/resolver/resolve.go
@@ -173,32 +173,13 @@ func Resolve(prog *ast.Program, config *Config) *ResolvedProgram {
 	// Main resolver pass: determine types of variables and find function
 	// information. Can't call ast.Walk on prog directly, as it will not
 	// iterate through functions in topological (call graph) order.
-	main := mainVisitor{r: &r, nativeFuncs: config.Funcs}
-	for _, funcName := range orderedFuncs {
-		if funcName == "" {
-			continue // BEGIN, END, and actions are processed below
-		}
-		function, exists := callGraph.funcs[funcName]
-		if !exists {
-			// Happens in the case where someone tries to call a local
-			// variable as a function: function f(x) { x() }. That is checked
-			// and flagged as an error in the visitor.
-			continue
-		}
-		r.defineFunc(callGraph.funcIndexes[funcName], funcName, function.Params)
-		main.curFunc = funcName
-		ast.WalkStmtList(&main, function.Body)
-		main.curFunc = ""
-	}
-	for _, stmts := range prog.Begin {
-		ast.WalkStmtList(&main, stmts)
-	}
-	for _, action := range prog.Actions {
-		ast.Walk(&main, action)
-	}
-	for _, stmts := range prog.End {
-		ast.WalkStmtList(&main, stmts)
-	}
+	main := mainVisitor{r: &r, nativeFuncs: config.Funcs, funcIndexes: callGraph.funcIndexes}
+	main.walkOrdered(prog, orderedFuncs)
+
+	// Do another pass to set parameter types in functions which don't use
+	// their parameters, such as f1's A parameter in this example:
+	//  function f1(A) {}  function f2(x, A) { x[0]; f1(a); f2(a) }
+	main.walkOrdered(prog, orderedFuncs)
 
 	// For any variables that are still unknown, set their type to scalar.
 	// This can happen for unused variables, such as in the following:
@@ -326,6 +307,10 @@ func (r *resolver) recordVar(funcName, varName string, typ Type, pos lexer.Posit
 
 // Define a function and its local variables.
 func (r *resolver) defineFunc(index int, name string, params []string) {
+	if info, ok := r.funcInfo[name]; ok && !info.Native {
+		// Function already defined in the previous pass
+		return
+	}
 	r.funcInfo[name] = FuncInfo{Native: false, Index: index, Params: params}
 	// Define the local variable names (we don't know their types yet).
 	r.varInfo[name] = make(map[string]VarInfo)
@@ -372,9 +357,39 @@ func (v *callGraphVisitor) Visit(node ast.Node) ast.Visitor {
 type mainVisitor struct {
 	r           *resolver
 	nativeFuncs map[string]interface{}
+	funcIndexes map[string]int
 	curFunc     string
 }
 
+// Walk prog's AST, with functions walked as ordered by orderedFuncs.
+func (v *mainVisitor) walkOrdered(prog *ast.Program, orderedFuncs []string) {
+	for _, funcName := range orderedFuncs {
+		if funcName == "" {
+			continue // BEGIN, END, and actions are processed below
+		}
+		function, exists := v.r.funcs[funcName]
+		if !exists {
+			// Happens in the case where someone tries to call a local
+			// variable as a function: function f(x) { x() }. That is checked
+			// and flagged as an error in the visitor.
+			continue
+		}
+		v.r.defineFunc(v.funcIndexes[funcName], funcName, function.Params)
+		v.curFunc = funcName
+		ast.WalkStmtList(v, function.Body)
+		v.curFunc = ""
+	}
+	for _, stmts := range prog.Begin {
+		ast.WalkStmtList(v, stmts)
+	}
+	for _, action := range prog.Actions {
+		ast.Walk(v, action)
+	}
+	for _, stmts := range prog.End {
+		ast.WalkStmtList(v, stmts)
+	}
+}
+
 func (v *mainVisitor) Visit(node ast.Node) ast.Visitor {
 	switch n := node.(type) {
 	case *ast.VarExpr:


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index c663965..6e0e17c 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -713,6 +713,7 @@ BEGIN { a[1]=2; f1(a); print f2(a, 1) }
 `, "", "2\n", "", ""},
 	{`BEGIN { arr[0]; f(arr) } function f(a) { print "x" }`, "", "x\n", "", ""},
 	{`function add(a, b) { return a+b }  BEGIN { print add(1, 2), add(1), add() }`, "", "3 1 0\n", "", ""},
+	{`function f1(A) {}  function f2(x, A) { x[0]; f1(a); f2(a) }`, "", "", "", ""}, // found via fuzzing
 
 	// Type checking / resolver tests
 	{`BEGIN { a[x]; a=42 }`, "", "", `parse error at 1:15: can't use array "a" as scalar`, "array"},