Skip to content

optimize JIT and web response paths - #44

Open
theMackabu wants to merge 40 commits into
masterfrom
perf/silver-jit-elysia-parity
Open

optimize JIT and web response paths#44
theMackabu wants to merge 40 commits into
masterfrom
perf/silver-jit-elysia-parity

Conversation

@theMackabu

@theMackabu theMackabu commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added WebCrypto key generation and export for AES-GCM and HMAC.
    • Expanded Buffer encoding support, including UTF-8, Latin-1, ASCII, UCS-2, hex, and Base64.
    • Added optional garbage-collection statistics reporting.
    • Improved Headers and Response handling, including byte-preserving values and lazy header creation.
  • Bug Fixes
    • Corrected Unicode regular-expression indices and lastIndex behavior.
    • Improved path resolution, constructor behavior, request properties, and buffer lifecycle handling.
  • Performance
    • Improved object, property, string, regex, and server execution performance.
    • Added broader JIT optimization coverage for property access, constructors, imports, and string methods.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 79645f69-13d2-4e79-ad1b-6de1c87f3e85

📥 Commits

Reviewing files that changed from the base of the PR and between 149fcba and af696a8.

📒 Files selected for processing (10)
  • examples/bench-v8/score.json
  • include/internal.h
  • include/modules/headers.h
  • include/silver/engine.h
  • include/utf8.h
  • src/ant.c
  • src/modules/buffer.c
  • src/modules/headers.c
  • src/modules/response.c
  • tests/harness/manifest.js
💤 Files with no reviewable changes (1)
  • include/modules/headers.h
🚧 Files skipped from review as they are similar to previous changes (8)
  • examples/bench-v8/score.json
  • include/internal.h
  • include/utf8.h
  • include/silver/engine.h
  • src/modules/buffer.c
  • tests/harness/manifest.js
  • src/modules/response.c
  • src/ant.c

📝 Walkthrough

Walkthrough

The pull request updates runtime allocation, Web APIs, UTF handling, GC policy, Silver bytecode and JIT execution. It adds framework examples, benchmark workloads, harness support, regression tests, and benchmark documentation.

Changes

Runtime, Web APIs, and encoding

