Skip to content

feat(allowedsymbols): replace AST checker internals with go/analysis.Analyzer#160

Merged
thieman merged 5 commits intomainfrom
thieman/go-analysis-linter
Mar 27, 2026
Merged

feat(allowedsymbols): replace AST checker internals with go/analysis.Analyzer#160
thieman merged 5 commits intomainfrom
thieman/go-analysis-linter

Conversation

@thieman
Copy link
Copy Markdown
Collaborator

@thieman thieman commented Mar 27, 2026

Summary

Replaces the manual go/parser + go/ast walking inside allowedsymbols/symbols_test.go with a proper go/analysis.Analyzer (allowedsymbols/analyzer.go).

What changed:

  • New allowedsymbols/analyzer.go: a go/analysis.Analyzer built on go/analysis/passes/inspect that enforces all existing rules — permanently banned packages, per-package allowlists, per-command sublists, blank/dot import rejection, unused allowlist entry reporting — and emits violations as pass.Reportf() diagnostics with proper file:line:col positions
  • Refactored allowedsymbols/symbols_test.go: shared logic (buildAllowlistSets, checkFileImports, checkFileSelectors, reportUnused) extracted from the old duplicated functions; test harness now calls these shared helpers so the analyzer and the test-time checks stay in sync
  • Added golang.org/x/tools v0.43.0 as a direct dependency (needed for go/analysis and go/analysis/passes/inspect)
  • All data files (symbols_builtins.go, symbols_interp.go, etc.) are unchanged

What this enables:

  • The analyzer can be invoked via go vet for editor/gopls integration
  • Violations get proper source positions, not just package-level error strings
  • Future structural rules (Scanner.Buffer check, no goroutine spawns in builtins) can be added as additional analysis.Analyzer passes

The allowed-symbols.yml label gate is unaffected — it still triggers on changes to allowedsymbols/ and requires verified/allowed_symbols before merge.

Test plan

  • go test ./allowedsymbols/ -v — all 35 tests pass (including verification tests that inject invalid symbols)
  • go build ./allowedsymbols/... — clean
  • Verify allowed-symbols.yml still detects changes to the allowedsymbols/ directory

🤖 Generated with Claude Code

thieman and others added 2 commits March 27, 2026 11:47
…nternals

Replace the duplicated AST-walking logic in symbols_test.go with shared
helper functions in a new analyzer.go file that also exposes a proper
go/analysis.Analyzer (NewAnalyzer) for go vet integration.

- Add allowedsymbols/analyzer.go with NewAnalyzer, buildAllowlistSets,
  checkFileImports, checkFileSelectors, reportUnused, and
  fileLineReporter helpers
- Rewrite checkAllowedSymbols in symbols_test.go to use those helpers
  instead of its own inline AST walking; all existing test signatures
  and behaviors are preserved
- Add golang.org/x/tools v0.43.0 as a direct dependency

All 35 existing tests continue to pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… helper

Three code-quality fixes to analyzer.go and symbols_test.go:

- Replace interface{} with any in the NewAnalyzer run func signature
- Remove unused listName parameter from reportUnused; callers already
  embed the list name in the closure message they pass in
- Move fileLineReporter from analyzer.go (production code) into
  symbols_test.go where it is actually used; keeps production API clean

All 35 tests continue to pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The new allowedsymbols package imports golang.org/x/tools, which was
added to go.mod but missing from the third-party license file.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Collaborator Author

@thieman thieman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: allowedsymbols — go/analysis.Analyzer + checker refactor

This PR adds a go/analysis.Analyzer implementation (NewAnalyzer / AnalyzerConfig) alongside a refactor that extracts the core checking logic from the test-only checkAllowedSymbols into shared helpers (buildAllowlistSets, checkFileImports, checkFileSelectors, reportUnused) usable by both the test harness and the new analyzer. All existing tests pass; the package compiles cleanly with go vet and the race detector. License and dependency bookkeeping are up to date.

