You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Lint writes to protected record kind fields - #64791
Node, type, and type mapper headers carry a kind that unsafe casts trust to identify the concrete allocation around the header. Assigning that kind, or copying a record by value, can leave a header that claims to be a larger record than its storage holds.
Marks those kind fields with a customlint:"protected" struct tag. A new protectedfield analyzer rejects writes to protected fields (assignment, ++/--, &, keyed or positional literals) and by-value copies, variables, parameters, results, and receivers of structs that contain one.
The constructors that set a kind opt out with //nolint:customlint, so any new exception is visible in review. Casts already check the kind at runtime and kind zero is never valid, so zero-valued records cannot be cast into larger ones.
The declarations transformer no longer clones a JS import declaration and retags its kind. It builds a real import declaration instead, with a new importTag5 case. Tests that built bare node headers or retagged nodes now use the factory.
Jake Bailey (jakebailey)
changed the title
Enforce compiler record construction and cast invariants
Enforce compiler record construction and cast invariants
The compact layouts lower in this stack rely on every header belonging to the concrete allocation its kind identifies.
- Adds generated factory-kind guards, exhaustive construction and cast tests, and a type-aware analyzer guarding construction, retagging, header copying, and unsafe-cast boundaries.
- Declaration emit now constructs ordinary imports instead of retagging JSDoc imports.
- Protects the compact layouts without adding a physical-layout tag to every AST node.
Oct 13, 2026
Jake Bailey (jakebailey)
changed the title
Enforce compiler record construction and cast invariants
The compact layouts lower in this stack rely on every header belonging to the concrete allocation its kind identifies.
- Adds generated factory-kind guards, exhaustive construction and cast tests, and a type-aware analyzer guarding construction, retagging, header copying, and unsafe-cast boundaries.
- Declaration emit now constructs ordinary imports instead of retagging JSDoc imports.
- Protects the compact layouts without adding a physical-layout tag to every AST node.
Enforce compiler record construction and cast invariants
Oct 13, 2026
Detect implicit copies from range variable declarations
tools/customlint/unsafelayout.go:335
A for _, record := range records declaration performs an implicit value copy, but the defining identifier has no TypesInfo.Types entry, so checkCopy skips it; this branch only checks assignments to tags. Consequently range over a slice, map, or channel of Node/Type records can create a detached tagged header without a diagnostic. Inspect TypesInfo.Defs for := range variables and add a fixture for this case.
This issue also appears on line 340 of the same file.
This checks only the identifier spelling, so a local variable that shadows the constructor's kind parameter would be accepted as the tag source. That lets newType/newNode initialize a header with an unrelated value while the analyzer claims the constructor is verified. Resolve the identifier through TypesInfo and compare it with the actual constructor parameter object; add a shadowing case to the analyzer fixtures.
Builtin bulk-copy operations are not checked here. For example, copy(dst, src) and append(dst, src...) can copy slices of concrete compiler records, but each argument has slice type, so checkCopy does not report it and this branch handles only new/make. This bypasses the analyzer's by-value header-copy rule; inspect copy/variadic append element types and add corresponding fixtures.
copilot says: The layout lint is a best-effort guard against realistic mistakes, not a proof that records are always used safely. Checked casts still panic at runtime on a mismatched kind, so a gap in the lint can't silently corrupt anything. I've fixed the shadowed kind and copy/variadic append cases; further findings that need Go-level value tracking are out of scope for this PR.
A node's Kind identifies the concrete allocation that unsafe accessors
reinterpret it as. The declarations transformer cloned a JS import
declaration and overwrote its Kind; that only works while the two kinds
happen to share one layout. Some tests similarly built bare Node headers
or retagged one node as another kind.
Build the import declaration through the factory instead, and create
real nodes in the tests, so that nothing depends on retagging.
Unsafe casts trust the kind stored in a node, type, or type mapper header
to describe the allocation around it. Assigning that field, or copying a
record by value, can make a header claim to be a larger record than its
storage holds.
Mark those fields with a customlint:"protected" struct tag and reject
writes, address-taking, keyed literals, and by-value copies of structs
that contain a protected field. The constructors that legitimately set a
kind opt out with nolint comments, so new exceptions stay visible in
review. Casts already check the kind at runtime, and kind zero is never
valid, so zero-valued records cannot be cast into larger ones.
Jake Bailey (jakebailey)
changed the title
Enforce compiler record construction and cast invariants
Lint writes to protected record kind fields
Oct 14, 2026
The reason will be displayed to describe this comment to others. Learn more.
Leaving this out of scope. The lint guards against kinds that disagree with their storage. An element of a []Header is already a standalone header-sized value, and the only ways to give it a wrong kind are a write or a by-value copy out of a larger record, both of which are already flagged. Copying between such slices cannot create a new bad header.
Range clauses can assign a protected field directly, and an elided
literal inside a []*T or map[K]*T composite initializes a fresh header
just like &T{...} does. Both bypassed the write check.
This branch has not been deployed
No deployments
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Node, type, and type mapper headers carry a kind that unsafe casts trust to identify the concrete allocation around the header. Assigning that kind, or copying a record by value, can leave a header that claims to be a larger record than its storage holds.
customlint:"protected"struct tag. A newprotectedfieldanalyzer rejects writes to protected fields (assignment,++/--,&, keyed or positional literals) and by-value copies, variables, parameters, results, and receivers of structs that contain one.//nolint:customlint, so any new exception is visible in review. Casts already check the kind at runtime and kind zero is never valid, so zero-valued records cannot be cast into larger ones.importTag5case. Tests that built bare node headers or retagged nodes now use the factory.