Skip to content

Commit bd7bf51

Browse files
committed
Make full integration harness warning-clean under -Werror and enforce it in Linux CI.
Track binding reads in cgen to silence unused locals, fix string literal .data casts, regenerate example C snapshots, and replace the 13-test warning smoke with the full test_runner.sh harness.
1 parent 5c5c220 commit bd7bf51

14 files changed

Lines changed: 262 additions & 152 deletions

File tree

.cursor/skills/ion-integration-tests/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ RUNTIME_OBJ=/tmp/ion_runtime.o ./test_runner.sh
5757
CFLAGS="-Wall -Wextra -Werror" RUNTIME_OBJ=.ion_test_runtime_werror.o ./test_runner.sh
5858
```
5959

60-
Local `-Werror` over the full harness is not yet supported; CI runs `-Werror` on a curated smoke subset in `.github/workflows/ci.yml`.
60+
Local full-harness `-Werror` matches the Linux CI warning-clean job.
6161

6262
```bash
6363
CFLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer" LDFLAGS="-fsanitize=address,undefined" ./test_runner.sh

.github/workflows/ci.yml

Lines changed: 4 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -67,42 +67,15 @@ jobs:
6767
test_array_to_slice_coercion 10
6868
EOF
6969
70-
- name: Generated C warning-clean smoke (Linux)
70+
- name: Generated C warning-clean (Linux)
7171
shell: bash
7272
env:
7373
COMPILER: ${{ github.workspace }}/target/release/ion-compiler
74+
ION_BUILD: ${{ github.workspace }}/target/release/ion-build
7475
CC: gcc
7576
CFLAGS: -Wall -Wextra -Werror
76-
run: |
77-
cd tests
78-
"$CC" $CFLAGS -c ../runtime/ion_runtime.c -I. -I.. -I../runtime -o .ion_test_runtime_werror.o
79-
while IFS=' ' read -r test expected; do
80-
"$COMPILER" "$test.ion" >/dev/null
81-
"$CC" $CFLAGS "$test.c" .ion_test_runtime_werror.o -I. -I.. -I../runtime -lpthread -o "$test"
82-
set +e
83-
"./$test" >/dev/null
84-
status=$?
85-
set -e
86-
if [ "$status" -ne "$expected" ]; then
87-
echo "$test: expected exit $expected, got $status"
88-
exit 1
89-
fi
90-
rm -f "$test.c" "$test"
91-
done <<'EOF'
92-
test_basic 42
93-
test_move_basic 10
94-
test_arithmetic 30
95-
test_struct_basic 7
96-
test_box_basic 0
97-
test_break_continue 27
98-
test_defer_block 1
99-
test_ref_valid 0
100-
test_defer_basic 7
101-
test_for_string 0
102-
test_spawn_channel 0
103-
test_array_to_slice_coercion 10
104-
test_string_push_byte 79
105-
EOF
77+
RUNTIME_OBJ: .ion_test_runtime_werror.o
78+
run: cd tests && bash test_runner.sh
10679

10780
integration:
10881
permissions:

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## 2026-06
44

5-
- **Readiness hardening**: beta compatibility and runtime ABI documents, a lightweight security policy, CLI/`ion-build` multi-error type diagnostics, sanitizer CI smoke (6 tests), and generated-C `-Werror` CI smoke on a curated subset of 13 integration tests (not the full harness; most generated C is not yet warning-clean under `-Wall -Wextra -Werror`). Cgen warning-hygiene improvements (`ION_MAYBE_UNUSED`, borrow/defer silences, string `for...in` length casts), `String` runtime data as `uint8_t*`, and `CFLAGS`/`LDFLAGS` support in the integration harness.
5+
- **Readiness hardening**: beta compatibility and runtime ABI documents, a lightweight security policy, CLI/`ion-build` multi-error type diagnostics, sanitizer CI smoke (6 tests), and full integration harness `-Wall -Wextra -Werror` on Linux CI. Cgen warning-hygiene improvements (`ION_MAYBE_UNUSED`, binding usage tracking, borrow/defer silences, string literal `.data`/`uint8_t*` casts, string `for...in` length casts), `String` runtime data as `uint8_t*`, and `CFLAGS`/`LDFLAGS` support in the integration harness.
66
- **Language**: `for` iteration, `match` guards, `else if`, `break`/`continue`, `loop {}`, `+=`, hex/bin literals, function types `fn(T) -> R`, tuple literals and destructuring. Capture-free fn literals (`fn(T) -> R` lowered to static C function pointers; `ClosureCapture` for outer bindings).
77
- **Stdlib & runtime**: `fmt.ion`, `Result<T, E>`, `fs.read_to_string`, `String::push_byte`.
88
- **Compiler**: scope-drop codegen, `pthread` spawn, slice bounds checks, array-to-slice coercion, struct/enum field drops, `String` equality, module function name mangling, lasting-borrow rules (ION_SPEC 5.3), field-path borrow exclusivity, move/copy tracking fixes, generic monomorphization, generated C file banner (repo-relative source labels via `portable_source_label`, GNU C note, merged stdlib note, multi-file provenance, comment-safe path escaping).

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ cargo install --path . --bin ion-compiler --bin ion-build
2020

