Skip to content

Go

This page curates a list of example ast-grep rules to check and to rewrite Go code.

Detect problematic defer statements with function calls

Description

This rule detects a common anti-pattern in Go testing code where defer statements contain function calls with parameters that are evaluated immediately instead of when the defer executes.

In Go, defer schedules a function call to be executed when the surrounding function returns. However, the arguments to the deferred function are evaluated immediately when the defer statement is encountered, not when the defer executes.

This is particularly problematic when using assertion libraries in tests. For example:

go
defer require.NoError(t, failpoint.Disable("some/path"))

In this case, failpoint.Disable("some/path") is called immediately when the defer statement is reached, not when the function exits. This means the failpoint is disabled right after being enabled, making the test ineffective.

Pattern

shell
ast-grep \
  --lang go \
  --pattern '{ defer $A.$B(t, failpoint.$M($$$)) } \
  --selector defer_statement'

Example

go
func TestIssue16696(t *testing.T) {
	alarmRatio := vardef.MemoryUsageAlarmRatio.Load()
	vardef.MemoryUsageAlarmRatio.Store(0.0)
	defer vardef.MemoryUsageAlarmRatio.Store(alarmRatio)
	require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/pkg/executor/sortexec/testSortedRowContainerSpill", "return(true)"))
	defer require.NoError(t,
	   failpoint.Disable(
		"github.com/pingcap/tidb/pkg/executor/sortexec/testSortedRowContainerSpill"
	))
	require.NoError(t, failpoint.Enable("github.com/pingcap/tidb/pkg/executor/join/testRowContainerSpill", "return(true)"))
	defer require.NoError(t,
		failpoint.Disable("github.com/pingcap/tidb/pkg/executor/join/testRowContainerSpill"))
}

Fix

The correct way to defer a function with parameters is to wrap it in an anonymous function:

go
defer func() {
    require.NoError(t, failpoint.Disable("some/path"))
}()

Contributed by

Inspired by YangKeao's tweet about this common pitfall in TiDB codebase.

Find function declarations with names of certain pattern

Description

ast-grep can find function declarations by their names. But not all names can be matched by a meta variable pattern. For instance, you cannot use a meta variable pattern to find function declarations whose names start with a specific prefix, e.g. TestAbs with the prefix Test. Attempting Test$_ will fail because it is not a valid syntax.

Instead, you can use a YAML rule to use the regex atomic rule.

YAML

yaml
id: test-functions
language: go
rule:
  kind: function_declaration
  has:
    field: name
    regex: Test.*

Example

go
package abs
import "testing"
func TestAbs(t *testing.T) {
    got := Abs(-1)
    if got != 1 {
        t.Errorf("Abs(-1) = %d; want 1", got)
    }
}

Contributed by

kevinkjt2000 on Discord.

Match Function Call in Golang

Description

One of the common questions of ast-grep is to match function calls in Golang.

A plain pattern like fmt.Println($A) will not work. This is because Golang syntax also allows type conversions, e.g. int(3.14), that look like function calls. Tree-sitter, ast-grep's parser, will prefer parsing func_call(arg) as a type conversion instead of a call expression.

To avoid this ambiguity, ast-grep lets us write a contextual pattern, which is a pattern inside a larger code snippet. We can use context to write a pattern like this: func t() { fmt.Println($A) }. Then, we can use the selector call_expression to match only function calls.

Please also read the deep dive on ambiguous pattern.

YAML

yaml
id: match-function-call
language: go
rule:
  pattern:
    context: 'func t() { fmt.Println($A) }'
    selector: call_expression

Example

go
func main() {
    fmt.Println("OK")
}

Contributed by

Inspired by QuantumGhost from ast-grep/ast-grep#646

Match package import in Golang

Description

A generic rule template for detecting imports of specific packages in Go source code. This rule can be customized to match any package by modifying the regex pattern, making it useful for security auditing, dependency management, and compliance checking.

This rule identifies Go import statements based on the configured regex pattern, including:

Direct imports: import "package/name"
Versioned imports: import "package/name/v4"
Subpackage imports: import "package/name/subpkg"
Grouped imports within import () blocks

YAML

yaml
id: match-package-import
language: go
rule:
  kind: import_spec
  has:
    regex: PACKAGE_PATTERN_HERE

Example

JWT Library Detection

go
package main

import (
	"fmt"
	"github.com/golang-jwt/jwt" // This matches the AST rule
)

func main() {
	token := jwt.New(jwt.SigningMethodHS256) // Create a new token
	// Add some claims
	token.Claims = jwt.MapClaims{"user": "alice", "role": "admin"}
	tokenString, err := token.SignedString([]byte("my-secret")) // Sign the token
	if err != nil {
		fmt.Printf("Error signing token: %v\n", err)
		return
	}
	fmt.Printf("Generated token: %s\n", tokenString)
}

Contributed by

Sudesh Gutta

Detect problematic JSON tags with dash prefix

Description

This rule detects a security vulnerability in Go's JSON unmarshaling. When a struct field has a JSON tag that starts with -,, it can be unexpectedly unmarshaled with the - key.

According to the Go documentation, if the field tag is -, the field should be omitted. However, a field with name - can still be unmarshaled using the tag -,.

This creates a security issue where developers think they are preventing a field from being unmarshaled (like IsAdmin in authentication), but attackers can still set that field by providing the - key in JSON input.

go
type User struct {
    Username string `json:"username,omitempty"`
    Password string `json:"password,omitempty"`
    IsAdmin  bool   `json:"-,omitempty"`  // Intended to prevent marshaling
}

// This still works and sets IsAdmin to true!
json.Unmarshal([]byte(`{"-": true}`), &user)
// Result: main.User{Username:"", Password:"", IsAdmin:true}

YAML

yaml
id: unmarshal-tag-is-dash
severity: error
message: Struct field can be decoded with the `-` key because the JSON tag
  starts with a `-` but is followed by a comma.
rule:
  pattern: '`$TAG`'
  inside:
    kind: field_declaration
constraints:
  TAG:
    regex: json:"-,.*"

Example

go
package main

type TestStruct1 struct {
	A string `json:"id"` // ok
}

type TestStruct2 struct {
	B string `json:"-,omitempty"` // wrong
}

type TestStruct3 struct {
	C string `json:"-,123"` // wrong
}

type TestStruct4 struct {
	D string `json:"-,"` // wrong
}

Fix

To properly omit a field from JSON marshaling/unmarshaling, use just - without a comma:

go
type User struct {
    Username string `json:"username,omitempty"`
    Password string `json:"password,omitempty"`
    IsAdmin  bool   `json:"-"`  // Correctly prevents marshaling/unmarshaling
}

Contributed by

Inspired by Trail of Bits blog post and their public Semgrep rule.

Made with ❤️ with Rust