Layer / File(s) Summary
Runtime allocation and object metadata
include/ant.h, include/internal.h, include/object.h, include/arena.h, src/ant.c, src/gc/objects.c
Adds native-data allocation, sidecar-backed exotic operations, shape-aware object creation, bounded property interning, cached strings, iterator state, and related GC cleanup.
Headers, responses, buffers, regex, crypto, and paths
include/modules/*, include/utf8.h, src/modules/*, src/utf8.c
Adds native header storage, lazy response headers, encoding-aware buffers, UTF-8 validation, UTF-16 regex positions, WebCrypto key generation/export, and path resolution changes.

Silver inline caches and JIT

Layer / File(s) Summary
IC contracts and opcode flow
include/silver/*, src/silver/compiler.c, src/silver/engine.c, src/silver/ops/*
Adds wider IC operands, string intrinsic opcodes, receiver-aware property access, IC-assisted imports, constructor prototype lookup, and native-constructor handling.
JIT fast paths and object-site tracking
src/silver/glue.c, src/silver/swarm.c
Adds cached accessor calls, IC field writes, shape transitions, write barriers, string builtin routing, object-site metadata, and stack metadata propagation.

GC telemetry and validation

Layer / File(s) Summary
GC policy and telemetry
include/gc/*, include/gc.h, src/gc/*, src/pool.c, src/main.c
Adds opt-in GC statistics, policy snapshots, cause tracking, remembered-set metrics, revised adaptive thresholds, and the gc:stats debug option.
Examples, benchmarks, and harnesses
examples/*, tests/bench_*, tests/harness/*, docs/exec-plans/*
Adds Elysia and Hono examples, benchmark scripts, dependency preflight, stricter target checks, snapshot normalization, and benchmark protocols.
Regression and specification coverage
tests/test_*, examples/spec/*
Adds coverage for JIT accessors, globals, constructors, shapes, strings, buffers, headers, regex positions, WebCrypto, path resolution, GC barriers, and evaluated scopes.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the pull request's primary JIT and web response performance changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/silver-jit-elysia-parity

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (2)
src/modules/response.c (1)

85-86: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider using js_native_data_alloc in data_dup() for consistency with data_new().

data_new() uses js_native_data_alloc() (arena-backed) while data_dup() uses calloc(). Both are correctly freed by js_native_data_free(), but using calloc bypasses the arena optimization for cloned responses. If arena allocation is intended for all response_data_t, data_dup() should use js_native_data_alloc as well.

♻️ Proposed refactor
 static response_data_t *data_dup(const response_data_t *src) {
-  response_data_t *d = calloc(1, sizeof(response_data_t));
+  response_data_t *d = js_native_data_alloc(rt->js, sizeof(response_data_t));
   if (!d) return NULL;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/response.c` around lines 85 - 86, Update data_dup() to allocate
response_data_t with js_native_data_alloc(), matching data_new(), instead of
calloc(); preserve the existing initialization and js_native_data_free() cleanup
behavior.
tests/test_response_constructor_fast_path.cjs (1)

65-76: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Consider guarding Object.prototype cleanup with try/finally.

If the Response constructor throws between setting and deleting Object.prototype.status/statusText, the prototype remains polluted for subsequent tests. A try/finally block would ensure cleanup always runs.

🛡️ Proposed fix
 Object.prototype.status = 206;
 Object.prototype.statusText = "Partial Content";
-const inheritedLiteralResponse = new Response(null, {
-  headers: { "content-type": "text/plain" },
-});
-delete Object.prototype.status;
-delete Object.prototype.statusText;
+let inheritedLiteralResponse;
+try {
+  inheritedLiteralResponse = new Response(null, {
+    headers: { "content-type": "text/plain" },
+  });
+} finally {
+  delete Object.prototype.status;
+  delete Object.prototype.statusText;
+}
 assert(inheritedLiteralResponse.status === 206, "literal init inherited status");
 assert(
   inheritedLiteralResponse.statusText === "Partial Content",
   "literal init inherited statusText",
 );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_response_constructor_fast_path.cjs` around lines 65 - 76, Guard
the temporary Object.prototype.status and statusText mutations in the inherited
response test with a try/finally block: create inheritedLiteralResponse and
perform its assertions in the try section, and always delete both prototype
properties in finally, even if the Response constructor or assertions throw.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/npm/elysia/logger.ts`:
- Around line 5-16: Update the logger chain to use Elysia v2 hook APIs: replace
`.request()` with `.onRequest()`, `.afterHandle('global', ...)` with
`.onAfterHandle({ as: 'global' }, ...)`, and `.error('global', ...)` with
`.onError({ as: 'global' }, ...)`. Preserve the existing method filtering,
timing logic, `ctx.path`, and custom `ctx.start` field.

In `@src/modules/headers.c`:
- Around line 900-910: Update headers_data_append_if_missing to perform a
case-insensitive existence check by replacing the strcmp comparison in its
iteration over data->head with the existing ascii_case_equal helper, preserving
the early return when the header names match regardless of input casing.
- Around line 167-174: Update is_valid_value_n to allow obs-text bytes 0x80–0xff
in header values: remove the c > 127 rejection while continuing to reject NUL,
carriage return, and newline characters.

In `@src/silver/ops/property.h`:
- Around line 553-584: sv_get_elem_ic currently interns every string key before
caching, causing unbounded intern-table growth for unique runtime keys. Remove
the unconditional intern_string usage from this IC path and use the raw key
representation for lookup/cache validation, or otherwise ensure only bounded,
eligible keys are interned; preserve the existing fallback behavior and IC
guards.
- Around line 767-781: In the inline-cache property-add path, call
js_obj_ensure_prop_capacity() before retaining and assigning
ic->guard.add.to_shape; if allocation fails, return the OOM error without
modifying ptr->shape. Only swap the shape and release old_shape after capacity
is successfully ensured, then continue with ant_object_prop_set_unchecked and
cache updates.

In `@src/silver/swarm.c`:
- Around line 1417-1444: Add GC write-barrier calls before every direct value
store in the JIT put-field paths, including the inlined store in the generated
code around the overflow handling and jit_helper_put_field_transition_inobj in
glue.c. Reuse the existing barrier API and pass the target object and val with
the same semantics as sv_put_field_ic, ensuring both in-object and
overflow-property writes update the remembered set before storing.

---

Nitpick comments:
In `@src/modules/response.c`:
- Around line 85-86: Update data_dup() to allocate response_data_t with
js_native_data_alloc(), matching data_new(), instead of calloc(); preserve the
existing initialization and js_native_data_free() cleanup behavior.

In `@tests/test_response_constructor_fast_path.cjs`:
- Around line 65-76: Guard the temporary Object.prototype.status and statusText
mutations in the inherited response test with a try/finally block: create
inheritedLiteralResponse and perform its assertions in the try section, and
always delete both prototype properties in finally, even if the Response
constructor or assertions throw.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2904c354-7ef3-4262-9af9-8bca9420f515

📥 Commits

Reviewing files that changed from the base of the PR and between 8f8451f and 7323737.

📒 Files selected for processing (55)
  • examples/npm/elysia/ant.lockb
  • examples/npm/elysia/bench-no-server.ts
  • examples/npm/elysia/bench-server.ts
  • examples/npm/elysia/bench-stages.ts
  • examples/npm/elysia/logger.ts
  • examples/npm/elysia/package.json
  • examples/npm/elysia1/ant.lockb
  • examples/npm/elysia1/bench-no-server.ts
  • examples/npm/elysia1/bench-server.ts
  • examples/npm/elysia1/index.ts
  • examples/npm/elysia1/logger.ts
  • examples/npm/elysia1/package.json
  • examples/npm/hono/bench-server.ts
  • include/ant.h
  • include/arena.h
  • include/common.h
  • include/internal.h
  • include/modules/headers.h
  • include/modules/response.h
  • include/object.h
  • include/silver/engine.h
  • include/silver/glue.h
  • include/silver/opcode.h
  • meson/pgo/profiles/ant-darwin-aarch64.profdata
  • src/ant.c
  • src/gc/objects.c
  • src/modules/builtin.c
  • src/modules/headers.c
  • src/modules/request.c
  • src/modules/response.c
  • src/silver/compiler.c
  • src/silver/engine.c
  • src/silver/glue.c
  • src/silver/ops/calls.h
  • src/silver/ops/coercion.h
  • src/silver/ops/comparison.h
  • src/silver/ops/globals.h
  • src/silver/ops/property.h
  • src/silver/swarm.c
  • tests/bench_context_construction.cjs
  • tests/bench_jit_constructor_shape.cjs
  • tests/bench_jit_import_named.mjs
  • tests/bench_jit_import_named_source.mjs
  • tests/bench_jit_object_literal.cjs
  • tests/bench_jit_string_calls.cjs
  • tests/bench_response_construction.cjs
  • tests/test_ctor_prop_feedback.cjs
  • tests/test_instanceof_ic_prototype_guard.cjs
  • tests/test_jit_accessor_ic.cjs
  • tests/test_jit_global_ic.cjs
  • tests/test_jit_object_literal_shape.cjs
  • tests/test_jit_string_call_intrinsics.cjs
  • tests/test_jit_string_proto_lookup.cjs
  • tests/test_request_cached_accessors.cjs
  • tests/test_response_constructor_fast_path.cjs

Comment thread examples/npm/elysia2/logger.ts
Comment thread src/modules/headers.c
Comment thread src/modules/headers.c
Comment thread src/silver/ops/property.h
Comment thread src/silver/ops/property.h Outdated
Comment thread src/silver/swarm.c
…sia-parity

# Conflicts:
#	include/object.h
#	meson/pgo/profiles/ant-darwin-aarch64.profdata
#	src/ant.c
#	src/silver/compiler.c
#	src/silver/engine.c
#	src/silver/ops/globals.h

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
examples/npm/elysia2/logger.ts (1)

5-16: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the v2 hook names here. .request(), .afterHandle('global', ...), and .error('global', ...) don't match Elysia v2.0.0-exp.38; use .onRequest(), .onAfterHandle({ as: 'global' }, ...), and .onError({ as: 'global' }, ...). ctx.path is valid, and ctx.start can stay as a custom field once the request hook is fixed.

♻️ Proposed fix
 export const logger = ({ methods = ['GET', 'PUT', 'POST', 'DELETE'] } = {}) =>
   new Elysia()
-    .request(ctx => {
+    .onRequest(ctx => {
       if (!methods.includes(ctx.request.method)) return;
       ctx.start = performance.now();
       console.log('<--', ctx.request.method, ctx.path);
     })
-    .afterHandle('global', ctx => {
+    .onAfterHandle({ as: 'global' }, ctx => {
       if (!methods.includes(ctx.request.method)) return;
       console.log('-->', ctx.request.method, ctx.path, ctx.set.status ?? 200, 'in', Number((performance.now() - ctx.start).toFixed(2)), 'ms');
     })
-    .error('global', ctx => {
+    .onError({ as: 'global' }, ctx => {
       if (!methods.includes(ctx.request.method)) return;
       console.log('-->', ctx.request.method, ctx.path, ctx.set.status, 'in', ctx.start ? Number((performance.now() - ctx.start).toFixed(2)) : Number.NaN, 'ms');
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/npm/elysia2/logger.ts` around lines 5 - 16, Update the Elysia hook
registrations in the logger chain: replace request with onRequest,
afterHandle('global', ...) with onAfterHandle({ as: 'global' }, ...), and
error('global', ...) with onError({ as: 'global' }, ...). Preserve the existing
method filtering, logging, ctx.path usage, and custom ctx.start timing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/harness/harness.js`:
- Around line 39-52: Update installDependencies to subscribe to child.stdout and
child.stderr streams using their data events when collecting output, rather than
listening for stdout/stderr events on the child process. Apply the same
stream-based handling in the Child implementation below, preserving the existing
output accumulation behavior.

---

Duplicate comments:
In `@examples/npm/elysia2/logger.ts`:
- Around line 5-16: Update the Elysia hook registrations in the logger chain:
replace request with onRequest, afterHandle('global', ...) with onAfterHandle({
as: 'global' }, ...), and error('global', ...) with onError({ as: 'global' },
...). Preserve the existing method filtering, logging, ctx.path usage, and
custom ctx.start timing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1648026f-80b8-4cbf-bb40-fefc66337d56

📥 Commits

Reviewing files that changed from the base of the PR and between 7323737 and bdc5d49.

📒 Files selected for processing (30)
  • examples/bench-v8/score.json
  • examples/npm/elysia2/ant.lockb
  • examples/npm/elysia2/bench-no-server.ts
  • examples/npm/elysia2/bench-server.ts
  • examples/npm/elysia2/bench-stages.ts
  • examples/npm/elysia2/index.ts
  • examples/npm/elysia2/logger.ts
  • examples/npm/elysia2/package.json
  • include/common.h
  • include/internal.h
  • include/object.h
  • include/silver/engine.h
  • include/silver/glue.h
  • include/silver/opcode.h
  • meson/pgo/profiles/ant-darwin-aarch64.profdata
  • src/ant.c
  • src/gc/objects.c
  • src/silver/compiler.c
  • src/silver/engine.c
  • src/silver/glue.c
  • src/silver/ops/calls.h
  • src/silver/ops/coercion.h
  • src/silver/ops/globals.h
  • src/silver/ops/property.h
  • src/silver/swarm.c
  • tests/harness/harness.js
  • tests/harness/manifest.js
  • tests/harness/run.js
  • tests/harness/snapshots/rolldown.txt
  • tests/test_jit_global_ic.cjs
🚧 Files skipped from review as they are similar to previous changes (12)
  • include/common.h
  • src/silver/ops/globals.h
  • src/silver/engine.c
  • include/silver/engine.h
  • include/internal.h
  • src/gc/objects.c
  • include/silver/opcode.h
  • include/silver/glue.h
  • src/silver/glue.c
  • src/silver/ops/property.h
  • src/ant.c
  • src/silver/swarm.c

Comment thread tests/harness/harness.js
Reconcile OP_GET_GLOBAL: keep the branch's global-object IC fastpath and
error dispatch, adopt master's guarded self-binding specialization (#50)
after the no_err join point, and drop the unguarded known_func early-out
that #50 removed.
@theMackabu

Copy link
Copy Markdown
Owner Author

@macroscope-app review

@macroscopeapp

macroscopeapp Bot commented Jul 16, 2026

Copy link
Copy Markdown

Manual reviews triggered for commit 66e4251:

All prior checks · these links stay valid even if you push more commits.

@macroscopeapp

macroscopeapp Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review triggered and in progress. Results will be posted as check runs when complete.

Comment thread src/silver/engine.c
NEXT(3);
}

L_CALL_STRING_INDEXOF: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High silver/engine.c:1483

The L_CALL_STRING_INDEXOF and L_CALL_STRING_SUBSTRING fast paths pass call_args (a pointer into vm->stack) to js_string_indexof_call/js_string_substring_call. When call_this is a non-string object, those helpers coerce it via to_string_val, which can invoke user JavaScript; that nested execution can realloc vm->stack, invalidating the call_args pointer. The helper then reads stale memory for the arguments, causing a use-after-free, wrong arguments, or a crash. Consider copying the arguments into a local array before calling the helper, or falling back to sv_vm_call for non-string receivers.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/silver/engine.c around line 1483:

The `L_CALL_STRING_INDEXOF` and `L_CALL_STRING_SUBSTRING` fast paths pass `call_args` (a pointer into `vm->stack`) to `js_string_indexof_call`/`js_string_substring_call`. When `call_this` is a non-string object, those helpers coerce it via `to_string_val`, which can invoke user JavaScript; that nested execution can `realloc` `vm->stack`, invalidating the `call_args` pointer. The helper then reads stale memory for the arguments, causing a use-after-free, wrong arguments, or a crash. Consider copying the arguments into a local array before calling the helper, or falling back to `sv_vm_call` for non-string receivers.

Comment thread include/internal.h Outdated
Comment thread src/modules/headers.c Outdated
Comment thread src/silver/swarm.c
@macroscopeapp

macroscopeapp Bot commented Jul 16, 2026

Copy link
Copy Markdown

Approvability

Verdict: Needs human review

4 blocking correctness issues found. This PR introduces substantial JIT optimizations and web API performance improvements with significant complexity. Unresolved high-severity review comments identify potential memory safety issues (use-after-free in string call fast paths) and a flag collision that could break constructor behavior. Human review is needed to verify correctness of these low-level changes.

You can customize Macroscope's approvability policy. Learn more.

unregister_buffer scanned the global registry linearly per freed buffer,
making GC sweeps of dead buffers quadratic in live-buffer population;
an intrusive registry_slot (index+1, 0 = never registered, verified
before removal) makes it O(1) and keeps foreign wasm/wasi/clone buffers
safe. Buffer.write now honors Node's (string[, offset[, length]][,
encoding]) signature via per-encoding bounded encoders; byteLength is
encoding-aware; utf8 writes never split a multibyte sequence.
Four unbraced HASH_ITER cleanups freed one entry after the loop (on
NULL) instead of each entry, leaking a json_key_entry_t per object key
per parse. path.relative treated '.' as a literal segment; both
arguments now resolve against the working directory first, matching
Node's relative(resolve(from), resolve(to)).
AES-GCM (128/192/256) and HMAC (block-size default or explicit bit
length) generation, raw export gated on a native extractable flag, and
extractable/usages recorded on CryptoKey objects. Fixes vite dev
crashing on @vitejs/plugin-rsc's startup key generation.
ANT_DEBUG=gc:stats moves to src/gc/stats.c behind thin note hooks with
a policy snapshot exporter. GC_POOL_PRESSURE_FLOOR drops 8MiB->1MiB
(the floor only governs sub-MiB live pools, where majors cost ~2ms;
at 8MiB a 1.4MiB-live pool grew ~7x between majors and slowed the
mutator). Nursery growth cap 8x->2x per measurement; main-stack
conservative scan is gated on a running coroutine; pool-pressure
majors are cause-tagged. Adds gc_get_string_epoch (major-only) for
string-pointer-keyed caches.
match.index, lastIndex (both directions), d-flag indices, replacer
offsets, search results and split boundaries were pcre2 byte offsets;
they now convert at the pcre2 boundary with an ASCII fast path and
ascending-order conversions for the forward-resuming scan cache.
Interpreted matches skip re-validating subjects already validated this
major-GC string epoch (pcre2_jit_match never validates and never arms
the cache); empty-match loops advance whole characters, never handing
pcre2 a mid-character offset. Symbol.split gains a guarded
scan-and-slice fast path over the cached compiled pattern (~7000x on
50KB subjects), exec lookups walk the prototype so overridden exec
dispatches per spec and test() reaches its truthy-only branch, and
no-JIT sv_tfb_* stubs restore -Djit=false.
…sia-parity

json.c takes master's HASH_ITER fix (same bug, fixed independently on
both sides). String.indexOf composes the branch ascii fast path with
master's cached str_utf16_len clamp and memchr scan. OP_GET_GLOBAL
adopts master's dedicated global-read IC fastpath wholesale. The
branch's 16-param get-field IC emitter learns imp_accessor == NULL
(accessor hits bail to slow) so master's inline-body call site works
without accessor plumbing; func->filename follows master's move to
func->debug->filename.
UTF-8 validity is a property of immutable string content, so it now
lives in the flat-string meta word (2 spare bits beside the ascii
state, ascii accessor masked accordingly) with a strict RFC 3629
validator that classifies WTF-8 lone-surrogate strings as invalid.
regex_subject_match_options collapses to a flag check; the TLS
validated-subject cache, mark-after-interpreted-match choreography,
and the major-GC string epoch it was keyed on are all deleted. Valid
subjects skip pcre2 UTF re-validation permanently instead of
per-epoch; invalid ones never do.
Comment thread src/modules/path.c
Comment thread src/modules/regex.c
// pcre2_jit_match never validates UTF; it must not arm the cache
rc = pcre2_jit_match(compiled.code, (PCRE2_SPTR)str_ptr, str_len, (PCRE2_SIZE)q, match_options, compiled.match_data, match_ctx);
} else rc = pcre2_match(compiled.code, (PCRE2_SPTR)str_ptr, str_len, (PCRE2_SIZE)q, match_options, compiled.match_data, match_ctx);
if (rc < 0) break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium modules/regex.c:2289

regexp_split_fast calls pcre2_match/pcre2_jit_match directly but never calls update_regexp_statics, so an eligible fast-path split like 'a,b'.split(/(,)/) leaves RegExp.$1, RegExp.lastMatch, RegExp.input, and the other legacy statics stale instead of reflecting the separator match. The normal split loop updates these via regexp_exec_abstract, so the fast path diverges in user-visible behavior when those statics are read after split. Consider calling update_regexp_statics with the match data after each successful match in regexp_split_fast.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/modules/regex.c around line 2289:

`regexp_split_fast` calls `pcre2_match`/`pcre2_jit_match` directly but never calls `update_regexp_statics`, so an eligible fast-path split like `'a,b'.split(/(,)/)` leaves `RegExp.$1`, `RegExp.lastMatch`, `RegExp.input`, and the other legacy statics stale instead of reflecting the separator match. The normal split loop updates these via `regexp_exec_abstract`, so the fast path diverges in user-visible behavior when those statics are read after `split`. Consider calling `update_regexp_statics` with the match data after each successful match in `regexp_split_fast`.

Comment thread src/modules/crypto.c
Comment thread src/modules/crypto.c Outdated
Comment thread src/modules/path.c Outdated
Comment thread src/modules/regex.c Outdated
Comment thread src/silver/swarm.c
Comment thread src/modules/buffer.c
Comment thread src/modules/regex.c

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ant.c (1)

12473-12476: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Coerce String.prototype.indexOf search arguments before string comparison.

indexOf’s search argument is subject to ToString, so "".indexOf() and "".indexOf(1) should search "undefined" and "1". The current indexOf fast path returns -1 for omitted or non-string searches (undefined, null, numbers) instead of using the string-coerced search value.

Proposed fix
-  if (nargs == 0) return tov(-1);
-
-  ant_value_t search = args[0];
-  if (vtype(search) != T_STR) return tov(-1);
+  ant_value_t search = nargs == 0
+    ? js_mkstr(js, "undefined", 9)
+    : js_tostring_val(js, args[0]);
+  if (is_err(search)) return search;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ant.c` around lines 12473 - 12476, Update the String.prototype.indexOf
implementation around the nargs and vtype(search) checks to apply ToString
coercion to the first search argument, including omitted arguments as undefined,
before performing string comparison. Preserve the existing indexOf behavior and
return values after coercion, so null, numbers, and other values are searched
using their string representations.
🧹 Nitpick comments (1)
meson/pgo/profiles/ant-darwin-aarch64.profdata (1)

1-3448: 🧹 Nitpick | 🔵 Trivial

Binary PGO profile — nothing to line-review; flagging repo-hygiene considerations instead.

This file is a binary LLVM indexed-profile artifact (PGO data for ant on darwin-aarch64), not source code — the "lines" here are arbitrary byte-chunk boundaries injected by the diff tool, so there's no logic to inspect. A few process-level points worth confirming for this class of change:

  • Regenerated PGO profiles are opaque, full-file binary diffs every time; consider documenting (or automating via CI) how/when this file is regenerated so reviewers/maintainers know it's reproducible from a known compiler + source revision rather than hand-edited.
  • PGO profiles are sensitive to compiler/toolchain version skew — worth confirming the build fails loudly (rather than silently ignoring or degrading) if ant-darwin-aarch64.profdata doesn't match the Clang/LLVM version used to consume it in meson/pgo.
  • As more platforms are supported, per-arch binary blobs like this will accumulate in the main tree; consider whether these belong in Git LFS or a release-asset/artifact store instead of the primary source repo.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@meson/pgo/profiles/ant-darwin-aarch64.profdata` around lines 1 - 3448,
Document how and when ant-darwin-aarch64.profdata is regenerated, including the
expected compiler and source revision, and ensure the meson/pgo consumption path
validates toolchain/profile compatibility and fails clearly on mismatch. Also
establish the repository policy for storing growing per-architecture binary PGO
profiles, including whether Git LFS or an artifact store is required.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/exec-plans/active/gc-and-server-benchmark-protocol.md`:
- Around line 151-158: Update both termination blocks in
docs/exec-plans/active/gc-and-server-benchmark-protocol.md (lines 151-158 and
195-203) so they terminate the Ant child or dedicated benchmark process group
rather than only the /usr/bin/time wrapper; capture the relevant child/group
identifier, signal it with the existing TERM/KILL sequence, then wait for the
wrapper without orphaning the runtime.

In `@src/modules/buffer.c`:
- Around line 3307-3308: Update js_buffer_write to validate or coerce args[0]
before calling js_getstr, ensuring buffer_encode_into never receives a NULL
string. Use js_tostring_val for the required string conversion, or reject
non-string values consistently with js_buffer_copy, while preserving normal
string writes.

In `@src/modules/crypto.c`:
- Around line 933-936: Update crypto_subtle_generate_key_impl() so both AES and
HMAC generation branches call OPENSSL_cleanse(buf, len) after
crypto_make_key_object() has consumed the random bytes and before returning.
Preserve the existing key-object result and error handling while ensuring each
generated buffer is cleared on the return path.

In `@tests/test_buffer_registry_slots.cjs`:
- Around line 68-73: Update the drift assertion in the external buffer
accounting test to require the absolute value of drift to remain below 4 * 1024
* 1024, rejecting both positive overcounting and negative underflow while
preserving the existing diagnostic message.

In `@tests/test_regex_utf16_positions.cjs`:
- Around line 55-64: Update the WTF-8 global regex assertions in the test block
to explicitly verify that the first g.exec(wtf) finds the expected X match
before asserting the subsequent exec returns null. Keep the existing sticky
y.test(wtf) assertion unchanged.
- Around line 66-72: Update the astral-character expectation in the “empty-match
match loop astral” test to preserve non-Unicode RegExp behavior: /x*/g must
advance by UTF-16 code units and include the mid-surrogate position, producing
three empty matches for “😀”. Keep the Unicode and split assertions unchanged.

In `@tests/test_webcrypto_generate_export.cjs`:
- Around line 32-37: Add a nonzero-byte assertion for the first exported key
buffer `raw`, alongside the existing checks for `raw2`, before comparing the two
generated keys. Preserve the current `raw2` and key-difference assertions.

---

Outside diff comments:
In `@src/ant.c`:
- Around line 12473-12476: Update the String.prototype.indexOf implementation
around the nargs and vtype(search) checks to apply ToString coercion to the
first search argument, including omitted arguments as undefined, before
performing string comparison. Preserve the existing indexOf behavior and return
values after coercion, so null, numbers, and other values are searched using
their string representations.

---

Nitpick comments:
In `@meson/pgo/profiles/ant-darwin-aarch64.profdata`:
- Around line 1-3448: Document how and when ant-darwin-aarch64.profdata is
regenerated, including the expected compiler and source revision, and ensure the
meson/pgo consumption path validates toolchain/profile compatibility and fails
clearly on mismatch. Also establish the repository policy for storing growing
per-architecture binary PGO profiles, including whether Git LFS or an artifact
store is required.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c2c8cad-f4bc-4a15-978e-5c91af0246ef

📥 Commits

Reviewing files that changed from the base of the PR and between bd4718a and 4868012.

📒 Files selected for processing (41)
  • docs/exec-plans/active/README.md
  • docs/exec-plans/active/gc-and-server-benchmark-protocol.md
  • docs/exec-plans/completed/README.md
  • docs/exec-plans/completed/jit-put-field-gc-performance.md
  • include/gc.h
  • include/gc/stats.h
  • include/internal.h
  • include/modules/buffer.h
  • include/modules/regex.h
  • include/silver/engine.h
  • include/silver/glue.h
  • include/silver/opcode.h
  • include/utf8.h
  • meson/pgo/profiles/ant-darwin-aarch64.profdata
  • src/ant.c
  • src/gc/gc.c
  • src/gc/objects.c
  • src/gc/stats.c
  • src/main.c
  • src/modules/buffer.c
  • src/modules/builtin.c
  • src/modules/crypto.c
  • src/modules/fetch.c
  • src/modules/path.c
  • src/modules/regex.c
  • src/modules/request.c
  • src/modules/response.c
  • src/pool.c
  • src/silver/compiler.c
  • src/silver/engine.c
  • src/silver/glue.c
  • src/silver/ops/coercion.h
  • src/silver/ops/globals.h
  • src/silver/ops/property.h
  • src/silver/swarm.c
  • src/utf8.c
  • tests/harness/manifest.js
  • tests/test_buffer_registry_slots.cjs
  • tests/test_path_relative_resolve.cjs
  • tests/test_regex_utf16_positions.cjs
  • tests/test_webcrypto_generate_export.cjs
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/modules/fetch.c
  • src/modules/builtin.c
  • src/silver/ops/coercion.h
  • src/silver/ops/globals.h
  • include/silver/glue.h
  • src/modules/request.c
  • tests/harness/manifest.js
  • include/silver/opcode.h
  • src/silver/ops/property.h
  • src/modules/response.c
  • src/silver/glue.c
  • src/silver/swarm.c

Comment thread docs/exec-plans/active/gc-and-server-benchmark-protocol.md
Comment thread src/modules/buffer.c
Comment thread src/modules/crypto.c Outdated
Comment thread tests/test_buffer_registry_slots.cjs
Comment thread tests/test_regex_utf16_positions.cjs
Comment thread tests/test_regex_utf16_positions.cjs
Comment thread tests/test_webcrypto_generate_export.cjs
Comment thread src/modules/buffer.c
…st paths

to_string_val on an object receiver (or a proxy length trap in the
includes generic path) can run user JS that reallocates vm->stack,
leaving the helpers' args pointer stale. Read the at-most-two used
argument values into locals before any coercion; the values stay
rooted by their stack slots. Also propagate a throwing toString
instead of masking it as a non-string error.
the branch-target reset block cleared slot_type, known_func, and
has_const but left obj_site, so a stale object-literal site pointer
could survive a control-flow merge and let OP_DEFINE_FIELD update the
wrong site's shared_shape. Clear it with the rest of the speculative
per-slot state; bench_jit_object_literal measures at parity.
the interpreter's sv_put_field_ic calls regexp_note_property_write
before every store, but the JIT put-field IC fastpath stored inline,
so a hot 'obj.exec = fn' never invalidated the regex fast paths. Gate
the fastpath at compile time on a shared watched-name predicate; the
define-field path is unwatched in the interpreter too, so it stays
ungated.
@@match, matchAll, and @@replace all consulted only the unicode
property when advancing past an empty match, so /(?:)/gv stepped one
UTF-16 unit into surrogate pairs. Share a helper that reads unicode
OR unicodeSets; matches Node on astral subjects for all three.
the scan-and-slice split loop called pcre2 directly and never updated
RegExp.$1/lastMatch, diverging from the exec-based slow path. Update
statics once per successful separator match, including empty matches.
write, Buffer.from, byteLength, and toString all treated latin1 and
ascii as UTF-8 passthrough, so 'é' wrote c3 a9 instead of e9 and
round-trips broke. Encode one byte per UTF-16 code unit (unit & 0xff),
report byteLength as the unit count, and decode bytes as U+00xx with
the ascii high-bit strip, matching Node on the full surface.
@@replace passed a custom exec's raw index value to the replacer while
slicing with a separately clamped byte position, so callbacks could see
1.5, out-of-range, or uncoerced values. Apply the spec conversion:
ToIntegerOrInfinity then clamp to the subject's utf16 length.
subtle.generateKey silently defaulted extractable and keyUsages when
called with fewer than the three required arguments; reject with a
TypeError like importKey does. crypto_make_key_object stored the
caller's usages array by reference, so mutating it after creation
changed the key's usage set; copy it at creation.
list_append_raw discarded list_append_parts' failure, so
headers_data_copy could return a silently partial copy on OOM and
response cloning would drop headers; return NULL instead (data_dup
already checks). The set, copy-from, and headers-init paths propagate
the failure too. Response.redirect's ensure-headers error exit now
frees href and clears the parsed url like its sibling exits.
path_absolutize treated any nonzero root length as fully qualified, so
path.win32.relative compared C:foo and \foo unresolved. Absolutize now
mirrors resolve(): drive-relative inputs keep their drive and join the
cwd, rooted inputs are already handled by the absolute check. Also
match Node's quirk where a device-less root with no common component
returns the resolved target instead of a dot-dot walk.
every read-path lookup (elem IC, lkp, lkp_val, getter/setter walks,
lkp_proto, lookup_prop_meta, ctor-proto) interned the probed key, so
reading unique runtime keys grew the never-evicted table without bound
(200k unique probes leaked ~9.3MB). Add a find-only
intern_string_existing and use it on all read paths: a key that can hit
was interned when its property was defined, so absence from the table
proves absence from every shape. Writes still intern at creation.
zero the stack copies of fresh AES/HMAC key material once the key
object owns them; make the protocol doc's stress10 snippets kill the
ant child they warn about; assert absolute drift in the buffer
registry test; assert the WTF-8 first match explicitly instead of a
vacuous null-chain; check the first generated key's material and pin
the generateKey arity and usages-snapshot behavior.
buf.write and Buffer.from copied the runtime's WTF-8 string bytes
verbatim, so a lone surrogate produced invalid UTF-8 (ED A0 80) where
Node emits U+FFFD. Share the same-length in-place patch te_encode
already used as utf8_replace_wtf8_surrogates, gated on the memoized
validity flag; te_encode adopts the gate too, turning TextEncoder's
byte-wise copy loop into a memcpy fast path for valid strings.
test_name="${test_file##*/}"
stdout="$RESULTS/gc-$label-$test_name.out"
timing="$RESULTS/gc-$label-$test_name.time"
if [ "$test_file" = tests/test_gc_stress10.js ]; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium active/gc-and-server-benchmark-protocol.md:151

The special case for tests/test_gc_stress10.js always sleeps 20 seconds and records EXPECTED_TIMEOUT regardless of whether the process was still running at that point. If the runtime crashes or exits early with a non-zero status, the suite still reports EXPECTED_TIMEOUT instead of the real failure, masking regressions. The loop should check whether the process is still alive before the timeout and inspect wait's exit status so crashes and early exits are recorded as failures.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @docs/exec-plans/active/gc-and-server-benchmark-protocol.md around line 151:

The special case for `tests/test_gc_stress10.js` always sleeps 20 seconds and records `EXPECTED_TIMEOUT` regardless of whether the process was still running at that point. If the runtime crashes or exits early with a non-zero status, the suite still reports `EXPECTED_TIMEOUT` instead of the real failure, masking regressions. The loop should check whether the process is still alive before the timeout and inspect `wait`'s exit status so crashes and early exits are recorded as failures.

Comment thread src/modules/regex.c
// fast path: unmodified builtin regexp with default species and a
// numeric (or absent) limit — bypasses splitter construction and the
// per-position sticky probing entirely
if (regexp_can_use_internal_fast_path(js, rx) &&

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium modules/regex.c:2393

The fast path in builtin_regexp_symbol_split calls regexp_split_fast using rx's internal compiled flags instead of the observed flags_str that the spec-mandated species splitter would use. When an ordinary RegExp has an own flags property (e.g. /a/ with flags overridden to "i"), regexp_can_use_internal_fast_path still returns true because it only inspects internal slots, so the fast path is taken. Splitting "A" with such a regexp returns ["A"] instead of [] because regexp_split_fast matches with the original case-sensitive flags. Invalid overridden flag strings also bypass the constructor error that the species path would raise. Consider guarding the fast path on flags_str matching rx's internal flags, or compiling with flags_str.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @src/modules/regex.c around line 2393:

The fast path in `builtin_regexp_symbol_split` calls `regexp_split_fast` using `rx`'s internal compiled flags instead of the observed `flags_str` that the spec-mandated species splitter would use. When an ordinary `RegExp` has an own `flags` property (e.g. `/a/` with `flags` overridden to `"i"`), `regexp_can_use_internal_fast_path` still returns true because it only inspects internal slots, so the fast path is taken. Splitting `"A"` with such a regexp returns `["A"]` instead of `[]` because `regexp_split_fast` matches with the original case-sensitive flags. Invalid overridden flag strings also bypass the constructor error that the species path would raise. Consider guarding the fast path on `flags_str` matching `rx`'s internal flags, or compiling with `flags_str`.

./build/ant examples/npm/elysia2/bench-server.ts >/tmp/elysia2-build.log 2>&1 &
server_pid=$!

until curl -fsS http://127.0.0.1:3000/ >/dev/null; do sleep 0.05; done

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium active/gc-and-server-benchmark-protocol.md:261

The server readiness loop at line 261 never times out and never checks whether $server_pid is still alive, so if the server process exits during startup (e.g. the fixture fails to load or port 3000 cannot be bound) the benchmark hangs forever instead of recording a failed run. The RSC readiness loop at line 319 has the same defect. Consider adding a bounded timeout and checking kill -0 "$server_pid" each iteration so a crashed server is detected and reported.

🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file @docs/exec-plans/active/gc-and-server-benchmark-protocol.md around line 261:

The server readiness loop at line 261 never times out and never checks whether `$server_pid` is still alive, so if the server process exits during startup (e.g. the fixture fails to load or port 3000 cannot be bound) the benchmark hangs forever instead of recording a failed run. The RSC readiness loop at line 319 has the same defect. Consider adding a bounded timeout and checking `kill -0 "$server_pid"` each iteration so a crashed server is detected and reported.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/harness/manifest.js (1)

47-53: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Preserve PID scrubbing for the rolldown snapshot.

Line 52 replaces the scrub list, so variable process IDs are no longer normalized. Add the cache rule alongside scrubPid to avoid snapshot churn.

Proposed fix
-      scrub: [[/(?:\.\.\/)+\.ant\/pkg\/cache\/[0-9a-f]+/g, '<ant-cache>']]
+      scrub: [
+        ...scrubPid,
+        [/(?:\.\.\/)+\.ant\/pkg\/cache\/[0-9a-f]+/g, '<ant-cache>']
+      ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/harness/manifest.js` around lines 47 - 53, Update the rolldown snapshot
entry in the manifest so its scrub configuration preserves the existing scrubPid
rule while also applying the ant cache-path normalization regex. Extend the
scrub list rather than replacing the PID scrubbing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/ant.c`:
- Around line 12490-12500: Restore standard coercion in the shared string fast
paths: at src/ant.c lines 12490-12525, update the indexOf path to apply ToString
to search and ToIntegerOrInfinity to pos_arg before clamping; at src/ant.c lines
12556-12578, apply ToIntegerOrInfinity to both substring bounds, clamp each with
min(..., D(str_utf16_len(js, str))), then swap them as needed. Use the existing
coercion helpers and preserve the argument snapshots before to_string_val.

In `@tests/test_jit_regexp_exec_override.cjs`:
- Around line 9-10: Update putReplace() and its related test setup to override
the object’s Symbol.replace method instead of the string-keyed replace property.
Make the custom implementation return an observable result, assert that result
through String.prototype.replace(), and restore originalReplace afterward.

---

Outside diff comments:
In `@tests/harness/manifest.js`:
- Around line 47-53: Update the rolldown snapshot entry in the manifest so its
scrub configuration preserves the existing scrubPid rule while also applying the
ant cache-path normalization regex. Extend the scrub list rather than replacing
the PID scrubbing behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7741a295-1c3d-4f99-8612-45007aea7340

📥 Commits

Reviewing files that changed from the base of the PR and between ecede3b and 547d4b2.

📒 Files selected for processing (27)
  • docs/exec-plans/active/README.md
  • docs/exec-plans/active/gc-and-server-benchmark-protocol.md
  • examples/spec/buffer.js
  • examples/spec/regexp.js
  • include/internal.h
  • include/modules/regex.h
  • include/utf8.h
  • src/ant.c
  • src/modules/buffer.c
  • src/modules/crypto.c
  • src/modules/headers.c
  • src/modules/path.c
  • src/modules/regex.c
  • src/modules/response.c
  • src/modules/textcodec.c
  • src/silver/ops/property.h
  • src/silver/swarm.c
  • src/utf8.c
  • tests/harness/manifest.js
  • tests/test_buffer_registry_slots.cjs
  • tests/test_intern_table_bounded.cjs
  • tests/test_jit_define_field_join_sites.cjs
  • tests/test_jit_regexp_exec_override.cjs
  • tests/test_path_relative_resolve.cjs
  • tests/test_regex_utf16_positions.cjs
  • tests/test_string_call_stack_realloc.cjs
  • tests/test_webcrypto_generate_export.cjs
💤 Files with no reviewable changes (1)
  • docs/exec-plans/active/README.md
🚧 Files skipped from review as they are similar to previous changes (11)
  • tests/test_path_relative_resolve.cjs
  • tests/test_buffer_registry_slots.cjs
  • tests/test_webcrypto_generate_export.cjs
  • src/modules/crypto.c
  • docs/exec-plans/active/gc-and-server-benchmark-protocol.md
  • include/internal.h
  • src/modules/response.c
  • src/modules/regex.c
  • src/silver/ops/property.h
  • src/modules/headers.c
  • src/silver/swarm.c

Comment thread src/ant.c
Comment on lines +12490 to 12500
// args may point into vm->stack; to_string_val can run user JS (object
// receiver with a toString) and realloc the stack, so snapshot first
ant_value_t search = nargs > 0 ? args[0] : js_mkundef();
ant_value_t pos_arg = nargs > 1 ? args[1] : js_mkundef();

ant_value_t str = to_string_val(js, this_val);
if (is_err(str)) return str;
if (vtype(str) != T_STR) return js_mkerr(js, "indexOf called on non-string");
if (nargs == 0) return tov(-1);

ant_value_t search = args[0];
if (vtype(search) != T_STR) return tov(-1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
console.log('undefined'.indexOf());       // 0
console.log('aa'.indexOf('a', '1'));      // 1
console.log('abc'.substring('1', '2'));   // b
NODE

Repository: theMackabu/ant

Length of output: 158


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file excerpt =="
sed -n '12460,12610p' src/ant.c

echo
echo "== search for fast-path helpers / to_integer helpers =="
rg -n "ToIntegerOrInfinity|to_integer|indexOf|substring|substring_call|string_indexof" src/ant.c src -g '*.c' -g '*.h' | head -200

Repository: theMackabu/ant

Length of output: 10153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== inspect glue/engine handling around fast paths =="
sed -n '630,680p' src/silver/glue.c
sed -n '1480,1535p' src/silver/engine.c
sed -n '3275,3305p' src/silver/compiler.c

echo
echo "== inspect integer coercion implementations and string constructor helpers =="
rg -n "ant_value_t js_(|to_integer|to_number|to_string|to_bigint|to_boolean)|ToInteger|ToIntegerOrInfinity|js_str_\\{|defvalue|defnumber|defstring|defundefined|js_mknum|js_mkstr" src/ant.c src -g '*.c' | head -300
sed -n '1,260p' src/ant.c
sed -n '260,520p' src/ant.c

Repository: theMackabu/ant

Length of output: 43885


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== coercion and string methods in ant.c =="
sed -n '2560,2665p' src/ant.c
sed -n '13660,13910p' src/ant.c

echo
echo "== test harness/build files =="
git ls-files | rg '(^|/)(Makefile|CMake|meson|test|package.json|compile|run)' | head -200

echo
echo "== small behavior probe via Node for JS spec semantics =="
node - <<'NODE'
const cases = [
  [`'undefined'.indexOf()`, `'undefined'.indexOf()`],
  [`'abc'.indexOf('b', '1')`, `'abc'.indexOf('b', '1')`],
  [`'abc'.substring('1', '2')`, `'abc'.substring('1', '2')`],
  [`'abc'.substring('2', '1')`, `'abc'.substring('2', '1')`],
  [`'abc'.substring(NaN, Infinity)`, `'abc'.substring(NaN, Infinity)`],
  [`'abc'.substring(-Infinity, -Infinity)`, `'abc'.substring(-Infinity, -Infinity)`],
];
for (const [label, expr] of cases) {
  try {
    console.log(expr, '=>', eval(expr));
  } catch (e) {
    console.log(expr, 'THROWS', e && e.name + ': ' + e.message);
  }
}
NODE

Repository: theMackabu/ant

Length of output: 17339


Restore standard argument coercion in the shared string fast paths.

The fast paths correctly snapshot stack arguments before to_string_val(), but the subsequent branches skip standard coercion: indexOf rejects string/object search and non-numeric pos, while substring rejects string/bignum/boolean bounds and treats omitted/NaN as non-bounds instead of 0.

  • src/ant.c#L12490-L12525: coerce search with ToString and coerce pos_arg with ToIntegerOrInfinity before clamping.
  • src/ant.c#L12557-L12581: coerce both substring bounds with ToIntegerOrInfinity, then clamp with min(..., D(str_utf16_len(js, str))) before swapping.
📍 Affects 1 file
  • src/ant.c#L12490-L12500 (this comment)
  • src/ant.c#L12556-L12578
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/ant.c` around lines 12490 - 12500, Restore standard coercion in the
shared string fast paths: at src/ant.c lines 12490-12525, update the indexOf
path to apply ToString to search and ToIntegerOrInfinity to pos_arg before
clamping; at src/ant.c lines 12556-12578, apply ToIntegerOrInfinity to both
substring bounds, clamp each with min(..., D(str_utf16_len(js, str))), then swap
them as needed. Use the existing coercion helpers and preserve the argument
snapshots before to_string_val.

Comment on lines +9 to +10
function putExec(o, f) { o.exec = f; }
function putReplace(o, f) { o.replace = f; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
const re = /-/;
RegExp.prototype.replace = undefined;
console.log('string-key:', 'a-b'.replace(re, '+')); // still a+b

const saved = RegExp.prototype[Symbol.replace];
RegExp.prototype[Symbol.replace] = () => 'overridden';
console.log('symbol-key:', 'a-b'.replace(re, '+')); // overridden
RegExp.prototype[Symbol.replace] = saved;
NODE

Repository: theMackabu/ant

Length of output: 191


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the referenced test file around the relevant helpers and assertion.
if [ -f tests/test_jit_regexp_exec_override.cjs ]; then
  nl -ba tests/test_jit_regexp_exec_override.cjs | sed -n '1,80p'
else
  echo "tests/test_jit_regexp_exec_override.cjs not found"
  fd -a 'test_jit_regexp_exec_override.cjs' .
fi

Repository: theMackabu/ant

Length of output: 192


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "PWD=$(pwd)"
echo "files:"
git ls-files | grep 'test_jit_regexp_exec_override\.cjs$' || true

if [ -f tests/test_jit_regexp_exec_override.cjs ]; then
  awk '{ printf "%6d\t%s\n", NR, $0 }' tests/test_jit_regexp_exec_override.cjs | sed -n '1,90p'
else
  echo "tests/test_jit_regexp_exec_override.cjs not found"
fi

node - <<'NODE'
const re = /-/;
RegExp.prototype.replace = undefined;
console.log('string-key:', 'a-b'.replace(re, '+'));

const saved = RegExp.prototype[Symbol.replace];
RegExp.prototype[Symbol.replace] = () => 'overridden';
console.log('symbol-key:', 'a-b'.replace(re, '+'));
RegExp.prototype[Symbol.replace] = saved;
NODE

Repository: theMackabu/ant

Length of output: 2194


Exercise Symbol.replace, not the unrelated "replace" property.

putReplace() writes RegExp.prototype.replace, but String.prototype.replace() dispatches via RegExp.prototype[Symbol.replace], so the assertion cannot observe the override path under test. Store through Symbol.replace, assert an observable custom result, and restore originalReplace.

Also applies to: 36-39

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_jit_regexp_exec_override.cjs` around lines 9 - 10, Update
putReplace() and its related test setup to override the object’s Symbol.replace
method instead of the string-keyed replace property. Make the custom
implementation return an observable result, assert that result through
String.prototype.replace(), and restore originalReplace afterward.

@coderabbitai coderabbitai Bot mentioned this pull request Jul 31, 2026
…sia-parity

# Conflicts:
#	docs/exec-plans/active/README.md
#	docs/exec-plans/completed/README.md
#	examples/bench-v8/score.json
#	include/internal.h
#	include/modules/headers.h
#	include/utf8.h
#	meson/pgo/profiles/ant-darwin-aarch64.profdata
#	src/ant.c
#	src/gc/gc.c
#	src/gc/objects.c
#	src/modules/builtin.c
#	src/modules/headers.c
#	src/modules/request.c
#	src/modules/response.c
#	src/utf8.c
#	tests/harness/harness.js
#	tests/harness/manifest.js

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/modules/regex.c (1)

1277-1286: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use invalid-subject-safe PCRE2 matching for JavaScript strings.

All PCRE2 compilations use PCRE2_UTF, and interpreted matches validate the subject unless PCRE2_NO_UTF_CHECK is set; lone-UTF-16-surrogate subjects cannot get that option. Add PCRE2_MATCH_INVALID_UTF to the compile options so valid ECMAScript strings such as "\uD800" do not fail before matching through either pcre2_match or pcre2_jit_match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/regex.c` around lines 1277 - 1286, Update the PCRE2 compilation
options in the regex compilation path to include PCRE2_MATCH_INVALID_UTF
alongside PCRE2_UTF, ensuring JavaScript strings containing lone UTF-16
surrogates can be matched without validation failure through both pcre2_match
and pcre2_jit_match. Locate the compile-options setup used to create
compiled.code; leave the matching branch logic unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/modules/response.c`:
- Around line 1142-1151: Update js_response_clone to capture and validate the
bool results from headers_copy_from and the related header-copy operations,
including headers_create_empty, headers_set_immutable, and readable_stream_tee.
On any false result, free the duplicated clone data and return the corresponding
error value instead of constructing a partial Response.

In `@src/modules/server.c`:
- Around line 920-923: The server_finish_websocket_upgrade path in
src/modules/server.c lines 920-923 must check whether response_get_headers
returns an error before enumerating headers and stop the upgrade on failure; the
regular response completion path at src/modules/server.c lines 1026-1029 must
perform the same check before capturing or serializing headers and send the
internal-error response when it fails.

In `@src/utf8.c`:
- Around line 396-400: Update utf8_replace_wtf8_surrogates to validate bytes[i +
2] as a UTF-8 continuation byte before replacing the three-byte sequence, while
preserving the existing ED A0-BF prefix check and replacement behavior for valid
surrogate sequences.

In `@tests/harness/harness.js`:
- Around line 2-5: Update the Child process handling to listen for both spawn
exit and error events, settling this.exited exactly once regardless of which
occurs first. In the error handler, append the launch error message to output so
callers can record the failed executable launch, while preserving the existing
exit handling behavior.

---

Outside diff comments:
In `@src/modules/regex.c`:
- Around line 1277-1286: Update the PCRE2 compilation options in the regex
compilation path to include PCRE2_MATCH_INVALID_UTF alongside PCRE2_UTF,
ensuring JavaScript strings containing lone UTF-16 surrogates can be matched
without validation failure through both pcre2_match and pcre2_jit_match. Locate
the compile-options setup used to create compiled.code; leave the matching
branch logic unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d22a73d-ac9f-436a-9616-e78761e97660

📥 Commits

Reviewing files that changed from the base of the PR and between 547d4b2 and 149fcba.

📒 Files selected for processing (38)
  • docs/exec-plans/active/README.md
  • docs/exec-plans/completed/README.md
  • include/ant.h
  • include/arena.h
  • include/gc.h
  • include/internal.h
  • include/modules/buffer.h
  • include/modules/headers.h
  • include/modules/regex.h
  • include/modules/response.h
  • include/object.h
  • include/silver/engine.h
  • include/utf8.h
  • src/ant.c
  • src/gc/gc.c
  • src/gc/objects.c
  • src/main.c
  • src/modules/buffer.c
  • src/modules/builtin.c
  • src/modules/crypto.c
  • src/modules/fetch.c
  • src/modules/headers.c
  • src/modules/path.c
  • src/modules/regex.c
  • src/modules/request.c
  • src/modules/response.c
  • src/modules/server.c
  • src/modules/textcodec.c
  • src/silver/compiler.c
  • src/silver/engine.c
  • src/silver/glue.c
  • src/silver/ops/coercion.h
  • src/silver/ops/comparison.h
  • src/silver/ops/globals.h
  • src/silver/ops/property.h
  • src/utf8.c
  • tests/harness/harness.js
  • tests/harness/manifest.js
💤 Files with no reviewable changes (2)
  • include/utf8.h
  • src/modules/path.c
🚧 Files skipped from review as they are similar to previous changes (26)
  • src/modules/builtin.c
  • src/main.c
  • include/gc.h
  • include/modules/buffer.h
  • src/modules/fetch.c
  • docs/exec-plans/active/README.md
  • docs/exec-plans/completed/README.md
  • include/modules/headers.h
  • src/silver/ops/coercion.h
  • src/silver/ops/globals.h
  • src/silver/ops/comparison.h
  • include/silver/engine.h
  • include/object.h
  • src/modules/request.c
  • include/arena.h
  • src/gc/objects.c
  • src/modules/textcodec.c
  • include/ant.h
  • src/modules/crypto.c
  • src/gc/gc.c
  • src/silver/compiler.c
  • src/modules/buffer.c
  • include/modules/regex.h
  • src/silver/glue.c
  • src/silver/ops/property.h
  • src/ant.c

Comment thread src/modules/response.c
Comment on lines 1142 to 1151
src_headers = js_get_slot(this, SLOT_RESPONSE_HEADERS);
new_headers = headers_create_empty(js);
if (is_err(new_headers)) {
data_free(nd);
return new_headers;
if (headers_is_headers(src_headers)) {
new_headers = headers_create_empty(js);
if (is_err(new_headers)) {
data_free(js, nd);
return new_headers;
}
headers_copy_from(js, new_headers, src_headers);
headers_set_immutable(new_headers, headers_is_immutable(src_headers));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 '\bheaders_copy_from\s*\(' include src
rg -n -C 14 \
  'js_response_clone|headers_copy_from|headers_data_copy' \
  src/modules/response.c src/modules/headers.c

Repository: theMackabu/ant

Length of output: 16357


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1080,1185p' src/modules/response.c
sed -n '240,288p' src/modules/headers.c
sed -n '9,30p' include/modules/headers.h
rg -n 'headers_finalize|headers_data_destroy|headers_free|js_value.*free|is_err|headers_is_headers|headers_is_immutable|headers_set_immutable|headers_create_from_data|headers_create_empty' src/modules/headers.c src/modules/response.c include/modules/headers.h src/modules/request.c | sed -n '1,180p'

Repository: theMackabu/ant

Length of output: 16295


Check header-copy results from Response.clone.

headers_copy_from returns false when allocation fails during headers_create_empty, headers_copy_from, headers_set_immutable, or readable_stream_tee; each path in js_response_clone currently ignores that result and may return a Response object with only data copied from data_dup. Store and test the bool ok result before proceeding, free the clone data on failure, and return the error value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/response.c` around lines 1142 - 1151, Update js_response_clone to
capture and validate the bool results from headers_copy_from and the related
header-copy operations, including headers_create_empty, headers_set_immutable,
and readable_stream_tee. On any false result, free the duplicated clone data and
return the corresponding error value instead of constructing a partial Response.

Comment thread src/modules/server.c
Comment on lines 920 to 923
static bool server_finish_websocket_upgrade(server_request_t *req, ant_value_t response_obj, ant_value_t websocket_obj) {
response_data_t *resp = response_get_data(response_obj);
ant_value_t headers = response_get_headers(response_obj);
ant_value_t headers = response_get_headers(req->server->js, response_obj);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Handle errors from response_get_headers.

response_get_headers lazily materializes a Headers object and can return an error value. Both completion paths treat that value as Headers data.

  • src/modules/server.c#L920-L923: Check for an error before enumerating WebSocket upgrade headers. Stop the upgrade on failure.
  • src/modules/server.c#L1026-L1029: Check for an error before capturing or serializing regular response headers. Send the internal-error response on failure.
📍 Affects 1 file
  • src/modules/server.c#L920-L923 (this comment)
  • src/modules/server.c#L1026-L1029
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/server.c` around lines 920 - 923, The
server_finish_websocket_upgrade path in src/modules/server.c lines 920-923 must
check whether response_get_headers returns an error before enumerating headers
and stop the upgrade on failure; the regular response completion path at
src/modules/server.c lines 1026-1029 must perform the same check before
capturing or serializing headers and send the internal-error response when it
fails.

Comment thread src/utf8.c
Comment on lines +396 to +400
void utf8_replace_wtf8_surrogates(uint8_t *bytes, size_t len) {
for (size_t i = 0; i + 2 < len; i++) {
if (bytes[i] != 0xed || (bytes[i + 1] & 0xe0) != 0xa0) continue;
memcpy(bytes + i, "\xef\xbf\xbd", 3);
i += 2;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate the third byte before replacing a surrogate.

Line 398 accepts ED A0-BF with any third byte. ED A0 41 is not a WTF-8 surrogate, but the function replaces it and loses byte 0x41. Require bytes[i + 2] to be a continuation byte.

Proposed fix
-    if (bytes[i] != 0xed || (bytes[i + 1] & 0xe0) != 0xa0) continue;
+    if (bytes[i] != 0xed ||
+        (bytes[i + 1] & 0xe0) != 0xa0 ||
+        (bytes[i + 2] & 0xc0) != 0x80) continue;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void utf8_replace_wtf8_surrogates(uint8_t *bytes, size_t len) {
for (size_t i = 0; i + 2 < len; i++) {
if (bytes[i] != 0xed || (bytes[i + 1] & 0xe0) != 0xa0) continue;
memcpy(bytes + i, "\xef\xbf\xbd", 3);
i += 2;
void utf8_replace_wtf8_surrogates(uint8_t *bytes, size_t len) {
for (size_t i = 0; i + 2 < len; i++) {
if (bytes[i] != 0xed ||
(bytes[i + 1] & 0xe0) != 0xa0 ||
(bytes[i + 2] & 0xc0) != 0x80) continue;
memcpy(bytes + i, "\xef\xbf\xbd", 3);
i += 2;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utf8.c` around lines 396 - 400, Update utf8_replace_wtf8_surrogates to
validate bytes[i + 2] as a UTF-8 continuation byte before replacing the
three-byte sequence, while preserving the existing ED A0-BF prefix check and
replacement behavior for valid surrogate sequences.

Comment thread tests/harness/harness.js
Comment on lines +2 to +5
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { resolve } from 'node:path';

const ANT = process.env.ANT_TEST_BIN || process.execPath;
const ANT = resolve(process.env.ANT_TEST_BIN || process.execPath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to current Node.js documentation, does child_process.spawn() report a missing or non-executable command through the ChildProcess errorevent, and does an EventEmitter without anerror listener throw?

💡 Result:

Yes, child_process.spawn reports a missing command (and other spawn-related failures, such as a non-executable command that cannot be executed) by emitting an 'error' event on the ChildProcess instance [1][2][3]. Regarding the behavior of an EventEmitter when an 'error' event is emitted: according to the Node.js documentation, if an EventEmitter does not have at least one listener registered for the 'error' event, the error is thrown, a stack trace is printed, and the Node.js process exits [4][5][6]. Therefore, if you do not attach an 'error' listener to the child process object returned by spawn, any failure to spawn the process will result in an unhandled exception that terminates the entire Node.js process [7][8][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files:"
git ls-files | rg '(^|/)harness\.js$' || true

echo
echo "harness.js outline:"
ast-grep outline tests/harness/harness.js --view condensed || true

echo
echo "Relevant Child lines:"
nl -ba tests/harness/harness.js | sed -n '1,280p' | rg -n "class Child|constructor\\(|this\\.proc|\\.on\\(|\\.wait\\(|resolve\\(|exitCode|exited" -A 2 -B 2

echo
echo "All Child usages:"
rg -n "new Child|\\.wait\\(|child\\.wait" tests/harness/harness.js

echo
echo "Node event probe:"
node - <<'JS'
const { spawn } = require('node:child_process');
const path = require('node:path');
const fs = require('node:fs');

const absent = path.join(process.cwd(), 'this-file-definitely-does-not-exist-ant-test-bin');
const proc = spawn(absent);

let exitEmitted = false;
let errorEmitted = false;
let thrown = false;
let code = null;

try {
  proc.on('exit', (code) => {
    exitEmitted = true;
    code = code;
  });
  proc.on('close', () => {});
  proc.on('spawn', () => {});
} catch (e) {
  thrown = true;
}

const timeout = setTimeout(() => {
  console.log(JSON.stringify({
    hasErrorListener: proc.listenerCount('error') >= 1,
    exitEmitted,
    errorEmitted,
    thrown,
    exitCode: code
  }, null, 2));
  process.exit(0);
}, 200);

timeout.unref();
JS

Repository: theMackabu/ant

Length of output: 447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Relevant Child section:"
awk '{printf "%5d\t%s\n", NR, $0}' tests/harness/harness.js | sed -n '120,280p'

echo
echo "All Child usages:"
rg -n "new Child|\\.wait\\(|child\\.wait" tests/harness/harness.js

echo
echo "Node event probe:"
node - <<'JS'
const { spawn } = require('node:child_process');
const path = require('node:path');

const absent = path.join(process.cwd(), 'this-file-definitely-does-not-exist-ant-test-bin');
const proc = spawn(absent);

let exitEmitted = false;
let errorEmitted = false;
try {
  proc.on('exit', (code) => {
    exitEmitted = true;
    console.log({exitEmitted,errorEmitted,code});
  });
} catch (e) {
  console.log({exitEmitted,errorEmitted,thrown:true,message:e.message});
}
setTimeout(() => {
  console.log(JSON.stringify({exitEmitted,errorEmitted}, null, 2));
  process.exit(0);
}, 200);
JS

Repository: theMackabu/ant

Length of output: 8058


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top of tests/harness/harness.js:"
awk '{printf "%5d\t%s\n", NR, $0}' tests/harness/harness.js | sed -n '1,120p'

echo
echo "Node event probe with error handler:"
node - <<'JS'
const { spawn } = require('node:child_process');
const path = require('node:path');

const absent = path.join(process.cwd(), 'this-file-definitely-does-not-exist-ant-test-bin');
const proc = spawn(absent);

proc.on('exit', (code) => {
  console.log(JSON.stringify({exitEmitted:true,errorEmitted:true,code}));
  process.exit(0);
});
proc.on('error', (err) => {
  console.log(err.code);
});
setTimeout(() => {
  console.log(JSON.stringify({exitEmitted:false,errorEmitted:false}));
}, 200);
JS

Repository: theMackabu/ant

Length of output: 4183


Handle failed executable launch in Child.

ANT_TEST_BIN can name a missing or non-executable file. In that case, spawn() emits error. Child has no error listener, so the unhandled error event exits the harness before test callers can record a failure. Settle this.exited once from both exit and error, and append the launch error message to output.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'node:child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/harness/harness.js` around lines 2 - 5, Update the Child process
handling to listen for both spawn exit and error events, settling this.exited
exactly once regardless of which occurs first. In the error handler, append the
launch error message to output so callers can record the failed executable
launch, while preserving the existing exit handling behavior.

- restore O(1) ascii fast path for Buffer.byteLength/Buffer.from with
  ascii/latin1/ucs2 encodings, matching the memoized ucs2 branch
- guard ms() harness helper against swapped (min, max) arguments from
  upstream merges
- cross-reference JS_NATIVE_CTOR with the SV_CALL_* call_flags bits
- allocate response data_dup clones from the native-data arena
- export ANT_NATIVE_DATA_SLOT_SIZE and use it in the slot-fit asserts
- derive the substring ascii flag from the memoized utf16 length
- drop stale g_ prefixes on file statics; tidy decl order and blank
  lines left by the merge
- re-run bench-v8 on the merged tree
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