Skip to content

Runtime (C): expand ads_fmt_* family + add result mutators#4

Open
kevinelliott wants to merge 1 commit into
mainfrom
feat/c-runtime-formatters
Open

Runtime (C): expand ads_fmt_* family + add result mutators#4
kevinelliott wants to merge 1 commit into
mainfrom
feat/c-runtime-formatters

Conversation

@kevinelliott

@kevinelliott kevinelliott commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds 24 new formatter functions + 6 result-mutator functions to the C runtime, unblocking ~30+ stubs surfaced by the Stage 2.5 C bulk port (acars-decoder-c#1).

Mirrors the TS ResultFormatter shape exactly so cross-language corpus output stays byte-identical when the C escape hatches are filled in.

What landed

New formatters

Category Functions
Time-of-day (seconds since midnight → HH:MM:SS) ads_fmt_eta, _off, _on, _in, _out
Calendar ads_fmt_day, _departure_day, _arrival_day, _month
Velocity / atmosphere ads_fmt_mach, _groundspeed, _airspeed, _temperature, _total_air_temp
Fuel ads_fmt_current_fuel, _remaining_fuel
Routing ads_fmt_alternate_airport, _arrival_runway, _alternate_runway
Events ads_fmt_state_change, _door_event
Free-text ads_fmt_text, _unknown, _unknown_sep, _unknown_arr_sep
Diagnostic ads_fmt_checksum, _checksum_algorithm
Generic ads_fmt_push_item (for plugins that emit custom item shapes), ads_fmt_time_of_day_str

New result mutators

For escape hatches that need to override fields the ads_result_new() already set up (variant-dependent description, custom remaining text, etc.):

  • ads_result_set_description
  • ads_result_set_remaining
  • ads_result_append_remaining
  • ads_result_get_remaining
  • ads_result_set_decode_level
  • ads_result_clear_items

Convention

Every ads_fmt_* takes ownership of the ads_value_t * it receives and frees it (matches ads_result_raw_set's transfer semantics). NULL value → no-op, matching the TS guard-before-call pattern.

ads_fmt_unknown_arr refactored to delegate through ads_fmt_unknown_arr_sep + ads_result_append_remaining.

Test plan

  • runtimes/c builds clean as libads_runtime_c.a (verified with cmake)
  • CI runs on this PR
  • Reviewer spot-check that new formatter output strings ("OFF", "ETA", "GS", "FOB", etc.) match the TS `ResultFormatter` code/label conventions (cross-reference `runtimes/typescript/utils/result_formatter.ts`)

What this unblocks

The C bulk hatch translation surfaced this exact checklist (see acars-decoder-c#1's commit message for the full list). Once this merges:

  • ~30+ C escape-hatch stubs become directly portable from the TS reference (e.g. Label_10_LDR needs arrival_runway + alternate_runway + alternate_airport, all now present)
  • Future Rust runtime expansion can mirror the same shape for consistency

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Expanded formatting support for diverse data types including time/calendar information, aircraft parameters (speed, temperature), fuel metrics, routing identifiers, event data, and diagnostic information.
    • Enhanced result manipulation with new options for managing descriptions, remaining text, and decode levels.

Unblocks ~30+ C escape-hatch stubs surfaced by the Stage 2.5 C bulk
port (airframesio/acars-decoder-c#1). Mirrors the TS ResultFormatter
shape so cross-language corpus output stays byte-identical.

New formatters (24 functions):

  Time-of-day (seconds since midnight → HH:MM:SS):
    ads_fmt_eta, _off, _on, _in, _out

  Calendar:
    ads_fmt_day, _departure_day, _arrival_day, _month

  Velocity / atmosphere:
    ads_fmt_mach, _groundspeed, _airspeed, _temperature, _total_air_temp

  Fuel:
    ads_fmt_current_fuel, _remaining_fuel

  Routing:
    ads_fmt_alternate_airport, _arrival_runway, _alternate_runway

  Events:
    ads_fmt_state_change, _door_event

  Free-text:
    ads_fmt_text, _unknown, _unknown_sep, _unknown_arr_sep

  Diagnostic:
    ads_fmt_checksum, _checksum_algorithm

  Generic:
    ads_fmt_push_item (for plugins that emit custom item shapes)
    ads_fmt_time_of_day_str (caller-owned malloc'd string helper)

New result mutators (6 functions on ads_decode_result_t):

    ads_result_set_description
    ads_result_set_remaining
    ads_result_append_remaining
    ads_result_get_remaining
    ads_result_set_decode_level
    ads_result_clear_items

Convention: every ads_fmt_* takes ownership of the ads_value_t * it
receives and frees it (matches ads_result_raw_set's transfer
semantics). NULL value → no-op, matching the TS guard-before-call
pattern.

Refactor of existing ads_fmt_unknown_arr to delegate through the new
ads_fmt_unknown_arr_sep + ads_result_append_remaining.

Verified: runtimes/c builds clean as libads_runtime_c.a.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 5, 2026 21:42
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown

Too many files changed? Review this PR in Change Stack to see how the pieces fit before you dive in.

Review Change Stack

📝 Walkthrough

Walkthrough

This PR expands the ADS formatter library with comprehensive field-specific formatters (time-of-day, calendar, aircraft telemetry, events, diagnostics) and result-mutation helpers. The header declares all public APIs with documented ownership semantics, while the implementation provides the actual formatting and state-management functions.

Changes

ADS Formatter API Expansion

Layer / File(s) Summary
Public API Contract and Ownership Model
runtimes/c/include/ads_helpers.h
Header documentation clarifies that formatters take ownership of ads_value_t* inputs and free them; NULL inputs are no-ops. Declares 26+ formatter functions across time-of-day, calendar, velocity/atmosphere, fuel, routing, event, text, diagnostic, and item-push categories, plus 6 result mutator/getter functions.
Unknown and Remaining Text Handling
runtimes/c/src/result_formatter.c
ads_fmt_unknown_arr_sep joins string arrays with configurable separators; ads_fmt_unknown and ads_fmt_unknown_sep append single unknown values to the result "remaining" buffer with default or custom separators.
Time-of-Day and Calendar Formatters
runtimes/c/src/result_formatter.c
ads_fmt_time_of_day_str returns heap-allocated HH:MM:SS strings or raw seconds; internal push_tod writes both numeric and formatted values into JSON and items; public formatters wrap ETA/OFF/ON/IN/OUT. Calendar formatters for day/month write integers to JSON and standardized item entries.
Aircraft Telemetry and Routing Formatters
runtimes/c/src/result_formatter.c
Wrapper formatters for mach, speeds (ground/air), temperatures, fuel (current/remaining), and routing identifiers/runways delegate to existing numeric/string helpers with fixed raw JSON keys and standardized item metadata.
Event, State-Change, and Diagnostic Formatters
runtimes/c/src/result_formatter.c
ads_fmt_state_change and ads_fmt_door_event create nested JSON objects and corresponding items; ads_fmt_text writes generic text fields; checksum formatters parse integers, render as hex in items, and reference algorithm fields.
Generic Item Push and Result State Mutation
runtimes/c/src/result_formatter.c
ads_fmt_push_item normalizes null pointers and forwards to standardized item entry. Result mutators: set_description, set_remaining, append_remaining (with separator and grow/reallocate), get_remaining, set_decode_level, clear_items (recreates JSON array).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A rabbit hops through fields of flight,
Where aircraft whisper day and night,
With formatters in place so neat,
Time, fuel, and speeds—a complete suite!
New APIs bloom like morning dew,
To decode what the skies tell true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.21% 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 main changes: expanding the ads_fmt_* formatter family and adding result mutator functions to the C runtime.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/c-runtime-formatters

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Infer (1.2.0)
runtimes/c/src/result_formatter.c

runtimes/c/src/result_formatter.c:4:10: fatal error: 'ads_helpers.h' file not found
4 | #include "ads_helpers.h"
| ^~~~~~~~~~~~~~~
1 error generated.
Error: the following clang command did not run successfully:
/opt/infer-linux-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/bin/clang-18
@/tmp/coderabbit-infer/c649d6bab0e40b0c3d241297a5893418e6331614-50c1aa34ff07d59a/tmp/clang_command_.tmp.e9127c.txt
++Contents of '/tmp/coderabbit-infer/c649d6bab0e40b0c3d241297a5893418e6331614-50c1aa34ff07d59a/tmp/clang_command_.tmp.e9127c.txt':
"-cc1" "-load"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../../facebook-clang-plugins/libtooling/build/FacebookClangPlugin.dylib"
"-add-plugin" "BiniouASTExporter" "-plugin-arg-BiniouASTExporter" "-"
"-plugin-arg-BiniouASTExporter" "PREPEND_CURRENT_DIR=1"
"-plugin-arg-BiniouASTExporter" "MAX_STRING_SIZE=65535" "-cc1" "-triple"
"x86_64-unknown-linux-gnu" "-emit-obj" "-mrelax-all" "-disab

... [truncated 728 characters] ...

x-x86_64-v1.2.0/lib/infer/facebook-clang-plugins/clang/install/lib/clang/18/include"
"-internal-isystem" "/usr/local/include" "-internal-isystem"
"/usr/lib/gcc/x86_64-linux-gnu/12/../../../../x86_64-linux-gnu/include"
"-internal-externc-isystem" "/usr/include/x86_64-linux-gnu"
"-internal-externc-isystem" "/include" "-internal-externc-isystem"
"/usr/include" "-Wno-ignored-optimization-argument" "-Wno-everything"
"-ferror-limit" "19" "-fgnuc-version=4.2.1" "-fskip-odr-check-in-gmf"
"-D__GCC_HAVE_DWARF2_CFI_ASM=1" "-o"
"/tmp/coderabbit-infer/50c1aa34ff07d59a/file.o" "-x" "c"
"runtimes/c/src/result_formatter.c" "-O0" "-fno-builtin" "-include"
"/opt/infer-linux-x86_64-v1.2.0/lib/infer/infer/bin/../lib/clang_wrappers/global_defines.h"
"-Wno-everything"


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 and usage tips.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c649d6bab0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

void ads_fmt_checksum_algorithm(ads_decode_result_t *r, const char *value) {
if (!r || !value) return;
cJSON_AddStringToObject(r->raw, "checksum_algorithm", value);
push_item(r, "checksum_algorithm", "CKSUM_ALGO", "Checksum Algorithm", value);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Omit the checksum algorithm formatted item

When ARINC 702-style hatches call ads_fmt_checksum_algorithm, this adds a visible formatted item even though the TypeScript reference only sets raw.checksum_algorithm and leaves the formatted push commented out in runtimes/typescript/utils/result_formatter.ts. That makes every checksum-algorithm result diverge from the byte-identical corpus output this runtime is trying to mirror.

Useful? React with 👍 / 👎.

Comment on lines +248 to +249
snprintf(buf, sizeof(buf), "%llx", (long long)n);
push_item(r, "checksum", "CKSUM", "Checksum", buf);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match TS checksum output formatting

For messages that include a checksum, the TypeScript formatter emits type: 'message_checksum', code: 'CHECKSUM', label: 'Message Checksum', and a zero-padded value like 0x00af; this C formatter emits a bare lowercase hex string with different metadata. Downstream corpus comparisons and consumers of the formatted item shape will see different results for the same decoded message.

Useful? React with 👍 / 👎.

Comment on lines +194 to +195
void ads_fmt_temperature(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "outside_air_temperature", "outside_air_temperature", "OATEMP", "Outside Air Temperature (C)", "degrees"); }
void ads_fmt_total_air_temp(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "total_air_temperature", "total_air_temperature", "TATEMP", "Total Air Temperature (C)", "degrees"); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse temperature formatter inputs like TS

The TS temperature/totalAirTemp formatters accept strings such as M05/P03 and convert M/P signs before storing the numeric raw temperature; these C wrappers call push_numeric, so a ported hatch passing the same string value will fail ads_value_as_double and emit 0 degrees. That corrupts ARINC 702 temperature fields rather than matching the reference output.

Useful? React with 👍 / 👎.

Comment on lines +191 to +193
void ads_fmt_mach(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "mach", "mach", "MACH", "Mach", ""); }
void ads_fmt_groundspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "groundspeed", "groundspeed", "GS", "Ground Speed", "knots"); }
void ads_fmt_airspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "airspeed", "airspeed", "IAS", "Indicated Airspeed", "knots"); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve TS velocity item metadata

When hatches use these new velocity formatters, their formatted item shapes differ from ResultFormatter: groundspeed should be aircraft_groundspeed/GSPD/Aircraft Groundspeed, airspeed should use ASPD/True Airspeed, and Mach should be labeled Mach Number with a mach suffix. These mismatches break the intended byte-for-byte cross-language formatted output.

Useful? React with 👍 / 👎.


void ads_fmt_alternate_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_icao", "icao", "ALT_DST", "Alternate Destination"); }
void ads_fmt_arrival_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "arrival_runway", "runway", "ARWY", "Arrival Runway"); }
void ads_fmt_alternate_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_runway", "runway", "ALT_RWY", "Alternate Runway"); }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Use the TS alternate runway code

