From 58f844a2eaec03c1cc05eb3d65fdb571b9af1982 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 06:37:57 +0700 Subject: [PATCH] test(claw): kill the 22 real sarif.t27 mutation survivors A mutation sweep of specs/claw/sarif.t27 (137 single-operator mutants) left 35 alive: 22 real, 13 equivalent. Nine new tests kill the 22 real ones. They cover: - the put_byte clip at cap - printable edges in messages and paths - a trailing `..` in a path - parse_num on an empty field and on nine digits - a reversed field - per-field line rejects in parse_line - line_end stopping at len - no leading comma before the first rule or result - balanced refusing unbalanced input Production code is unchanged. Re-sweep: 124/137 caught, and the 13 left are equivalent. sarif 24/24 in t27c and t27b. refresh, sarif_feed and sarif_pins pass. The committed gen-c output is byte-identical. The claw_ClawSarif seal is resaved; 32/32 seals verify. service.t27 was swept in the same tick: 8/8 caught. Closes #10717 Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/claw_ClawSarif.json | 22 ++--- specs/claw/sarif.t27 | 125 +++++++++++++++++++++++++++++ 2 files changed, 136 insertions(+), 11 deletions(-) diff --git a/.trinity/seals/claw_ClawSarif.json b/.trinity/seals/claw_ClawSarif.json index 0b25ecc7db..c3c42186cb 100644 --- a/.trinity/seals/claw_ClawSarif.json +++ b/.trinity/seals/claw_ClawSarif.json @@ -1,33 +1,33 @@ { - "built_by": "t27c-bootstrap@0.5.2+4ab200073", + "built_by": "t27c-bootstrap@0.5.2+9448ab4be", "closure": [ { "spec": "specs/claw/text.t27", - "spec_hash": "sha256:b65da69dff231c61a6d3a48558af9e17af42e1acbe3f4763348f1f0c2f118f21" + "spec_hash": "sha256:968755ec1917f766eb2cdd0ab4c0c48aef028a0e7d69e191911695e925b286ad" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:f09132016a8a74445e9e2ee835456cc5e7b99790cf78ab34c3c4ef9a4198bc3b", - "gen_hash_rust": "sha256:74aa0e64a79fa33f32fa4dec318f54a6e01cb9fe75cd096ccf2a75246f873ef0", - "gen_hash_verilog": "sha256:af953a3d72c805414462fa9fec8bb4d32da5a549263a9e45e3e6e67ff8686365", - "gen_hash_zig": "sha256:6d88653c9a12ace10d6cf9c67b6a4792244e2c4fded93c89a4d06e8cc39b67d4", + "gen_hash_c": "sha256:f3e63a8026175da4f1087b71baf47925538719544e9afaa94f66a62b3a7ba602", + "gen_hash_rust": "sha256:a56d6add49bc5caeb85b0b8423cc9e58670845923a7ae2a9a77d91915f969fb8", + "gen_hash_verilog": "sha256:9d860df423d532c5efb6af07495dd5e04151e69bcc1c885e3157968de311be58", + "gen_hash_zig": "sha256:18db9ea8b52bd2c4f5f3e880653aba6ccfdbea6639fb5f5bc1d3b9f700cd8252", "module": "ClawSarif", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T09:41:16Z", + "sealed_at": "2026-10-18T23:37:02Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:22258cfd518c5b27ad46db8bf5c507d97e783bd79073f83ff26b20549c927db8", + "spec_hash": "sha256:f9c08d09fafe8c0e5665c3b5b803949b8c7f63432a66c3f1846f6731a15ffbdc", "spec_path": "specs/claw/sarif.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 15, - "total": 15, + "passed": 24, + "total": 24, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/specs/claw/sarif.t27 b/specs/claw/sarif.t27 index ca5406e4f6..869434bba7 100644 --- a/specs/claw/sarif.t27 +++ b/specs/claw/sarif.t27 @@ -688,3 +688,128 @@ test every_rule_row_is_filled { assert RULE_TEXT_LENS[i] > 0; } } + +// The tests below were written against a mutation sweep of this file (#10717): each one kills +// a single-operator mutant the tests above let through. + +test a_clipped_document_writes_nothing_at_or_past_cap { + // put_byte's clip is `w < cap`. With `<=` the byte at cap is written. The buffer is longer + // than cap and zeroed, so a stray write past cap shows. + var t : [256]u8 = undefined; + const w : usize = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1\tboom\n", 39); + var out : [200]u8 = undefined; + for i in 0..200 { out[i] = 0; } + const n : usize = sarif_render(&t, w, DEFAULT_MAX_RESULTS, &out, 128); + assert n > 128; + assert out[127] != 0; + var stray : u32 = 0; + for i in 128..200 { if (out[i] != 0) { stray += 1; } } + assert stray == 0; + // Which writer lands on byte cap depends on the document's layout, so pin put_byte itself: + // a write at w == cap advances the cursor and stores nothing. + var small : [4]u8 = undefined; + for i in 0..4 { small[i] = 0; } + assert put_byte(&small, 3, 3, 'x') == 4; + assert small[3] == 0; + assert put_byte(&small, 3, 2, 'y') == 3; + assert small[2] == 'y'; +} + +test the_printable_edges_survive_in_messages_and_paths { + // `~` (126) and `!` (33) are the last and first printable bytes after space: a message keeps + // them, and a path may hold them. + var t : [256]u8 = undefined; + const w : usize = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1\ta~b\n", 38); + var out : [4096]u8 = undefined; + const n : usize = sarif_render(&t, w, DEFAULT_MAX_RESULTS, &out, 4096); + assert buf_has(&out, n, "\"text\":\"a~b\"}", 13) == true; + var b : [64]u8 = undefined; + const p : usize = put_str(&b, 64, 0, "specs/a!b~c.t27", 15); + assert path_ok(&b, 0, p) == true; +} + +test a_trailing_dot_dot_is_refused_and_a_trailing_dot_is_not { + var b : [64]u8 = undefined; + const p : usize = put_str(&b, 64, 0, "specs/a..", 9); + // `..` as the last two bytes is still `..`. + assert path_ok(&b, 0, p) == false; + // The field ends at the first dot; the dot after it is outside the field and is not read. + assert path_ok(&b, 0, p - 1) == true; +} + +test parse_num_bounds_empty_and_nine_digits { + var b : [16]u8 = undefined; + var p : usize = put_str(&b, 16, 0, "123456789", 9); + assert parse_num(&b, 0, p) == 123456789; + assert parse_num(&b, 3, 3) == -1; + p = put_str(&b, 16, 0, "1234567890", 10); + assert parse_num(&b, 0, p) == -1; +} + +test a_reversed_field_is_no_severity { + var b : [16]u8 = undefined; + const p : usize = put_str(&b, 16, 0, "error", 5); + assert p == 5; + // e < s is no field at all, never the empty word and never a match. + assert sev_of(&b, 5, 3) == SEV_NONE; +} + +test each_field_check_rejects_its_own_line { + // One test per field: a line that fails only that field's check is invalid. + var t : [256]u8 = undefined; + // missing the message field + var w : usize = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1", 33); + assert sarif_valid(&t, w) == 0; + assert sarif_invalid(&t, w) == 1; + // unknown rule + w = put_str(&t, 256, 0, "error\tno-such-rule\tspecs/a.t27\t3\t1\tm", 36); + assert sarif_valid(&t, w) == 0; + // path outside the repository + w = put_str(&t, 256, 0, "error\tparse-error\t../a.t27\t3\t1\tm", 32); + assert sarif_valid(&t, w) == 0; + // column not a number + w = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\tx\tm", 35); + assert sarif_valid(&t, w) == 0; + // empty message + w = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1\t", 34); + assert sarif_valid(&t, w) == 0; + assert sarif_invalid(&t, w) == 1; +} + +test a_line_ends_at_len_not_at_the_next_newline { + // The buffer holds one byte past len. A line reader that looked at text[len] would take it. + var t : [256]u8 = undefined; + const w : usize = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1\tboomX", 39); + var out : [4096]u8 = undefined; + const n : usize = sarif_render(&t, w - 1, DEFAULT_MAX_RESULTS, &out, 4096); + assert buf_has(&out, n, "\"text\":\"boom\"}", 14) == true; + assert buf_has(&out, n, "boomX", 5) == false; +} + +test the_first_rule_and_the_first_result_have_no_comma_before_them { + var t : [256]u8 = undefined; + const w : usize = put_str(&t, 256, 0, "error\tparse-error\tspecs/a.t27\t3\t1\tboom\n", 39); + var out : [4096]u8 = undefined; + const n : usize = sarif_render(&t, w, DEFAULT_MAX_RESULTS, &out, 4096); + assert buf_has(&out, n, "\"rules\":[{\"id\":\"T27C001\"", 24) == true; + assert buf_has(&out, n, "\"results\":[{\"ruleId\"", 20) == true; + assert buf_has(&out, n, "[,", 2) == false; +} + +test balanced_refuses_what_is_not_closed { + // The structural check every render test leans on had only ever been shown balanced input. + var b : [8]u8 = undefined; + var p : usize = put_str(&b, 8, 0, "{}", 2); + assert balanced(&b, p) == true; + // nothing written + assert balanced(&b, 0) == false; + // opened, never closed + p = put_str(&b, 8, 0, "{", 1); + assert balanced(&b, p) == false; + // closed before it was opened + p = put_str(&b, 8, 0, "}{", 2); + assert balanced(&b, p) == false; + // a brace inside a string does not count + p = put_str(&b, 8, 0, "{\"}\"}", 5); + assert balanced(&b, p) == true; +}