Overall the extraction is clean and the refactoring is safe. One functional bug in the new analyzer and one correctness gap deserve attention before this is wired up to go vet.


Findings

P1 — inspect.Analyzer declared in Requires but never used

P1 Badge

analyzer.go line 71 adds inspect.Analyzer to Requires, which tells the go/analysis framework to run the inspect pass first and make its *inspector.Inspector result available via pass.ResultOf[inspect.Analyzer]. However, the Run function never calls pass.ResultOf — it uses ast.Inspect (stdlib) directly, just as the test harness does.

This is a latent bug: the unused Requires entry imposes unnecessary ordering overhead on the analysis framework, and the doc comment on lines 37-38 falsely describes the implementation as using "the inspect pass for efficient AST traversal." If the intent was to use inspector.Inspector's filtered pre-order walk, the pass.ResultOf call is missing; if the intent is to keep ast.Inspect, the inspect.Analyzer dependency and import should be removed.

P2 — NewAnalyzer is exported but has zero call sites and zero tests

P2 Badge

NewAnalyzer and AnalyzerConfig are the primary deliverables of this PR (the package doc says it "integrates with go vet"), but there are no tests that call NewAnalyzer or run it via analysistest.Run. The unique code path at line 59 (pass.Files[0].Package position for unused-symbol reporting) is not exercised by any existing test. There is also no cmd/ entry point or Makefile target that wires the analyzer into go vet. The README does not mention how to invoke it.

Since the public API is untested and not yet integrated, there is a risk the go/analysis path diverges silently from the test harness as the codebase evolves.

P2 — Silent skip on malformed allowlist entry in buildAllowlistSets diverges from test harness

P2 Badge

buildAllowlistSets silently skips entries where dot <= 0 (line 84 of analyzer.go). The test harness calls buildAllowlistSets first and then separately validates entries with t.Fatalf (lines 59-64 of symbols_test.go). Through the analyzer path (NewAnalyzer), a malformed entry is silently dropped with no diagnostic, so a maintainer who makes a typo would get no error and the entry would simply not be enforced.

P3 — Package doc comment overstates current integration state

P3 Badge

The package-level doc comment says the package "integrates with go vet" and reports "file:line:col diagnostics." This is accurate only if NewAnalyzer is wired into a go vet/golangci-lint pipeline, which this PR does not do. The README correctly describes the implementation as test-based AST analysis. Consider aligning the package doc with reality until integration is completed.

P3 — checkFileImports doc comment refers to caller by filename

P3 Badge

Lines 98-102 of analyzer.go name symbols_test.go as an example caller in the doc comment. This tight coupling will rot as the code evolves; prefer describing the contract (pos == token.NoPos means no position prefix) without referencing a specific caller.


Code path coverage table

Code path Covered?
buildAllowlistSets — normal entries yes (test harness + verification tests)
buildAllowlistSets — malformed entry (dot <= 0) test harness: t.Fatalf; analyzer: silently dropped, not tested
checkFileImports — permanently banned (exact) yes (verification test)
checkFileImports — permanently banned (prefix) yes (verification test)
checkFileImports — blank/dot import yes (verification test)
checkFileImports — package not in allowlist yes (verification test)
checkFileImports — exempt import yes (verification test)
checkFileImports — aliased import yes (verification test)
checkFileSelectors — unlisted symbol yes (verification test)
reportUnused — unused symbol detected yes (verification test)
NewAnalyzer — any invocation not tested
NewAnalyzer Run — unused-symbol position not tested
NewAnalyzer Run — malformed allowlist entry not tested, silently dropped

Security assessment

This PR touches only the allowedsymbols analysis tooling — it is not a builtin command, does not run during shell execution, and has no impact on the sandbox at runtime. The refactoring is conservative: behavior of the test harness path is preserved (verified by all existing tests passing under -race). No new os.Open, os/exec, network, or unsafe code is introduced. The golang.org/x/tools dependency is a well-known, widely-audited Go module used exclusively in this analysis-time package.