For decoded alternate runways, the TypeScript formatter emits code ALT_ARWY, but this C helper emits ALT_RWY. Any C escape hatch that was unblocked by this helper will produce a different formatted item code from the reference runtime for the same message.

Useful? React with 👍 / 👎.

Comment on lines +163 to +167
void ads_fmt_in(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "in_time", "time", "IN", "Arrival at Gate");
}
void ads_fmt_out(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "out_time", "time", "OUT", "Departure from Gate");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Match the TS OOOI time labels

For OUT/IN time formatters, the TypeScript reference labels these items Out of Gate Time and In Gate Time; this implementation emits different labels. Ported OOOI hatches that call these new helpers will therefore generate formatted output that does not match the shared corpus even though the raw times are correct.

Useful? React with 👍 / 👎.

Copilot AI 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.

Pull request overview

This PR expands the C runtime’s result formatting surface by adding many new ads_fmt_* formatter functions and several ads_result_* mutators intended to mirror the TypeScript ResultFormatter API and unblock C escape-hatch ports.

Changes:

  • Added new formatter functions for time-of-day, calendar, routing, fuel, velocity/atmosphere, events, diagnostics, and free-text.
  • Refactored unknown-array formatting to delegate via ads_fmt_unknown_arr_sep() and ads_result_append_remaining().
  • Added result “mutator” helpers for modifying description/remaining/decode level and clearing formatted items.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 11 comments.

