Skip to content

Commit 6cca768

Browse files
authored
Merge pull request DeusData#1773 from rarepops/fix/dockerfile-trailing-blank-eof-1746
fix(extraction): treat trailing blanks at EOF as an absent newline, not a miss (DeusData#1746)
2 parents cc3e263 + 572725e commit 6cca768

2 files changed

Lines changed: 82 additions & 4 deletions

File tree

‎internal/cbm/cbm.c‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -821,9 +821,13 @@ static void cbm_error_regions_push(cbm_error_regions_t *acc, TSNode n) {
821821
* untouched.
822822
*
823823
* #1746: the Dockerfile grammar places that zero-width missing newline before
824-
* trailing horizontal whitespace rather than at raw EOF. Preserve the broad
825-
* exact-EOF rule above; only extend it past spaces/tabs when the missing token
826-
* is specifically a newline. */
824+
* trailing whitespace rather than at raw EOF. Preserve the broad exact-EOF
825+
* rule above; only extend it past blanks when the missing token is specifically
826+
* a newline. */
827+
static bool cbm_is_blank_not_newline(char c) {
828+
return c == ' ' || c == '\t' || c == '\v' || c == '\f' || c == '\r';
829+
}
830+
827831
static bool cbm_is_eof_terminator_miss(TSNode n, const char *source, int source_len) {
828832
if (!ts_node_is_missing(n) || source_len < 0) {
829833
return false;
@@ -840,7 +844,7 @@ static bool cbm_is_eof_terminator_miss(TSNode n, const char *source, int source_
840844
return false;
841845
}
842846
for (uint32_t i = end; i < (uint32_t)source_len; i++) {
843-
if (source[i] != ' ' && source[i] != '\t') {
847+
if (!cbm_is_blank_not_newline(source[i])) {
844848
return false;
845849
}
846850
}

‎tests/test_parse_coverage.c‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,77 @@ TEST(perl_malformed_source_remains_partial_issue1838) {
470470
PASS();
471471
}
472472

473+
/* ── #1746: trailing blanks before EOF are still just an absent newline ───────
474+
*
475+
* #1610 suppressed the zero-width MISSING terminator but tested for it with
476+
* `end == source_len`. Trailing blanks are extras owned by no node, so
477+
* `ENTRYPOINT ["a"] ` + EOF parks it at [29,29) while source_len is 30.
478+
*
479+
* The reporter's byte-exact controls pin the trigger to the PAIR: `] ` + EOF
480+
* flags, `] ` + newline is clean, `]` + EOF is clean. */
481+
TEST(dockerfile_trailing_blank_at_eof_not_flagged_issue1746) {
482+
const char *cases[] = {
483+
"FROM scratch\nENTRYPOINT [\"a\"] ", /* space + EOF — the report */
484+
"FROM scratch\nENTRYPOINT [\"a\"]\t", /* tab + EOF */
485+
"FROM scratch\nENTRYPOINT [\"a\"]\v", /* vertical tab + EOF */
486+
"FROM scratch\nENTRYPOINT [\"a\"]\f", /* form feed + EOF */
487+
"FROM scratch\nENTRYPOINT [\"a\"] \t ", /* run of blanks + EOF */
488+
"FROM scratch\nENTRYPOINT [\"a\"] \r", /* CRLF file truncated to CR */
489+
};
490+
for (size_t i = 0; i < sizeof(cases) / sizeof(cases[0]); i++) {
491+
CBMFileResult *r = do_extract(cases[i], CBM_LANG_DOCKERFILE, "Dockerfile");
492+
ASSERT_NOT_NULL(r);
493+
bool flagged = r->parse_incomplete;
494+
if (flagged) {
495+
fprintf(stderr, " case %zu flagged: ranges=%s\n", i,
496+
r->error_ranges ? r->error_ranges : "(none)");
497+
}
498+
cbm_free_result(r);
499+
if (flagged) {
500+
FAIL("trailing blanks must not turn an absent final newline into parse_partial");
501+
}
502+
}
503+
PASS();
504+
}
505+
506+
/* GUARD: widening the tail must not swallow a genuine mid-file failure just
507+
* because the file happens to end in blanks. */
508+
TEST(real_error_before_eof_still_flagged_with_trailing_blank_issue1746) {
509+
size_t n = strlen(C_IFDEF_SPLIT);
510+
char *buf = (char *)malloc(n + 1);
511+
ASSERT_NOT_NULL(buf);
512+
memcpy(buf, C_IFDEF_SPLIT, n + 1);
513+
buf[n - 1] = ' '; /* final newline becomes a blank */
514+
515+
CBMFileResult *r = do_extract(buf, CBM_LANG_C, "split.c");
516+
free(buf);
517+
ASSERT_NOT_NULL(r);
518+
bool flagged = r->parse_incomplete;
519+
bool has_ranges = r->error_ranges != NULL;
520+
cbm_free_result(r);
521+
if (!flagged) {
522+
FAIL("a real mid-file parse failure must still be reported when the file ends in blanks");
523+
}
524+
if (!has_ranges) {
525+
FAIL("a reported failure must still name its line range");
526+
}
527+
PASS();
528+
}
529+
530+
/* GUARD: a WIDTH-BEARING loss at EOF stays honest with a blank tail too — the
531+
* Makefile recipe really is dropped, and only zero-width nodes are excused. */
532+
TEST(width_bearing_error_at_eof_still_flagged_with_trailing_blank_issue1746) {
533+
const char *src = "all:\n\techo hi ";
534+
CBMFileResult *r = do_extract(src, CBM_LANG_MAKEFILE, "Makefile");
535+
ASSERT_NOT_NULL(r);
536+
bool flagged = r->parse_incomplete;
537+
cbm_free_result(r);
538+
if (!flagged) {
539+
FAIL("a width-bearing parse failure at EOF must still be reported when the file ends in blanks");
540+
}
541+
PASS();
542+
}
543+
473544
SUITE(parse_coverage) {
474545
RUN_TEST(c_ifdef_split_brace_sets_parse_incomplete);
475546
RUN_TEST(c_ifdef_split_brace_neighbors_still_extracted);
@@ -491,4 +562,7 @@ SUITE(parse_coverage) {
491562
RUN_TEST(width_bearing_error_at_eof_still_flagged_issue1610);
492563
RUN_TEST(perl_format_followed_by_named_sub_is_complete_issue1838);
493564
RUN_TEST(perl_malformed_source_remains_partial_issue1838);
565+
RUN_TEST(dockerfile_trailing_blank_at_eof_not_flagged_issue1746);
566+
RUN_TEST(real_error_before_eof_still_flagged_with_trailing_blank_issue1746);
567+
RUN_TEST(width_bearing_error_at_eof_still_flagged_with_trailing_blank_issue1746);
494568
}

0 commit comments

Comments
 (0)