No sandbox-integrity or security concerns identified.

Comment thread allowedsymbols/analyzer.go Outdated
return &analysis.Analyzer{
Name: "allowedsymbols",
Doc: "enforces symbol-level import restrictions via an allowlist",
Requires: []*analysis.Analyzer{inspect.Analyzer},
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge

inspect.Analyzer is declared in Requires but its result is never retrieved via pass.ResultOf[inspect.Analyzer] — the code uses ast.Inspect (stdlib) directly throughout. This adds unnecessary framework overhead and the doc comment on lines 37-38 incorrectly describes the implementation as using the inspect pass for efficient AST traversal.

Either remove the inspect import and Requires entry (keeping ast.Inspect), or switch to pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) and use inspector.Preorder for actual efficiency gains.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Sonnet 4.6] Fixed — removed inspect.Analyzer from Requires and the unused inspect import. Updated the doc comment to accurately describe the ast.Inspect-based traversal. (commit a437e30)

allowedPkgs := make(map[string]bool)
for _, entry := range symbols {
dot := strings.LastIndexByte(entry, '.')
if dot <= 0 {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge

buildAllowlistSets silently skips malformed entries (dot <= 0) when called from the analyzer path. The test harness catches this with t.Fatalf, but the NewAnalyzer Run function has no equivalent guard, so a malformed allowlist entry would be silently dropped and not enforced. Consider adding a diagnostic or returning an error when encountering a malformed entry through the analyzer path.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Sonnet 4.6] Fixed — NewAnalyzer now panics at construction time if any allowlist entry is missing a dot separator, matching the test-harness t.Fatalf behaviour. Errors surface immediately rather than being silently dropped by buildAllowlistSets. (commit a437e30)

// import restrictions described by cfg. The analyzer uses the inspect pass for
// efficient AST traversal and reports violations via pass.Reportf so they
// appear as go vet diagnostics with proper file:line:col positions.
func NewAnalyzer(cfg AnalyzerConfig) *analysis.Analyzer {
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge

NewAnalyzer has no tests via analysistest.Run or any other mechanism. The unique code paths in the Run function (especially the pass.Files[0].Package position used for unused-symbol reporting at line 59) are not covered by any existing test. Consider adding at least one analysistest-based test before this API is considered production-ready.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Claude Sonnet 4.6] Fixed — added analyzer_test.go with six analysistest.Run-based tests covering: metadata (correct Name, empty Requires), malformed-entry panic, allowed imports (no diagnostics), banned imports, disallowed imports, and unused allowlist symbols. (commit a437e30)

thieman and others added 2 commits March 27, 2026 12:47
…dd tests

Three issues from code review:

1. inspect.Analyzer was declared in Requires but never used — the run
   function uses ast.Inspect directly. Remove the Requires entry and the
   inspect import to avoid unnecessary framework overhead.

2. Malformed allowlist entries (no dot separator) were silently dropped by
   buildAllowlistSets in the analyzer path, unlike the test harness which
   calls t.Fatalf. NewAnalyzer now panics on construction if any entry is
   malformed, matching the test-harness behaviour and surfacing programmer
   errors early.

3. NewAnalyzer had no tests. Add analysistest.Run-based tests covering:
   metadata (correct Name, empty Requires), malformed-entry panic,
   allowed imports (no diagnostics), banned imports, disallowed imports,
   and unused allowlist symbols.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…g/x/mod

The four testdata Go files added for analysistest were missing the
required Apache 2.0 license header. Also adds golang.org/x/mod (pulled
in as an indirect dependency of analysistest) to LICENSE-3rdparty.csv.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@thieman thieman marked this pull request as ready for review March 27, 2026 17:00
@thieman thieman added this pull request to the merge queue Mar 27, 2026
Merged via the queue into main with commit e4ec746 Mar 27, 2026
34 checks passed
@thieman thieman deleted the thieman/go-analysis-linter branch March 27, 2026 17:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants