Skip to content

runtime: make string builtins binary-safe (slen-aware)#18

Merged
samuelSavanovic merged 1 commit into
mainfrom
runtime/binary-safe-string-builtins
May 10, 2026
Merged

runtime: make string builtins binary-safe (slen-aware)#18
samuelSavanovic merged 1 commit into
mainfrom
runtime/binary-safe-string-builtins

Conversation

@samuelSavanovic
Copy link
Copy Markdown
Owner

Summary

Six VAL_STRING ops were using strlen/strcmp/strstr/strncmp and silently truncated at the first embedded NUL. Strings in Gem carry an explicit slen and are documented as binary-safe — these were violating that invariant.

Found via a runtime audit. Memory-safety audit on the same pass came back clean (all reported findings were false positives — stb_ds idioms, arena-reset timing, deep-copy semantics on send were all correct).

Fixes

Op File Change
gem_val_eq runtime/gem_core.c memcmp + length compare
gem_lt runtime/gem_ops.c lexicographic memcmp with length tiebreak
sort default cmp runtime/gem_builtins_collection.c same pattern as gem_lt
sqlite3_bind_text runtime/gem_builtins_sqlite.c pass v.slen instead of -1 (sqlite was calling strlen internally)
path_join runtime/gem_builtins_io.c explicit-length concat, no strcpy/strcat
str_replace runtime/gem_builtins_string.c memcmp scan instead of strstr/strncmp

Skipped input() (gem_builtins_core.c:479) — reads interactive lines via fgets, embedded NULs aren't a sensible input there.

Test plan

  • make test — 120 examples pass (was 119; new 93_binary_safe_strings.gem covers all six fixes with 12 assertions)
  • Equality past \0: "x\0y" == "x" is now false
  • Ordering past \0: "x\0y" < "x\0z" is now true
  • sort ordering past \0
  • str_replace finds patterns containing \0
  • path_join preserves bytes past \0 in either argument
  • make build clean — no compiler source changes, so no bootstrap needed

Six VAL_STRING ops were using strlen/strcmp/strstr/strncmp and silently
truncated at the first embedded NUL. Replaced with slen-aware variants:

  - gem_val_eq, gem_lt: memcmp + length compare
  - sort default cmp: same lexicographic memcmp pattern
  - sqlite3_bind_text: pass v.slen instead of -1 (sqlite was calling strlen)
  - path_join: explicit-length concat, no strcpy/strcat
  - str_replace: memcmp scan instead of strstr/strncmp

Adds examples/93_binary_safe_strings.gem covering all six.
@samuelSavanovic samuelSavanovic enabled auto-merge (squash) May 10, 2026 07:15
@samuelSavanovic samuelSavanovic merged commit 5bea430 into main May 10, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant