[tools] go/analysis: avoid panics on multi-valued call arguments

0 views
Skip to first unread message

shuang cui (Gerrit)

unread,
Aug 11, 2026, 11:54:50 PM (4 hours ago) Aug 11
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

shuang cui has uploaded the change for review

Commit message

go/analysis: avoid panics on multi-valued call arguments

Several analyzers access call arguments before checking the argument count.

A valid Go call such as:

net.Dial(dialArgs())

contains one AST argument even though dialArgs returns two values. Similar
cases occur with sort.Slice, strings.Index, and strings.HasPrefix.

Check the expected argument count before indexing call.Args. Add regression
tests covering hostport, slicessort, stringscut, and stringscutprefix.

Fixes golang/go#80842
Change-Id: I1c2e66544c733d52fd3e566b18b333472c6e7fa3

Change diff

diff --git a/go/analysis/passes/hostport/hostport.go b/go/analysis/passes/hostport/hostport.go
index d41a0e4..46c9319 100644
--- a/go/analysis/passes/hostport/hostport.go
+++ b/go/analysis/passes/hostport/hostport.go
@@ -170,6 +170,10 @@
} {
for curCall := range index.Calls(callee) {
call := curCall.Node().(*ast.CallExpr)
+ if len(call.Args) < 2 {
+ // A multi-valued call may supply the complete argument list.
+ continue
+ }
switch address := call.Args[1].(type) {
case *ast.CallExpr:
if len(call.Args) == 2 { // avoid spread-call edge case
diff --git a/go/analysis/passes/hostport/testdata/src/a/a.go b/go/analysis/passes/hostport/testdata/src/a/a.go
index 708fef3..9a7f8f3 100644
--- a/go/analysis/passes/hostport/testdata/src/a/a.go
+++ b/go/analysis/passes/hostport/testdata/src/a/a.go
@@ -52,3 +52,12 @@
const port = 0x7B
_, _ = net.Dial("tcp", fmt.Sprintf("%s:%d", "localhost", port)) // want `address format "%s:%d" does not work with IPv6`
}
+
+func dialArgs() (string, string) {
+ return "tcp", "localhost:80"
+}
+
+func multiValueDial() {
+ // A multi-valued call may supply the complete argument list.
+ _, _ = net.Dial(dialArgs())
+}
diff --git a/go/analysis/passes/hostport/testdata/src/a/a.go.golden b/go/analysis/passes/hostport/testdata/src/a/a.go.golden
index 93a89dc..165269e 100644
--- a/go/analysis/passes/hostport/testdata/src/a/a.go.golden
+++ b/go/analysis/passes/hostport/testdata/src/a/a.go.golden
@@ -52,3 +52,12 @@
const port = 0x7B
_, _ = net.Dial("tcp", net.JoinHostPort("localhost", fmt.Sprintf("%d", port))) // want `address format "%s:%d" does not work with IPv6`
}
+
+func dialArgs() (string, string) {
+ return "tcp", "localhost:80"
+}
+
+func multiValueDial() {
+ // A multi-valued call may supply the complete argument list.
+ _, _ = net.Dial(dialArgs())
+}
diff --git a/go/analysis/passes/modernize/sortslice.go b/go/analysis/passes/modernize/sortslice.go
index 08d8667..ecfe99e 100644
--- a/go/analysis/passes/modernize/sortslice.go
+++ b/go/analysis/passes/modernize/sortslice.go
@@ -64,6 +64,10 @@
)
for curCall := range index.Calls(sortSlice) {
call := curCall.Node().(*ast.CallExpr)
+ if len(call.Args) != 2 {
+ // A multi-valued call may supply the complete argument list.
+ continue
+ }
if lit, ok := call.Args[1].(*ast.FuncLit); ok && len(lit.Body.List) == 1 {
sig := info.Types[lit.Type].Type.(*types.Signature)

diff --git a/go/analysis/passes/modernize/stringscut.go b/go/analysis/passes/modernize/stringscut.go
index daa6a67..8013ef3 100644
--- a/go/analysis/passes/modernize/stringscut.go
+++ b/go/analysis/passes/modernize/stringscut.go
@@ -136,6 +136,10 @@
continue // strings.Index not available in this file
}
indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
+ if len(indexCall.Args) != 2 {
+ // A multi-valued call may supply the complete argument list.
+ continue
+ }
obj := typeutil.Callee(info, indexCall)
if obj == nil {
continue
@@ -396,6 +400,14 @@
for _, obj := range []types.Object{stringsSplit, stringsSplitN} {
for curCall := range index.Calls(obj) {
callExpr := curCall.Node().(*ast.CallExpr)
+ expectedArgs := 2
+ if obj.Name() == "SplitN" {
+ expectedArgs = 3
+ }
+ if len(callExpr.Args) != expectedArgs {
+ // A multi-valued call may supply the complete argument list.
+ continue
+ }

// For SplitN, the third argument must be the integer constant 2.
if obj.Name() == "SplitN" && !isIntLiteral(info, callExpr.Args[2], 2) {
diff --git a/go/analysis/passes/modernize/stringscutprefix.go b/go/analysis/passes/modernize/stringscutprefix.go
index 6e28545..0a80b4a 100644
--- a/go/analysis/passes/modernize/stringscutprefix.go
+++ b/go/analysis/passes/modernize/stringscutprefix.go
@@ -74,6 +74,10 @@

// pattern1
if call, ok := ifStmt.Cond.(*ast.CallExpr); ok && ifStmt.Init == nil && len(ifStmt.Body.List) > 0 {
+ if len(call.Args) != 2 {
+ // A multi-valued call may supply the complete argument list.
+ continue
+ }

obj := typeutil.Callee(info, call)
if !typesinternal.IsFunctionNamed(obj, "strings", "HasPrefix", "HasSuffix") &&
@@ -87,6 +91,9 @@
firstStmt := curIfStmt.Child(ifStmt.Body).Child(ifStmt.Body.List[0])
for curCall := range firstStmt.Preorder((*ast.CallExpr)(nil)) {
call1 := curCall.Node().(*ast.CallExpr)
+ if len(call1.Args) != 2 {
+ continue
+ }
obj1 := typeutil.Callee(info, call1)
// bytesTrimPrefix or stringsTrimPrefix might be nil if the file doesn't import it,
// so we need to ensure the obj1 is not nil otherwise the call1 is not TrimPrefix and cause a panic (ditto Suffix).
@@ -180,6 +187,9 @@
isSimpleAssign(ifStmt.Init) {
assign := ifStmt.Init.(*ast.AssignStmt)
if call, ok := assign.Rhs[0].(*ast.CallExpr); ok && assign.Tok == token.DEFINE {
+ if len(call.Args) != 2 {
+ continue
+ }
lhs := assign.Lhs[0]
obj := typeutil.Callee(info, call)

diff --git a/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go b/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go
index 50c9021..3b690bd 100644
--- a/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go
+++ b/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go
@@ -2,6 +2,16 @@

import "sort"

+func sortArgs() ([]int, func(int, int) bool) {
+ s := []int{2, 1}
+ return s, func(i, j int) bool { return s[i] < s[j] }
+}
+
+func multiValueSort() {
+ // A multi-valued call may supply the complete argument list.
+ sort.Slice(sortArgs())
+}
+
type myint int

func _(s []myint) {
diff --git a/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden b/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden
index 58be9ab..e2990c2 100644
--- a/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden
@@ -4,6 +4,16 @@

import "sort"

+func sortArgs() ([]int, func(int, int) bool) {
+ s := []int{2, 1}
+ return s, func(i, j int) bool { return s[i] < s[j] }
+}
+
+func multiValueSort() {
+ // A multi-valued call may supply the complete argument list.
+ sort.Slice(sortArgs())
+}
+
type myint int

func _(s []myint) {
diff --git a/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go b/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go
index 472b2f8..6f1df43 100644
--- a/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go
+++ b/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go
@@ -5,6 +5,15 @@
"strings"
)

+func indexArgs() (string, string) {
+ return "abc", "b"
+}
+
+func multiValueIndex() bool {
+ i := strings.Index(indexArgs())
+ return i >= 0
+}
+
func basic() bool {
s := "value"
i := strings.Index(s, "=") // want "strings.Index can be simplified using strings.Cut"
diff --git a/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden b/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden
index 9dd053e..52776bf 100644
--- a/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden
@@ -5,6 +5,15 @@
"strings"
)

+func indexArgs() (string, string) {
+ return "abc", "b"
+}
+
+func multiValueIndex() bool {
+ i := strings.Index(indexArgs())
+ return i >= 0
+}
+
func basic() bool {
s := "value"
before, _, ok := strings.Cut(s, "=") // want "strings.Index can be simplified using strings.Cut"
diff --git a/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go b/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go
index c9ea96e..4dbf11e 100644
--- a/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go
+++ b/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go
@@ -8,6 +8,16 @@
s, pre, suf string
)

+func prefixArgs() (string, string) {
+ return "abc", "a"
+}
+
+func multiValuePrefix() {
+ if strings.HasPrefix(prefixArgs()) {
+ _ = strings.TrimPrefix("abc", "a")
+ }
+}
+
// test supported cases of pattern 1 - CutPrefix
func _() {
if strings.HasPrefix(s, pre) { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"
diff --git a/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden b/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden
index 9699cbe..84dad2a 100644
--- a/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden
@@ -8,6 +8,16 @@
s, pre, suf string
)

+func prefixArgs() (string, string) {
+ return "abc", "a"
+}
+
+func multiValuePrefix() {
+ if strings.HasPrefix(prefixArgs()) {
+ _ = strings.TrimPrefix("abc", "a")
+ }
+}
+
// test supported cases of pattern 1 - CutPrefix
func _() {
if after, ok := strings.CutPrefix(s, pre); ok { // want "HasPrefix \\+ TrimPrefix can be simplified to CutPrefix"

Change information

Files:
  • M go/analysis/passes/hostport/hostport.go
  • M go/analysis/passes/hostport/testdata/src/a/a.go
  • M go/analysis/passes/hostport/testdata/src/a/a.go.golden
  • M go/analysis/passes/modernize/sortslice.go
  • M go/analysis/passes/modernize/stringscut.go
  • M go/analysis/passes/modernize/stringscutprefix.go
  • M go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go
  • M go/analysis/passes/modernize/testdata/src/slicessort/slicessort.go.golden
  • M go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go
  • M go/analysis/passes/modernize/testdata/src/stringscut/stringscut.go.golden
  • M go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go
  • M go/analysis/passes/modernize/testdata/src/stringscutprefix/stringscutprefix.go.golden
Change size: M
Delta: 12 files changed, 106 insertions(+), 0 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I1c2e66544c733d52fd3e566b18b333472c6e7fa3
Gerrit-Change-Number: 813721
Gerrit-PatchSet: 1
Gerrit-Owner: shuang cui <imc...@gmail.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

shuang cui (Gerrit)

unread,
Aug 11, 2026, 11:56:56 PM (4 hours ago) Aug 11
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

shuang cui voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I1c2e66544c733d52fd3e566b18b333472c6e7fa3
Gerrit-Change-Number: 813721
Gerrit-PatchSet: 1
Gerrit-Owner: shuang cui <imc...@gmail.com>
Gerrit-Reviewer: shuang cui <imc...@gmail.com>
Gerrit-Comment-Date: Wed, 12 Aug 2026 03:56:47 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy
Reply all
Reply to author
Forward
0 new messages