Repository: benhoyt/goawk
Pull request #179 (2023-05-16 09:05:35)

Fix panic with certain obscure regexes
The parser checks regexes with regex.Compile, but when the compiler gets to it it adds regex flags, and some bad regexes pass the first but not the second.


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:
diff --git a/parser/parser.go b/parser/parser.go
index 4c3b855..72b3e33 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -1013,7 +1013,7 @@ func (p *parser) nextRegex() string {
 		panic(p.errorf("%s", p.val))
 	}
 	regex := p.val
-	_, err := regexp.Compile(regex)
+	_, err := regexp.Compile(compiler.AddRegexFlags(regex))
 	if err != nil {
 		panic(p.errorf("%v", err))
 	}


Test patch:
diff --git a/interp/interp_test.go b/interp/interp_test.go
index c663965..5bd7912 100644
--- a/interp/interp_test.go
+++ b/interp/interp_test.go
@@ -271,7 +271,8 @@ BEGIN {
 	{`BEGIN { print '\"' '\'' 'xy' "z" "'" '\"' }`, "", "\"'xyz'\"\n", "", "invalid char"}, // Check support for single-quoted strings
 	{`BEGIN { print "0\n1\t2\r3\a4\b5\f6\v7\x408\xf" }  # !posix`, "", "0\n1\t2\r3\a4\b5\f6\v7@8\x0f\n", "", ""},
 	{`{ print /foo/ }`, "food\nfoo\nxfooz\nbar\n", "1\n1\n1\n0\n", "", ""},
-	{`/[a-/`, "foo", "", "parse error at 1:1: error parsing regexp: missing closing ]: `[a-`", "terminated"},
+	{`/[a-/`, "foo", "", "parse error at 1:1: error parsing regexp: invalid character class range: `a-)`", "terminated"},
+	{`/\Q/  # !gawk`, "", "", "parse error at 1:1: error parsing regexp: missing closing ): `(?s:\\Q)`", ""}, // Gawk produces a warning (not an error), so skip
 	{`/=foo/`, "=foo", "=foo\n", "", ""},
 	{`BEGIN { RS="x" } /^a.*c$/`, "a\nb\nc", "a\nb\nc\n", "", ""},
 	{`BEGIN { print "-12"+0, "+12"+0, " \t\r\n7foo"+0, ".5"+0, "5."+0, "+."+0 }`, "", "-12 12 7 0.5 5 0\n", "", ""},
@@ -713,6 +714,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"},
diff --git a/testdata/gawk/regexpbrack.ok b/testdata/gawk/regexpbrack.ok
index eb99815..094d621 100644
--- a/testdata/gawk/regexpbrack.ok
+++ b/testdata/gawk/regexpbrack.ok
@@ -1,1 +1,1 @@
-parse error at 1:1: error parsing regexp: missing closing ]: `[]+()0-9.,$%`
\ No newline at end of file
+parse error at 1:1: error parsing regexp: missing closing ]: `[]+()0-9.,$%)`
\ No newline at end of file