File Description
runtimes/c/src/result_formatter.c Implements the new formatter family and adds result mutator helpers.
runtimes/c/include/ads_helpers.h Exposes the new formatter and result-mutator APIs via the public helpers header.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

void ads_fmt_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "day", "day", "MSG_DAY", "Day of Month"); }
void ads_fmt_departure_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "departure_day", "day", "DEP_DAY", "Departure Day"); }
void ads_fmt_arrival_day(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "arrival_day", "day", "ARR_DAY", "Arrival Day"); }
void ads_fmt_month(ads_decode_result_t *r, ads_value_t *v) { push_int_item(r, v, "month", "month", "MSG_MONTH", "Month"); }
Comment on lines +191 to +195
void ads_fmt_mach(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "mach", "mach", "MACH", "Mach", ""); }
void ads_fmt_groundspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "groundspeed", "groundspeed", "GS", "Ground Speed", "knots"); }
void ads_fmt_airspeed(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "airspeed", "airspeed", "IAS", "Indicated Airspeed", "knots"); }
void ads_fmt_temperature(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "outside_air_temperature", "outside_air_temperature", "OATEMP", "Outside Air Temperature (C)", "degrees"); }
void ads_fmt_total_air_temp(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "total_air_temperature", "total_air_temperature", "TATEMP", "Total Air Temperature (C)", "degrees"); }

