fix(extraction): treat trailing blanks at EOF as an absent newline, not a miss (#1746) - #1773
Conversation
|
Thanks for opening this — it has been seen, and it is queued. This note is automated, but it is not a brush-off: it exists so you know where your PR stands instead of having to guess from silence. Current review status: working through a backlog. What that means for this PR, concretely:
Things that will genuinely speed it up whenever review does happen:
If this fixes a bug, a reproduction we can run is worth more than a description of the symptom. Thanks for contributing, and sorry in advance for the wait. |
cbb456a to
2888d3e
Compare
|
The What failedalongside three setup failures in the same run: That is daemon crash-recovery and UI-config acceptance. This PR only changes zero-width MISSING-node handling in The same failure on two unrelated branchesIdentical signature, line for line, including the same three
Neither of the other two shares any code with this one. This PR's content did not change between a pass and a failThe branch was rebased onto successive bases. The patch is byte-identical in both runs (same per-file diff,
I first suspected something in Across the last 11 completed pull_request runs that include this job, I count 9 pass and 1 fail on distinct branches, so it looks intermittent rather than base-dependent (PR #1777 also passed it on the new base at 07:01Z). Local verification of the change itselfFor what it is worth, on Ubuntu 24.04 + gcc with the ASan/UBSan build:
Happy to open a separate issue for the flaky guard if that is useful. I did not file one unprompted because blank issues are disabled and neither template really fits a CI-infrastructure flake, so I would rather follow whatever process you prefer. |
2888d3e to
d5a73fb
Compare
|
Thank you for the careful cross-branch attribution. Your evidence is useful and the parse-coverage diff is isolated from the failing daemon and UI guard. Please do not open another issue for that historical signature right now. The startup-lock failure was addressed by #1772, and the remaining UI-configuration result is already tracked by #1796. Keeping those records together avoids a duplicate while this PR stays focused on the Dockerfile EOF false positive. I also checked current main: cbm_is_eof_terminator_miss() only suppresses a zero-width missing node when its end byte equals the raw source length, so trailing blanks do expose the exact gap you described. Thank you for the binding controls and for not bundling the unrelated guard work. |
|
I owe you an apology before anything else. You opened this on 20 August. It sat without a single comment, and on 29 August I merged #1853 — my own fix for the same issue, #1746 — without reviewing yours first and without crediting you. You were nine days ahead of me and I did not look. That is my mistake, not a process quirk, and I am sorry. It is worse than a duplicate, because your version is the better one on the point that still matters. What landed, and where it is still wrong
if (!source || strcmp(ts_node_type(n), "\n") != 0) {
return false;
}
for (uint32_t i = end; i < (uint32_t)source_len; i++) {
if (source[i] != ' ' && source[i] != '\t') {
return false;
}
}Space and tab only. Which means the exact objection you wrote into your PR description still applies to the code that shipped:
You were right, and
Your two guards are also additive: The one thing #1853 has that your patch drops
What I would like to ask of youRebase onto
That is a much smaller diff than what you have now, and the tests are the proof: rebased against Both files conflict with If you would rather not spend more time on a PR that got treated this way — entirely fair — say so and I will land the delta myself with Thank you for this. The parse-tree dump with byte offsets for all three controls is the reason the mechanism was unambiguous, and I should have read it three weeks ago. |
…ot a miss A file whose final line lacks its newline leaves the grammar's mandatory terminator MISSING at EOF. DeusData#1610 suppressed that phantom parse_partial on the grounds that the node is ZERO-WIDTH: the parser consumed no source for it, so by construction nothing was dropped. That suppression tested `end == source_len` exactly. Trailing blanks are extras owned by no node, so `ENTRYPOINT ["a"] ` + EOF parks the zero-width terminator one byte short of source_len and the check missed it. This is why neither a trailing blank nor an absent final newline flagged on its own -- only the pair did, exactly as the reporter's byte-exact control matrix showed. Treat "at EOF" as EOF modulo a trailing blank run. Every blank except newline qualifies, deliberately not a hand-picked subset: a form feed is no more content than a space, and DeusData#1610 exists precisely because whether a file got flagged used to hinge on such incidentals. Newline stays excluded because a terminated final line produces no MISSING terminator at all. The rule remains ZERO-WIDTH ONLY. A width-bearing MISSING/ERROR at EOF is a genuine loss and is still flagged (a Makefile's unterminated final recipe really does vanish), as is any failure earlier in the file; both are pinned by guard tests alongside the existing DeusData#1610 guards. Verified end to end: the issue's own `cli index_repository` reproduction goes from parse_partial_count 1 to 0 with node count unchanged, so the phantom flag is gone without anything dropping out of the graph. Fixes DeusData#1746 Signed-off-by: Rares Popa <2606875+rarepops@users.noreply.github.com>
d5a73fb to
572725e
Compare
|
Rebased onto current The residual diff now follows the requested shape:
Validation completed in the repository test image with GCC ASan/UBSan:
|
|
That is exactly the delta — thank you for turning it round so quickly. You kept - if (source[i] != ' ' && source[i] != '\t') {
+ if (!cbm_is_blank_not_newline(source[i])) {with the helper covering the full set — space, tab, vertical tab, form feed, CR. So CI is queued — our Actions pool is badly backlogged today (roughly one job at a time against 38 queued runs), so expect a wait that has nothing to do with this PR. I will merge on green. And to say it once more plainly: you were right about the blank set from the start, in August, and |
|
Your The failing assertion was real:
So I tested it instead of reasoning about it. Built the actual merge — current Two other things point the same way: that test's fixture ( Nothing for you to do. The change itself is exactly the delta I asked for and I am happy with it — |
|
Merged as To restate the record plainly, because it matters more than the diff: you opened this on 20 August, and on 27 August I merged my own #1853 for the same issue — uncredited, and narrower. The final shape is better than either original: On the red you sawYour That leg was also carrying a known runner fault, and it reported Four of your changes have landed today: #1779, #1799, #1802 and this. Thank you for staying with it. |
Fixes #1746
Root cause
#1610 suppressed the phantom
parse_partialproduced by a missing final newline, on the grounds that the grammar's MISSING terminator at EOF is zero-width: the parser consumed no source for it, so by construction nothing was dropped.That suppression tested
end == source_lenexactly. Trailing blanks are extras owned by no node, so the terminator parks one byte short and the check misses it.Dumping the parse tree for the reporter's three byte-exact controls confirms the mechanism:
source_lenENTRYPOINT ["a"]+ EOFbytes[29,29)zero-widthENTRYPOINT ["a"]+ EOFbytes[29,29)zero-widthENTRYPOINT ["a"] \nThis is exactly why neither a trailing blank nor an absent newline flagged alone: only the pair did.
Fix
Treat "at EOF" as EOF modulo a trailing blank run.
Every blank except newline qualifies, deliberately not a hand-picked subset. An earlier cut of this patch allowed only space/tab/CR and left
\fand\vstill flagged, which recreated in miniature the exact arbitrariness #1610 set out to kill: a form feed is no more content than a space. Newline stays excluded because a terminated final line produces no MISSING terminator at all.The rule remains zero-width only. A width-bearing MISSING/ERROR at EOF is a genuine loss and is still flagged (a Makefile's unterminated final recipe really does vanish), as is any failure earlier in the file.
Tests
Added to
tests/test_parse_coverage.c, mirroring the existing #1610 structure:dockerfile_trailing_blank_at_eof_not_flagged_issue1746: the report's repro plus tab, vertical tab, form feed, blank-run and CR variantsdockerfile_trailing_blank_then_newline_still_clean_issue1746: pins the reporter's clean control from the other sidereal_error_before_eof_still_flagged_with_trailing_blank_issue1746: guard, a genuine mid-file failure must not be swallowed just because the file ends in blankswidth_bearing_error_at_eof_still_flagged_with_trailing_blank_issue1746: guard, a width-bearing loss at EOF stays honestBoth guards fail if the suppression is widened too far.
Verification
End to end, using the issue's own command:
parse_partial_countgoes 1 -> 0, withnodes: 8unchanged, so the phantom flag is gone without anything dropping out of the graph. Controls stay at 0.Gates run locally (Ubuntu 24.04 + gcc, ASan + UBSan, mirroring CI):
parse_coverage: 18/18, including all five pre-existing Dockerfile grammar flags stock .NET Dockerfiles asparse_partial#1610 testsclang-format-20(CI's pin): cleancppcheck 2.20.0(CI's pin): cleanlint-no-suppress: cleanOne caveat on the full-suite run: five
cliinstall/activation tests failed locally withancestor_directory_world_writable (mode 0777, uid 0). That is a Docker-on-Windows bind mount presenting mode 0777 and tripping the install activation guard, not a regression: re-running that suite from a container-native directory gives 282/282. Mentioning it in case it helps anyone else developing on a Windows host.I have not been able to exercise the Windows CI leg locally. The change is byte arithmetic over the source buffer with no platform-dependent behaviour, so I would expect parity, but flagging it rather than assuming.