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
Cover all override directives in the apple_support CI guard - #424
Added all four remaining Bazel module override directives to the alternation, plus a test for each. Confirmed each new test fails against the pre-fix pattern (standalone repro) before adding the fix.
Test plan
bazelisk test //python:ci_macos_asan_test — passes with the fixed helper; each new test confirmed red against the old pattern first
_has_apple_support_override() only matched single_version_override --
archive_override, git_override, local_path_override, and
multiple_version_override all silently returned False. Same gap flagged
by review on the equivalent toolchains_llvm guard in #423. Added the
remaining four directives to the alternation and a test for each;
confirmed each new test fails against the pre-fix pattern (standalone
repro) before adding the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Same gap review caught on the toolchains_llvm guard in #423: the inline
regex only matched "name" immediately followed by "version", so reordered
or multiline bazel_dep(...) arguments would fail the guard even with the
dependency correctly pinned. Extracted _apple_support_bazel_dep_version(),
parsing the whole call body and finding both fields independently, matching
_rules_cc_bazel_dep_version() in ci_windows_cppopts_test.py. Confirmed the
new reordered-args test fails against the old inline pattern (standalone
repro) before the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Proactively applied the same fix review caught on #423's analogous version parser: the inline `bazel_dep` regex here only matched `name` immediately followed by `version`, so reordered/multiline args would fail the guard despite the dependency being correctly pinned. Extracted `_apple_support_bazel_dep_version()` parsing the whole call body, matching `_rules_cc_bazel_dep_version()` in `ci_windows_cppopts_test.py`. Confirmed red against the old pattern before the fix (2c95756).
Same bug review found on the toolchains_llvm guard in #423: [^)]*)\)
stops at the first ")" even inside a string, comment, or nested call, so
a leading comment like "# fixes Xcode 27 crosstool warnings (see docs)"
before the real args would truncate the match. _iter_call_bodies()
replaces both regexes with a character scanner tracking string/comment
state and paren depth. Added a test with a ")" in a leading comment for
each helper; confirmed both fail against the old regex (standalone repro)
before the fix.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Proactively applied the same fix review caught on #423's analogous helpers: `[^)]*)\)` truncates at the first `)`, including one inside a leading comment, which would spuriously fail/bypass this guard. Replaced both regexes with `_iter_call_bodies()`, a character scanner tracking string/comment state and paren depth. Added a test with a `)` in a leading comment for each helper; confirmed red against the old regex before the fix (d9589ac).
…tring.
- git_override was the one new directive in the #421 follow-up without its
own dedicated test, even though the alternation already matched it
correctly -- added test_has_apple_support_override_detects_git_override
so a future regression in that branch would be caught.
- _has_apple_support_override()'s docstring said it detects a pin "to a
specific commit/version set," which doesn't describe local_path_override
(selects a local directory, not a commit or version). Reworded to
describe the actual predicate: any of the five override directives
targeting apple_support.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Addressed in a8ffd41 — these two weren't covered by the earlier proactive mirroring from #423 (they're specific to this PR's own diff):
Added `test_has_apple_support_override_detects_git_override`, the one directive left without a dedicated test.
Reworded `_has_apple_support_override()`'s docstring, which described the predicate as detecting a pin "to a specific commit/version set" — inaccurate for `local_path_override`, which selects a local directory, not a commit or version.
Ignore commented-out directives when detecting version overrides
python/tests/ci_macos_asan_test.py:35
pattern.finditer() locates call-looking text before any lexical state is tracked, so commented-out directives are treated as active overrides. For example, # single_version_override(module_name = "apple_support", version = "1.24.2") makes _has_apple_support_override() return True even though MODULE.bazel contains no override. Please identify call starts while scanning code state (excluding comments and strings), and add a regression test for a commented-out override.
Same bug fixed on the toolchains_llvm guard in #423: _iter_call_bodies()
located each call with a plain regex search for "name(", which matches
that text inside a "#" comment too -- a commented-out "bazel_dep(...
version = \"9.9.9\")" before the real dependency made the version guard
read the dead value, and a commented-out override made
_has_apple_support_override return a false positive. Rewritten as a
single linear scan that tracks string/comment state continuously.
Confirmed both failure modes against the old scanner (standalone repro)
before the fix, and added a dedicated commented-out-call test for each
helper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Mirrored the fix from #423: `_iter_call_bodies()` located each call via a plain regex search for `name(`, which matches that text inside a `#` comment too — confirmed with a standalone repro that a commented-out `bazel_dep(... version = "9.9.9")` before the real dependency made the version guard read the dead value, and a commented-out override made `_has_apple_support_override` return a false positive.
Rewrote it as a single linear scan tracking string/comment state continuously, and added a dedicated commented-out-call test for each helper (8c8e2b0).
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Parser edge cases can cause the CI guard to miss overrides or accept commented-out version values.
0 open findings
Previously missed (2)
In code that hasn't changed since last review
Allow whitespace between directive name and opening parenthesis
python/tests/ci_macos_asan_test.py:76
This requires ( to immediately follow the directive name, although whitespace before the call parenthesis is valid. An override written as archive_override ( is therefore skipped and the CI guard can pass despite the prohibited override. Skip horizontal whitespace after a matched name before checking and consuming (.
Ignore commented-out assignments when matching directives
python/tests/ci_macos_asan_test.py:98
The scanner tracks comment state while balancing parentheses, but returns the original slice with comments intact. Both downstream regexes therefore treat commented-out assignments as live: for example, an active version = "1.0.0" preceded by # version = "9.9.9" makes the minimum-version guard pass, and a comment containing module_name = "apple_support" makes an override for another module look like an apple_support override. Return a body with comment text masked/removed (while preserving strings), or parse active argument tokens before matching.
Two gaps in _iter_call_bodies(), confirmed with standalone adversarial
inputs before fixing:
- "archive_override (module_name = ...)" (space before the parenthesis,
valid Starlark) matched zero calls, so the guard missed it entirely —
a false negative.
- The body text it returned still contained comment characters verbatim,
so a commented-out "module_name = \"apple_support\"" inside a different
module's override made it look like an apple_support override (false
positive), and a commented-out stale version before the real one could
win the regex search (version guard reads the wrong value).
The name/paren scan now skips whitespace between the call name and "(",
and the body-building loop drops "#" comment characters from the output
(string contents are still kept verbatim) instead of just tracking comment
state to avoid breaking paren balance. Added a regression test for each.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Whitespace before "(": `archive_override (module_name = ...)` (valid Starlark, space before the paren) previously matched zero calls — a false negative. Fixed by skipping whitespace between the call name and "(" before requiring it.
Commented-out assignments inside a call body: the returned body text kept comment characters verbatim, so a commented-out `module_name = "apple_support"` inside a different module's override made it look like an apple_support override, and a commented-out stale version before the real one could win the version-guard's regex search. Fixed by dropping "#" comment characters from the body text the function returns (string contents are kept verbatim).
Confirmed both with standalone adversarial-input repros before fixing, and added a dedicated regression test for each.
Mirrors the fix just applied to the identical apple_support guard in #424,
confirmed here with the same standalone adversarial-input repros before
fixing:
- "archive_override (module_name = ...)" (space before the parenthesis,
valid Starlark) matched zero calls -- a false negative.
- The returned body text kept comment characters verbatim, so a
commented-out "module_name = \"toolchains_llvm\"" inside a different
module's override made it look like a toolchains_llvm override, and a
commented-out stale version before the real one could win the
version-guard's regex search.
The name/paren scan now skips whitespace between the call name and "(",
and the body-building loop drops "#" comment characters from the output
(string contents are kept verbatim). Added a regression test for each.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Starlark accepts both quote styles, but this matcher only recognizes double quotes. A valid override such as archive_override(module_name = 'apple_support', ...) therefore makes the guard return False, allowing exactly the override this test is meant to reject. Match and back-reference either quote delimiter (and add a single-quoted regression case).
…y text.
Mirrors the fix just applied to the identical toolchains_llvm guard in
#423: masking "#" comments out of the returned body still left a gap --
a regex search over that body text also matches an unrelated argument's
string value that happens to contain assignment-shaped text, e.g. a
bazel_dep for rules_cc with
patch_cmds = ['historically name = "apple_support", version = "9.9.9"']
would be misread as the apple_support dependency, and the same shape of
string value fools the override check.
_iter_call_bodies() is replaced by _iter_call_kwargs(), which parses actual
"identifier = value" pairs at the call's top-level parenthesis depth only,
with the identifier-matcher never offered a character while scanning
inside a string or comment. _apple_support_bazel_dep_version() and
_has_apple_support_override() now check dict lookups instead of regexes.
Added a dedicated test for the string-value case on each helper.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Both reported by Copilot review on #424 (same parser):
- The direct call-name match (immediately before the "(" lookup) used
_skip_ws() rather than _skip_ws_and_comments(), so a comment between
the name and its own "(" -- e.g. `(local_path_override # note\n
(module_name = "toolchains_llvm", ...))`, a whole call wrapped in
redundant grouping parens -- was missed, even though the grouped
"(name)(...)" *indirection* form already tolerated such comments.
Fixed by extracting the direct-match and grouped-indirection logic
into one _match_call_name() helper (replacing the former inline
duplicate), used uniformly by both the outer scan and the new nested
scan below, so both forms get the same comment handling for free.
- The body-scanning loop consumed a whole call's argument list via
paren/bracket/brace balancing only, so a recognized call *nested* in
an argument value -- e.g. `patches = single_version_override(...)
or []` inside another override's call -- was swallowed as inert
text instead of being recognized as the real call it is. Fixed by
extracting the body-scanning loop into _scan_call_body(), which now
also calls _match_call_name() at every position in the body (not
just the call's own name before entry); a match there is scanned
recursively and its own {keyword: value} dict is appended to the
shared results list before resuming the outer call's own scan past
the nested call's span.
Both reproduced against the exact Copilot-supplied examples before
the fix, and added as permanent regression tests. Full //python/...
suite green (24/24).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Fixed all three "previously missed" findings from this round:
Line continuations between callable name and parenthesis bypass detection: _skip_ws() only recognized the bare whitespace characters " \t\r\n", not a backslash-newline line continuation, so archive_override\ + newline + (module_name = "apple_support", ...) was never recognized as a call (confirmed with the exact example). Fixed by having _skip_ws() treat a backslash immediately followed by LF or CRLF as whitespace too. (This is the same fix already made on Bump toolchains_llvm to 1.11.2, drop #791 archive_override #423 last round for its own finding — should have mirrored it back here then; doing so now.)
Comments between callable name and parenthesis bypass override detection: the direct call-name match used _skip_ws() instead of _skip_ws_and_comments(), so (local_path_override # note\n(module_name="apple_support", ...)) — a whole call wrapped in redundant grouping parens — was missed (confirmed). Fixed by extracting the direct-match and grouped-indirection logic into one _match_call_name() helper used uniformly everywhere, so both forms share the same comment handling.
Nested override calls are omitted from scanner results: the body-scanning loop only paren/bracket/brace-balanced a call's argument list, so a recognized call nested in an argument value (e.g. patches = single_version_override(...) or []) was swallowed as inert text instead of being recognized as the real call it is (confirmed with the exact example). Fixed by extracting the body scan into _scan_call_body(), which now also matches _match_call_name() at every position inside the body — a nested match is scanned recursively and its own kwargs appended to the shared results.
All three reproduced against the exact reported examples before fixing, with permanent regression tests added. Full //python/... suite green (24/24).
Mirrored all three to #423 (same parser) in a separate commit just pushed there.
Two scanner defects, both reported by Copilot review:
- Attribute calls were excluded only when "." immediately preceded the
name, so "ext. bazel_dep(...)" (or a continuation or comment after the
".") was read as a real directive: an earlier qualified bazel_dep
could supply the pinned version, and a qualified archive_override
tripped the guard. Both scans now consume the attribute name after any
"." (_skip_attribute), so the forward scan that already skips strings
and comments decides it; a "." inside a comment is never seen.
- After a keyword's "=", scanning resumed at the right-hand side and its
tokens could be recorded as positional arguments, so
`repo_name = "apple_support_" + "9.9.9"` supplied a fake second
positional (bazel_dep's version) to a call that pins none. The body
scan now tracks top-level "," boundaries and classifies each argument
once, by its first token; later tokens are only scanned for nesting
and nested calls.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Same shared parser, same two defects Copilot reported on #424:
- "ext. bazel_dep(...)" / "ext. archive_override(...)" (or a
continuation or comment after the ".") was read as a real directive,
since only the character immediately before the name was checked.
Both scans now consume the attribute name after any "."
(_skip_attribute); a "." inside a comment is never seen.
- Tokens inside a keyword's right-hand side could be recorded as
positional arguments, so `repo_name = "toolchains_llvm_" + "9.9.9"`
supplied a fake version to a versionless bazel_dep. The body scan now
tracks top-level "," boundaries and classifies each argument once,
by its first token.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixed both findings from the latest review in 5725299. Each was reproduced with the exact reported example first:
Incorrect preceding-character check misidentifies Bazel directives. Attribute calls were excluded only when . came directly before the name, so ext. bazel_dep(...) was treated as a real directive. That let an earlier qualified call supply the version (9.9.9 won over the real 1.24.2), and a qualified archive_override tripped the guard. Both scans now consume the attribute name after any . (_skip_attribute). This happens in the same forward scan that already skips strings and comments, so whitespace, line continuations and comments after the . are covered, and a . inside a comment is never seen. Regression tests cover both helpers, all three separators, and a real directive right after a comment that ends in ..
Keyword-value tokens are misclassified as positional arguments. After =, scanning picked up again at the right-hand side, and those tokens could be recorded as positionals. For example, the "9.9.9" tail of repo_name = "apple_support_" + "9.9.9" became bazel_dep's version. The body scan now tracks top-level , boundaries and classifies each argument once, by its first token. Later tokens are still scanned for nesting and nested calls, but nothing in them is recorded. A regression test asserts that the versionless call is rejected, and the nested-call tests still pass.
Full //python/... suite: 24/24 green. Mirrored to #423 (same parser) in 1452404.
Starlark's syntax is a subset of Python's, so ast.parse handles strings,
comments, continuations and statement boundaries exactly. This fixes the
scanner joining "dep_function = bazel_dep" and a following "(...)" tuple
into a fictitious call, and replaces ~1000 lines of lexer and its
tokenizer-level tests with ~70 lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixed the latest finding, Preserve statement boundaries when matching dependency calls, by replacing the hand-written lexer altogether (d935001).
Starlark's syntax is a subset of Python's, so the guard now parses MODULE.bazel with Python's own ast and walks the ast.Call nodes whose callee is a bare name (bazel_dep, *_override). Python's tokenizer gets strings, escapes, raw strings, comments, line continuations and statement boundaries right by construction:
dep_function = bazel_dep followed by ("apple_support", "9.9.9") on the next line parses as two statements, so the real 1.24.2 pin is the one that counts. The same holds for (bazel_dep) and for the override directives.
Attribute calls such as ext. bazel_dep(...) have an ast.Attribute callee and are excluded.
Grouping parens around a callable, as in (bazel_dep)(...), disappear from the tree.
Any argument that isn't a single string literal still fails closed.
Copilot's example was added as a regression test, for both helpers and both callable forms, and confirmed failing against the old parser before the rewrite.
I also removed about 30 tests that only exercised escape decoding, raw-string backslash parity, comment placement and continuations. With ast they were testing CPython's tokenizer rather than the guard. The eight fail-closed cases are now subtests of one test. One sample, [ext. # note\n…, never closed its bracket, so it wasn't valid Starlark; it now does. The file goes from 1534 lines to 465. Full //python/... suite green (24/24).
#423 was merged with the old parser, so the same rewrite for ci_msan_test.py will come as a follow-up PR.
ast records keyword unpacking as a keyword with arg None, so
local_path_override(**{"module_name": "apple_support", ...}) read as having
no module_name and slipped past the guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mirrors #424. ast records keyword unpacking as a keyword with arg None, so
local_path_override(**{"module_name": "toolchains_llvm", ...}) read as
having no module_name and slipped past the guard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixed Reject keyword unpacking that bypasses override detection (22b30ab).
ast records **kwargs as a keyword whose arg is None, so _argument() saw local_path_override(**{"module_name": "apple_support", ...}) as having no module_name at all. When the argument isn't given by keyword or position and the call has any ** unpacking, _argument() now returns _UNRESOLVED, so the override guard fails closed.
Regression tests added, confirmed red first:
Copilot's example, plus **override_kwargs (a variable), both now detected.
*["apple_support"] as module_name. This already failed closed, because a starred positional is an ast.Starred node, which _literal() treats as non-literal. The test pins that behaviour.
local_path_override("rules_cc", **{...}), which stays not-an-override, since a positional module_name can't also come from **.
Full //python/... suite green (24/24). Mirrored to #433.
@tameware , memory serves me correctly we started to add tests for build overrides and CI scripts when one of the sanitiser runs did not do what we expected. With these changes it feels more reasonable to keep these tests hanging around. I am happy to revise the need for these tests if you think they are overkill.
memory serves me correctly we started to add tests for build overrides and CI scripts when one of the sanitiser runs did not do what we expected. With these changes it feels more reasonable to keep these tests hanging around. I am happy to revise the need for these tests if you think they are overkill.
Your memory is better than mine! If you don't mind writing them, I don't mind keeping them. There's something comforting about having a large body of unit tests. We should run a code coverage analysis to see how much they're missing. I'll ask Cursor to take a look.
Follow-up to dds-bridge#423, mirroring dds-bridge#424. Starlark's syntax is a subset of
Python's, so ast.parse handles strings, comments, continuations and
statement boundaries exactly. This fixes the scanner joining
"dep_function = bazel_dep" and a following "(...)" tuple into a
fictitious call, and replaces ~1000 lines of lexer and its
tokenizer-level tests with ~70 lines.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Test plan
bazelisk test //python:ci_macos_asan_test— passes with the fixed helper; each new test confirmed red against the old pattern firstbazelisk test //python/...— 24/24 pass🤖 Generated with Claude Code