2121
`ion-lsp` is built with the release command above; point the editor extension at `target/release/ion-lsp` (see README IDE Support).
2222

23-
CI (`.github/workflows/ci.yml`) builds all three binaries on Linux, runs integration tests, ASan/UBSan smoke on generated C, and a `-Wall -Wextra -Werror` smoke subset on Linux (full-suite warning-clean builds are not yet enforced). The Windows job builds `ion-compiler` and `ion-build` only. Build `ion-lsp` locally for IDE work.
23+
CI (`.github/workflows/ci.yml`) builds all three binaries on Linux, runs integration tests, ASan/UBSan smoke on generated C, and the full integration harness under `-Wall -Wextra -Werror` on Linux. The Windows job builds `ion-compiler` and `ion-build` only. Build `ion-lsp` locally for IDE work.
2424

2525
## Test
2626

examples/access_log.c

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ int scan_logs(ion_sender_t ION_MAYBE_UNUSED code_tx, int ION_MAYBE_UNUSED expect
186186
__for_i_1660 = (__for_i_1660 + 1);
187187
}
188188
ret_val = auth_failures;
189+
if (code_tx.channel) { ion_channel_handle_drop(code_tx.channel); }
189190
goto epilogue;
190191
epilogue:
191192
return ret_val;
@@ -221,20 +222,19 @@ int main(void) {
221222
int server_errors = ({ int tmp; ion_channel_recv(&done_rx_mut, &tmp); tmp; });
222223
if (server_errors != 1) {
223224
ret_val = 1;
225+
(void)auth_failures;
224226
if (done_rx_mut.channel) { ion_channel_handle_drop(done_rx_mut.channel); }
225227
goto epilogue;
226228
}
227229
if (auth_failures != 1) {
228230
ret_val = 2;
229-
if (done_rx_mut.channel) { ion_channel_handle_drop(done_rx_mut.channel); }
230231
goto epilogue;
231232
}
232233
ion_string_t* server_line = fmt_int_to_string(server_errors);
233234
io_println(server_line);
234235
ion_string_t* auth_line = fmt_int_to_string(auth_failures);
235236
io_println(auth_line);
236237
ret_val = 0;
237-
if (done_rx_mut.channel) { ion_channel_handle_drop(done_rx_mut.channel); }
238238
goto epilogue;
239239
epilogue:
240240
return ret_val;
@@ -244,9 +244,11 @@ void io_print(ion_string_t* ION_MAYBE_UNUSED s) {
244244
{
245245
int _result = write(1, s->data, (int)s->len);
246246
(void)_result;
247+
(void)_result;
247248
}
248249
goto epilogue;
249250
epilogue:
251+
if (s) { ion_string_free(s); }
250252
return;
251253
}
252254

@@ -256,19 +258,24 @@ void io_println(ion_string_t* ION_MAYBE_UNUSED s) {
256258
(void)_result;
257259
int _newline = write(1, (uint8_t*)"\n", 1);
258260
(void)_newline;
261+
(void)_newline;
262+
(void)_result;
259263
}
260264
goto epilogue;
261265
epilogue:
266+
if (s) { ion_string_free(s); }
262267
return;
263268
}
264269