void ads_fmt_alternate_airport(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_icao", "icao", "ALT_DST", "Alternate Destination"); }
void ads_fmt_arrival_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "arrival_runway", "runway", "ARWY", "Arrival Runway"); }
void ads_fmt_alternate_runway(ads_decode_result_t *r, ads_value_t *v) { push_string(r, v, "alternate_runway", "runway", "ALT_RWY", "Alternate Runway"); }
Comment on lines +163 to +167
void ads_fmt_in(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "in_time", "time", "IN", "Arrival at Gate");
}
void ads_fmt_out(ads_decode_result_t *r, ads_value_t *v) {
push_tod(r, v, "out_time", "time", "OUT", "Departure from Gate");
Comment on lines +210 to +219
void ads_fmt_state_change(ads_decode_result_t *r, const char *from, const char *to) {
if (!r || !from || !to) return;
cJSON *obj = cJSON_CreateObject();
cJSON_AddStringToObject(obj, "from", from);
cJSON_AddStringToObject(obj, "to", to);
cJSON_AddItemToObject(r->raw, "state_change", obj);
char buf[128];
snprintf(buf, sizeof(buf), "%s -> %s", from, to);
push_item(r, "state_change", "STATE_CHANGE", "State Change", buf);
}
Comment on lines +253 to +257
void ads_fmt_checksum_algorithm(ads_decode_result_t *r, const char *value) {
if (!r || !value) return;
cJSON_AddStringToObject(r->raw, "checksum_algorithm", value);
push_item(r, "checksum_algorithm", "CKSUM_ALGO", "Checksum Algorithm", value);
}
Comment on lines +279 to 297
void ads_result_set_remaining(ads_decode_result_t *r, const char *text) {
if (!r) return;
free(r->remaining);
r->remaining = text ? strdup(text) : NULL;
}

void ads_result_append_remaining(ads_decode_result_t *r, const char *text, const char *sep) {
if (!r || !text) return;
if (!sep) sep = ",";
if (r->remaining) {
size_t newlen = strlen(r->remaining) + 1 + strlen(joined) + 1;
size_t newlen = strlen(r->remaining) + strlen(sep) + strlen(text) + 1;
char *grown = malloc(newlen);
snprintf(grown, newlen, "%s,%s", r->remaining, joined);
if (!grown) return;
snprintf(grown, newlen, "%s%s%s", r->remaining, sep, text);
free(r->remaining);
r->remaining = grown;
} else {
r->remaining = strdup(joined);
r->remaining = strdup(text);
}
Comment on lines +273 to +277
void ads_result_set_description(ads_decode_result_t *r, const char *description) {
if (!r) return;
free(r->description);
r->description = description ? strdup(description) : NULL;
}
Comment on lines +309 to 313
void ads_result_clear_items(ads_decode_result_t *r) {
if (!r) return;
cJSON_Delete(r->items);
r->items = cJSON_CreateArray();
}
/* ─── Fuel formatters ────────────────────────────────────────────────────── */

void ads_fmt_current_fuel(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "fuel_on_board", "fuel_on_board", "FOB", "Fuel On Board", ""); }
void ads_fmt_remaining_fuel(ads_decode_result_t *r, ads_value_t *v) { push_numeric(r, v, "fuel_remaining", "fuel_remaining", "FUEL_REM", "Fuel Remaining", ""); }

@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

🧹 Nitpick comments (1)
runtimes/c/src/result_formatter.c (1)

99-114: 💤 Low value

Consider single-pass concatenation to avoid O(n²) strcat overhead.

The repeated strcat calls iterate from the start of joined each time, making the loop O(n²) in total string length. For typical small arrays this is fine, but if large arrays are expected, a pointer-based approach would be more efficient.

♻️ Optional O(n) approach
     char *joined = malloc(total);
     if (!joined) return;
-    joined[0] = '\0';
-    for (size_t i = 0; i < count; i++) {
-        if (i > 0) strcat(joined, sep);
-        strcat(joined, values[i] ? values[i] : "");
-    }
+    char *p = joined;
+    for (size_t i = 0; i < count; i++) {
+        if (i > 0) { memcpy(p, sep, sep_len); p += sep_len; }
+        const char *s = values[i] ? values[i] : "";
+        size_t len = strlen(s);
+        memcpy(p, s, len);
+        p += len;
+    }
+    *p = '\0';
🤖 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 `@runtimes/c/src/result_formatter.c` around lines 99 - 114, The function
ads_fmt_unknown_arr_sep uses repeated strcat causing O(n²) behavior; change
ads_fmt_unknown_arr_sep to build the joined buffer in a single pass by
allocating the computed total, then maintain a write pointer (e.g., char *p =
joined) and for each element memcpy/strcpy the value and insert the separator
between elements by advancing p by the copied lengths, finally null-terminate
and call ads_result_append_remaining(r, joined, sep); ensure you still handle
NULL values (treat as empty string), use sep_len already computed, and
free(joined) after the append.
🤖 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 `@runtimes/c/include/ads_helpers.h`:
- Around line 63-65: The header comment incorrectly states "Every ads_fmt_*
takes ownership of the ads_value_t pointers", which overstates ownership because
several functions in this file (e.g., ads_fmt_state_change, ads_fmt_door_event,
ads_fmt_text, ads_fmt_unknown*, ads_fmt_checksum_algorithm, ads_fmt_push_item)
do not accept ads_value_t* and thus do not take ownership; update the comment in
runtimes/c/include/ads_helpers.h to explicitly state that only the ads_fmt_*
variants that accept ads_value_t* parameters transfer ownership and free them
(and that callers should pass NULL to no-op), and optionally list or reference
the specific functions that do not take ads_value_t* to avoid confusion.

In `@runtimes/c/src/result_formatter.c`:
- Around line 126-138: The function ads_fmt_time_of_day_str uses a fixed 16-byte
buffer which can overflow/truncate when printing out-of-range int64_t values;
change it to allocate a sufficiently large buffer only when printing the raw
integer: compute the required length for snprintf of the int64_t (or use
snprintf(NULL,0, "%lld", (long long)seconds) + 1), malloc that size, check the
allocation, then call snprintf into that buffer; keep the existing 16-byte
formatted HH:MM:SS branch unchanged and ensure all allocation failures return
NULL.

---

Nitpick comments:
In `@runtimes/c/src/result_formatter.c`:
- Around line 99-114: The function ads_fmt_unknown_arr_sep uses repeated strcat
causing O(n²) behavior; change ads_fmt_unknown_arr_sep to build the joined
buffer in a single pass by allocating the computed total, then maintain a write
pointer (e.g., char *p = joined) and for each element memcpy/strcpy the value
and insert the separator between elements by advancing p by the copied lengths,
finally null-terminate and call ads_result_append_remaining(r, joined, sep);
ensure you still handle NULL values (treat as empty string), use sep_len already
computed, and free(joined) after the append.
🪄 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: e54d0864-607d-4c53-988e-67fd40d331df

📥 Commits

Reviewing files that changed from the base of the PR and between e7c66e1 and c649d6b.

📒 Files selected for processing (2)
  • runtimes/c/include/ads_helpers.h
  • runtimes/c/src/result_formatter.c

Comment on lines +63 to +65
/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Documentation overstates ownership scope.

The comment says "Every ads_fmt_*" takes ownership of ads_value_t*, but several ads_fmt_* functions declared below don't accept ads_value_t* at all (e.g., ads_fmt_state_change, ads_fmt_door_event, ads_fmt_text, ads_fmt_unknown*, ads_fmt_checksum_algorithm, ads_fmt_push_item). Consider clarifying:

📝 Suggested wording
-/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
+/* Each ads_fmt_* that accepts an ads_value_t* takes ownership of it and
  * frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
  * to no-op (matches the TS pattern of guarding before calling). */
📝 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
/* Every ads_fmt_* takes ownership of the ads_value_t pointers passed in and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */
/* Each ads_fmt_* that accepts an ads_value_t* takes ownership of it and
* frees them (matches ads_result_raw_set's transfer semantics). Pass NULL
* to no-op (matches the TS pattern of guarding before calling). */
🤖 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 `@runtimes/c/include/ads_helpers.h` around lines 63 - 65, The header comment
incorrectly states "Every ads_fmt_* takes ownership of the ads_value_t
pointers", which overstates ownership because several functions in this file
(e.g., ads_fmt_state_change, ads_fmt_door_event, ads_fmt_text, ads_fmt_unknown*,
ads_fmt_checksum_algorithm, ads_fmt_push_item) do not accept ads_value_t* and
thus do not take ownership; update the comment in
runtimes/c/include/ads_helpers.h to explicitly state that only the ads_fmt_*
variants that accept ads_value_t* parameters transfer ownership and free them
(and that callers should pass NULL to no-op), and optionally list or reference
the specific functions that do not take ads_value_t* to avoid confusion.

Comment on lines +126 to +138
char *ads_fmt_time_of_day_str(int64_t seconds) {
char *out = malloc(16);
if (!out) return NULL;
if (seconds >= 0 && seconds < 86400) {
int h = (int)(seconds / 3600);
int m = (int)((seconds % 3600) / 60);
int s = (int)(seconds % 60);
snprintf(out, 16, "%02d:%02d:%02d", h, m, s);
} else {
snprintf(out, 16, "%lld", (long long)seconds);
}
return out;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Buffer too small for out-of-range integer values.

When seconds is outside the valid 0–86399 range, snprintf outputs the raw integer. A 64-bit signed integer can require up to 20 characters plus sign and null terminator (e.g., -9223372036854775808). The 16-byte buffer will truncate these values silently.

🔧 Suggested fix
 char *ads_fmt_time_of_day_str(int64_t seconds) {
-    char *out = malloc(16);
+    char *out = malloc(24);  /* Room for longest int64 string + null */
     if (!out) return NULL;
     if (seconds >= 0 && seconds < 86400) {
         int h = (int)(seconds / 3600);
         int m = (int)((seconds % 3600) / 60);
         int s = (int)(seconds % 60);
-        snprintf(out, 16, "%02d:%02d:%02d", h, m, s);
+        snprintf(out, 24, "%02d:%02d:%02d", h, m, s);
     } else {
-        snprintf(out, 16, "%lld", (long long)seconds);
+        snprintf(out, 24, "%lld", (long long)seconds);
     }
     return out;
 }
🤖 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 `@runtimes/c/src/result_formatter.c` around lines 126 - 138, The function
ads_fmt_time_of_day_str uses a fixed 16-byte buffer which can overflow/truncate
when printing out-of-range int64_t values; change it to allocate a sufficiently
large buffer only when printing the raw integer: compute the required length for
snprintf of the int64_t (or use snprintf(NULL,0, "%lld", (long long)seconds) +
1), malloc that size, check the allocation, then call snprintf into that buffer;
keep the existing 16-byte formatted HH:MM:SS branch unchanged and ensure all
allocation failures return NULL.

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.

2 participants