Skip to main content

Structural pattern search and controlled rewrite

Run explicit-scope GoCPG pattern searches and scans, then gate any source rewrite through preview, approval, backup, and verification.

User Guides

GoCPG structural search matches code by its abstract syntax tree (AST). For example, malloc($SIZE) matches calls to malloc and binds the argument to $SIZE. Use it to find repeated constructs during an audit or while preparing a refactoring.

The workflow has three stages: search or scan with YAML rules, inspect findings, then preview and approve a rewrite. This guide shows the local CLI commands for each stage.

Pattern rules find code shapes and may filter them with CPG facts. Architecture rules first classify files and symbols into stable components, then evaluate typed dependencies and a separate release-gate contract. Use Architecture control in CodeGraph for that workflow.

Prerequisites and scope

The GoCPG CLI must be installed, with gocpg available in PATH. Build instructions in a local source checkout are in gocpg/README.md. Run the examples in PowerShell. gocpg search needs source files; gocpg scan also needs a CPG built from those files. Replace the quoted path placeholders with your paths. Single quotes preserve $SIZE as an AST-pattern variable rather than a PowerShell variable.

Search and scan without changing source

Save this sample as example.c in a separate directory. It contains two calls to malloc and one call to calloc:

#include <stdlib.h>
void example(int n) {
 void *p = malloc(100);
 void *q = calloc(10, sizeof(int));
 void *r = malloc(n * 2);
}

Pass that directory’s path as --input and run the AST-pattern search:

gocpg search --pattern 'malloc($SIZE)' --lang c --input "<source-root>" --json

The JSON should contain match_count: 2 and total_files: 1. The matches array contains malloc(100) on line 3 and malloc(n * 2) on line 5. Their bindings.SIZE values are 100 and n * 2, respectively. The calloc call is excluded.

For a repeatable check, save this rule as malloc.yaml in a separate rule directory:

id: docs-malloc
language: c
severity: warning
message: "Allocation: $SIZE"
rule:
  pattern: "malloc($SIZE)"

Build a CPG of the sample directory. Choose a new database path to preserve existing graphs. Then pass the absolute directory path containing malloc.yaml and save SARIF:

gocpg parse --input "<source-root>" --output "<cpg-path>" --lang c
gocpg scan --db "<cpg-path>" --rules "<absolute-rule-directory>" --format sarif | Set-Content -Encoding utf8 results.sarif

The results.sarif file should contain version 2.1.0 and two entries in runs[0].results with rule docs-malloc and locations on lines 3 and 5. These are structural findings: the rule marks malloc calls for review. Follow the locations to inspect how the source handles each call’s result.

gocpg search reads current source files and limits matches per file with --limit. gocpg scan reads the selected CPG and evaluates YAML rules. These commands preserve source files. Retain findings, diagnostics, the rule digest, and the CPG/source revision relationship.

Preview a rewrite first

Copy the sample example.c into a separate directory and build its CPG. Save malloc.yaml in a new rule directory:

id: docs-malloc-rewrite
language: c
severity: info
message: "Review allocation: $SIZE"
tags: [autofix]
rule:
  pattern: "malloc($SIZE)"
fix: "calloc(1, $SIZE)"

This tutorial transformation uses calloc, which allocates memory and fills it with zeros. For your own rule, choose a transformation that matches the intended behavior of your program.

gocpg scan --db "<cpg-path>" --rules "<absolute-rewrite-rule-directory>" --input "<rewrite-source-root>" --fix --dry-run

The preview shows two replacements: malloc(100) becomes calloc(1, 100), and malloc(n * 2) becomes calloc(1, n * 2). The call to calloc(10, sizeof(int)) stays unchanged. The source file remains unchanged during preview.

Approve, apply, and verify

Review the diff and approve the changes before running the next command. The native CLI applies --fix immediately; you provide the approval. Use the same source files and rule that you reviewed:

gocpg scan --db "<cpg-path>" --rules "<absolute-rewrite-rule-directory>" --input "<rewrite-source-root>" --fix

Expect two replacements in example.c and an example.c.bak backup containing the original source. Compare the file with the preview and check the result with search:

gocpg search --pattern 'malloc($SIZE)' --lang c --input "<rewrite-source-root>" --json
gocpg search --pattern 'calloc(1, $SIZE)' --lang c --input "<rewrite-source-root>" --json

The first search returns match_count: 0. The second returns match_count: 2: lines 3 and 5, with SIZE values 100 and n * 2. Run the project build checks and tests. If GCC is installed, check the sample syntax:

gcc -fsyntax-only "<rewrite-source-root>/example.c"

After changing the source, build an updated CPG at a new path:

gocpg parse --input "<rewrite-source-root>" --output "<updated-cpg-path>" --lang c

To restore the sample, use the saved example.c.bak copy. For a project, retain the original revision and review the diff before accepting changes.

Choose a command

Use gocpg search --json for an ad hoc search and gocpg scan for repeatable checks with YAML rules. Add --fix --dry-run to preview replacements, then apply the reviewed rule with --fix. For Python CLI rule-catalog operations, see the command-line guide.