265270
void io_print_str(uint8_t* ION_MAYBE_UNUSED s, int ION_MAYBE_UNUSED len) {
266271
if (len < 0) {
267-
goto epilogue;
272+
(void)s;
273+
goto epilogue;
268274
}
269275
{
270276
int _result = write(1, s, len);
271277
(void)_result;
278+
(void)_result;
272279
}
273280
goto epilogue;
274281
epilogue:
@@ -284,15 +291,20 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
284291
{
285292
int _result = write(1, (uint8_t*)"0", 1);
286293
(void)_result;
294+
(void)_result;
287295
}
288-
goto epilogue;
296+
(void)negative;
297+
(void)len;
298+
(void)buf;
299+
goto epilogue;
289300
}
290301
if (value < 0) {
291302
negative = 1;
292303
if (value == (-2147483648)) {
293304
{
294305
int _result = write(1, (uint8_t*)"-2147483648", 11);
295306
(void)_result;
307+
(void)_result;
296308
}
297309
goto epilogue;
298310
}
@@ -315,11 +327,13 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
315327
{
316328
int _minus = write(1, (uint8_t*)"-", 1);
317329
(void)_minus;
330+
(void)_minus;
318331
}
319332
}
320333
{
321334
int _result = write(1, &buf[0], len);
322335
(void)_result;
336+
(void)_result;
323337
}
324338
goto epilogue;
325339
epilogue:
@@ -338,6 +352,7 @@ void fmt_println_int(int ION_MAYBE_UNUSED n) {
338352
{
339353
int _newline = write(1, (uint8_t*)"\n", 1);
340354
(void)_newline;
355+
(void)_newline;
341356
}
342357
goto epilogue;
343358
epilogue:
@@ -353,14 +368,17 @@ ion_string_t* fmt_int_to_string(int ION_MAYBE_UNUSED n) {
353368
ion_string_t* result = ion_string_new();
354369
if (value == 0) {
355370
ret_val = ion_string_from_literal("0", 1);
371+
(void)result;
372+
(void)negative;
373+
(void)len;
374+
(void)buf;
356375
if (result) { ion_string_free(result); }
357376
goto epilogue;
358377
}
359378
if (value < 0) {
360379
negative = 1;
361380
if (value == (-2147483648)) {
362381
ret_val = ion_string_from_literal("-2147483648", 11);
363-
if (result) { ion_string_free(result); }
364382
goto epilogue;
365383
}
366384
value = (0 - value);

examples/channel_worker.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ int main(void) {
6565
goto epilogue;
6666
}
6767
ret_val = 0;
68-
if (done_rx_mut.channel) { ion_channel_handle_drop(done_rx_mut.channel); }
69-
if (job_tx.channel) { ion_channel_handle_drop(job_tx.channel); }
7068
goto epilogue;
7169
epilogue:
7270
return ret_val;

examples/hello_world.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ int main(void) {
2121
{
2222
int _result = write(1, (uint8_t*)"Hello, World!\n", 14);
2323
(void)_result;
24+
(void)_result;
2425
}
2526
ret_val = 0;
2627
goto epilogue;

examples/hello_world_safe.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ void io_print(ion_string_t* ION_MAYBE_UNUSED s) {
3333
{
3434
int _result = write(1, s->data, (int)s->len);
3535
(void)_result;
36+
(void)_result;
3637
}
3738
goto epilogue;
3839
epilogue:
40+
if (s) { ion_string_free(s); }
3941
return;
4042
}
4143

@@ -45,19 +47,24 @@ void io_println(ion_string_t* ION_MAYBE_UNUSED s) {
4547
(void)_result;
4648
int _newline = write(1, (uint8_t*)"\n", 1);
4749
(void)_newline;
50+
(void)_newline;
51+
(void)_result;
4852
}
4953
goto epilogue;
5054
epilogue:
55+
if (s) { ion_string_free(s); }
5156
return;
5257
}
5358

5459
void io_print_str(uint8_t* ION_MAYBE_UNUSED s, int ION_MAYBE_UNUSED len) {
5560
if (len < 0) {
56-
goto epilogue;
61+
(void)s;
62+
goto epilogue;
5763
}
5864
{
5965
int _result = write(1, s, len);
6066
(void)_result;
67+
(void)_result;
6168
}
6269
goto epilogue;
6370
epilogue:
@@ -73,15 +80,20 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
7380
{
7481
int _result = write(1, (uint8_t*)"0", 1);
7582
(void)_result;
83+
(void)_result;
7684
}
77-
goto epilogue;
85+
(void)negative;
86+
(void)len;
87+
(void)buf;
88+
goto epilogue;
7889
}
7990
if (value < 0) {
8091
negative = 1;
8192
if (value == (-2147483648)) {
8293
{
8394
int _result = write(1, (uint8_t*)"-2147483648", 11);
8495
(void)_result;
96+
(void)_result;
8597
}
8698
goto epilogue;
8799
}
@@ -104,11 +116,13 @@ void io_print_int(int ION_MAYBE_UNUSED n) {
104116
{
105117
int _minus = write(1, (uint8_t*)"-", 1);
106118
(void)_minus;
119+
(void)_minus;
107120
}
108121
}
109122
{
110123
int _result = write(1, &buf[0], len);
111124
(void)_result;
125+
(void)_result;
112126
}
113127
goto epilogue;
114128
epilogue:

examples/http_server.c

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,12 @@ int handle_client(int ION_MAYBE_UNUSED client_fd) {
7373
int _sent6 = send_sys(client_fd, (uint8_t*)"<html><head><title>Ion HTTP Server</title></head><body><h1>Hello from Ion!</h1><p>This is a simple HTTP server written in Ion.</p></body></html>", 89, 0);
7474
(void)_sent6;
7575
close(client_fd);
76+
(void)_sent6;
77+
(void)_sent5;
78+
(void)_sent4;
79+
(void)_sent3;
80+
(void)_sent2;
81+
(void)_sent1;
7682
}
7783
ret_val = 0;
7884
goto epilogue;
@@ -142,6 +148,7 @@ static void* ion_spawn_entry_0(void* arg) {
142148
free(ctx);
143149
int _result = handle_client(client_fd);
144150
(void)_result;
151+
(void)_result;
145152
goto spawn_0_epilogue;
146153
spawn_0_epilogue:
147154
return NULL;

examples/showcase.c

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,16 @@ typedef struct Vec_int {
2727
size_t elem_size;
2828
} Vec_int;
2929

30-
typedef struct tuple_ion_sender_t_ion_receiver_t {
31-
ion_sender_t f0;
32-
ion_receiver_t f1;
33-
} tuple_ion_sender_t_ion_receiver_t;
34-
3530
typedef struct tuple_int_int {
3631
int f0;
3732
int f1;
3833
} tuple_int_int;
3934

35+
typedef struct tuple_ion_sender_t_ion_receiver_t {
36+
ion_sender_t f0;
37+
ion_receiver_t f1;
38+
} tuple_ion_sender_t_ion_receiver_t;
39+
4040
typedef struct Point {
4141
int x;
4242
int y;
@@ -249,7 +249,6 @@ int for_loop_example(void) {
249249
goto epilogue;
250250
}
251251
ret_val = 0;
252-
if (__for_container_2197) { ion_vec_free((ion_vec_t*)(__for_container_2197)); }
253252
goto epilogue;
254253
epilogue:
255254
return ret_val;
@@ -383,7 +382,9 @@ int pattern_matching_example(void) {
383382
}
384383
case 1: { // Err
385384
int _c = match_val_5.data.variant_1.code;
385+
(void)_c;
386386
ret_val = 6;
387+
(void)_c;
387388
goto epilogue;
388389
break;
389390
}
@@ -403,6 +404,8 @@ int reference_example(void) {
403404
(void)_ref_y;
404405
int* _ref_x = &x;
405406
(void)_ref_x;
407+
(void)_ref_x;
408+
(void)_ref_y;
406409
}
407410
Vec_int* values = ((Vec_int*)(ion_vec_new(sizeof(int))));
408411
ion_vec_push((ion_vec_t*)(values), &((int){x}), sizeof(int));
@@ -502,8 +505,6 @@ int spawn_channel_example(void) {
502505
goto epilogue;
503506
}
504507
ret_val = 0;
505-
if (rx_back_mut.channel) { ion_channel_handle_drop(rx_back_mut.channel); }
506-
if (tx.channel) { ion_channel_handle_drop(tx.channel); }
507508
goto epilogue;
508509
epilogue:
509510
return ret_val;
@@ -514,6 +515,7 @@ int fn_literal_example(void) {
514515
int (*twice)(int) = ion_fn_lit_0;
515516
if (twice(21) != 42) {
516517
ret_val = 1;
518+
(void)twice;
517519
goto epilogue;
518520
}
519521
ret_val = 0;

0 commit comments

Comments
 (0)