Skip to content

Add lint rule requiring SAFETY comments on unsafe operations - #65024

Open
Jake Bailey (jakebailey) wants to merge 11 commits into
microsoft:mainfrom
jakebailey:safety
Open

Jake Bailey (jakebailey) wants to merge 11 commits into
microsoft:mainfrom
jakebailey:safety

Conversation

@jakebailey

Copy link
Copy Markdown
Member

We don't have much unsafe (well, besides the AST stuff), but I think it'd be worth forcing ourselves to write out invariants rust-style.

Uses of package unsafe can silently break memory safety, and the
invariants that make each one sound are easy to lose track of. Like
Rust's SAFETY convention, require every unsafe operation to carry a
nearby `// SAFETY:` comment justifying it, so reviewers and future
editors can check the reasoning still holds.

Sizeof, Alignof, and Offsetof are exempt since they are compile-time
constants, as is naming unsafe.Pointer as a type.
The new unsafesafety lint rule requires every unsafe operation to be
justified. The generated casts from *Node to concrete node structs are
all sound for the same reason: each kind is only ever allocated as its
own struct, which embeds the Node header at offset zero. Have the
generator state that at each cast site.
Document the invariant each remaining unsafe operation relies on, as
required by the unsafesafety lint rule, so the reasoning is visible at
the use site and can be rechecked when surrounding code changes.
Copilot AI balanced review requested due to automatic review settings October 29, 2026 20:17
@github-project-automation github-project-automation Bot moved this to Not started in PR Backlog Oct 29, 2026
@typescript-automation typescript-automation Bot added Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug labels Oct 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The linter can silently allow unsafe operations without their own SAFETY justification by reusing trailing comments and skipping dot-imported calls and conversions from unsafe.Pointer.

3 open issues
What changed in this PR

Adds a custom Go lint rule requiring SAFETY: comments for unsafe operations, making memory-safety invariants explicit across the native compiler and filesystem code.

Changes:

  • Registers the analyzer and adds fixtures with expected diagnostics.
  • Documents existing unsafe operations without changing their runtime behavior.
  • Updates the AST generator to emit safety comments for generated casts.
File Description
tsc/​internal/​vfs/​vfstest/​vfstest.go Documents string-backed file-data immutability.
tsc/​internal/​vfs/​internal/​internal.go Documents byte-to-string conversion invariants.
tsc/​internal/​nativepath/​symlink_windows.go Documents Windows attribute-buffer compatibility.
tsc/​internal/​fswatch/​windows.go Documents notification-buffer casts and attribute queries.
tsc/​internal/​fswatch/​walkdir_windows.go Documents directory attribute-buffer compatibility.
tsc/​internal/​fswatch/​walkdir_unix.go Documents directory-entry alignment and boundaries.
tsc/​internal/​fswatch/​inotify_linux.go Documents event-record layout assumptions.
tsc/​internal/​fswatch/​fsevents_darwin.go Documents native-call lifetimes and event-array access.
tsc/​internal/​fswatch/​fsevents_darwin_ffi.go Documents native-buffer ownership and pointer handling.
tsc/​internal/​fswatch/​fanotify_linux.go Documents metadata alignment and size assumptions.
tsc/​internal/​checker/​types.go Documents type-kind and embedding invariants for casts.
tsc/​internal/​checker/​mapper.go Documents mapper-kind and embedding invariants.
tsc/​internal/​ast/​ast.go Documents flow-node cast invariants.
tools/​scripts/​tsc/​generate-go-ast.ts Emits safety comments for generated node casts.
tools/​customlint/​unsafesafety.go Implements unsafe-operation detection and comment matching.
tools/​customlint/​testdata/​unsafesafety/​unsafesafety.go.golden Records expected analyzer diagnostics.
tools/​customlint/​testdata/​unsafesafety/​unsafesafety.go Exercises accepted comments and missing-comment diagnostics.
tools/​customlint/​plugin.go Registers the new analyzer.

Review effort: Balanced

Comment thread tools/customlint/unsafesafety.go Outdated
Comment thread tools/customlint/unsafesafety.go Outdated
Comment thread tools/customlint/unsafesafety.go Outdated
Review found ways for unsafe operations to slip through without their
own justification: converting an unsafe.Pointer back to another type
(including via aliases or named pointer types), calling unsafe through
a dot import, and a trailing SAFETY comment on one statement silently
covering the statement after it. Each of these needs its own stated
invariant just as much as the forms already checked.
The unsafesafety rule now flags conversions from unsafe.Pointer, which
caught these uintptr arguments to syscall_syscall6. They were never
seen before because nothing lints the darwin build.
Several fswatch files are only built for darwin, the BSDs, or other
Unix targets, and none of our lint runners compile them, so rule
violations there go unnoticed. Cross-linting from Linux runners via
GOOS covers them without needing a runner for each OS.

Running all targets in one job took far too long, so each target gets
its own matrix entry. The custom linter build ignores GOOS and GOARCH
so the binary still runs on the host.
@jakebailey
Jake Bailey (jakebailey) requested a balanced review from Copilot October 29, 2026 21:33
The protectedfield test from main runs every analyzer over its test
data, so its unannotated cast now trips unsafesafety.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The lint check incorrectly rejects trailing SAFETY comments on multiline conversions and fails to flag undocumented unsafe-pointer conversions through type parameters.

2 open issues
3 resolved since last review

Review effort: Balanced

Comment thread tools/customlint/unsafesafety.go Outdated
Comment thread tools/customlint/unsafesafety.go
A trailing SAFETY comment after the closing paren of a multiline
unsafe call is clearly about that call, but was rejected because only
start lines were matched.

Conversions through a type parameter constrained to unsafe.Pointer
are just as unsafe as direct ones, but the parameter's underlying
type is an interface, so they slipped through unflagged.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Type-parameter aliases bypass required SAFETY checks in both conversion directions, while intersected constraints can incorrectly trigger those checks.

2 open issues
2 resolved since last review

Review effort: Balanced

Comment thread tools/customlint/unsafesafety.go Outdated
Comment thread tools/customlint/unsafesafety.go Outdated
An alias of a type parameter hid the parameter, letting its unsafe
conversions go unflagged. Embedded constraints were treated as a union
rather than an intersection, so a constraint narrowed to uintptr still
demanded a SAFETY comment.

Boxing an unsafe.Pointer into an interface cannot break memory safety,
yet it was flagged. That became much more visible once constraints
like any were treated as possibly containing unsafe.Pointer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

The lint rule incorrectly flags safe generic identity conversions as unsafe, requiring unnecessary safety comments even when source and destination types are identical.

1 open issue
2 resolved since last review

Review effort: Balanced

Comment thread tools/customlint/unsafesafety.go
Converting a value to its own type cannot break memory safety, but a
generic T(x) with T constrained by any or comparable was flagged
because those type sets include unsafe.Pointer.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

Subtle interactions between generic constraint intersections and named-type identity require human assessment because they can cause safe conversions to be incorrectly flagged as unsafe.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Author: Team For Uncommitted Bug PR for untriaged, rejected, closed or missing bug

Projects

Status: Not started

Development

Successfully merging this pull request may close these issues.

2 participants