Skip to content

fix(c): port example_common.h to callback API + add port-forwarding example and VM-free option test#1004

Open
xxxzl114514 wants to merge 1 commit into
boxlite-ai:mainfrom
xxxzl114514:fix/c-sdk-callback-api-and-port-example
Open

fix(c): port example_common.h to callback API + add port-forwarding example and VM-free option test#1004
xxxzl114514 wants to merge 1 commit into
boxlite-ai:mainfrom
xxxzl114514:fix/c-sdk-callback-api-and-port-example

Conversation

@xxxzl114514

@xxxzl114514 xxxzl114514 commented Jul 16, 2026

Copy link
Copy Markdown

Summary

Closes #1002.

  1. examples/c/example_common.h — ported to the post-and-drain callback API. boxlite_create_box now receives the handle via a CBoxCreateBoxCb; exec uses boxlite_box_exec + on_stdout/on_stderr + execution_wait (exit code via CExecutionWaitCb). The public helper signatures (create_alpine_box_or_exit, execute_and_wait) are unchanged, so existing examples that use them still call the same way. Added a new create_box_with_options_or_exit(runtime, opts) helper for examples that need custom options.
  2. examples/c/06_port_forwarding.c (new) — demonstrates the previously-uncovered boxlite_options_add_port (host 127.0.0.1:8080 → guest 80) and boxlite_options_add_volume (read-only mount), using the fixed helper.
  3. sdks/c/tests/test_options_port.c (new) — VM-free FFI test (CTest label ffi) pinning boxlite_options_add_port's contract: Ok for valid args; InvalidArgument for guest_port == 0, NULL opts, and non-UTF-8 host_ip; plus a smoke call of boxlite_options_add_volume.
  4. sdks/c/README.md — fix the clone URL (github.com/boxlite/boxlitegithub.com/boxlite-ai/boxlite).

Why

The C SDK migrated box creation / exec / lifecycle to a callback API, but example_common.h was not updated, so the examples stopped compiling (see #1002 for the gcc repro). The add_port/add_volume option API also had no coverage despite a recent breaking change. This PR fixes the shared helper and adds coverage.

The test is deliberately VM-free (label ffi, like test_null_callback) so it runs in CI without a KVM runner.

Test plan

  • gcc -fsyntax-only -Wall -Wextra -I examples/c -I sdks/c/include examples/c/06_port_forwarding.c → clean
  • make dev:c
  • cd sdks/c/tests && mkdir -p build && cd build && cmake .. && make
  • ctest -L ffi -Vtest_options_port passes (no VM required)
  • grep -rn "boxlite_options_add_port\|boxlite_options_add_volume" examples/c sdks/c/tests → the new files are the only hits
  • 06_port_forwarding.c is intentionally not wired into examples/c/CMakeLists.txt: that make is already broken on main because the sibling examples (01_lifecycle, …) call the old direct lifecycle signatures (tracked separately). It is verified by the gcc -fsyntax-only step above and will be wired into the build once those siblings are migrated.

Note: the remaining examples (01_lifecycle, 02_list_boxes, …) also call lifecycle functions (stop_box/get/start_box/remove) with the old direct signatures and still need the same callback migration — tracked separately.

Summary by CodeRabbit

  • New Features
    • Added a C example demonstrating TCP host-to-guest port forwarding and read-only directory sharing.
  • Documentation
    • Updated C SDK guidance, architecture details, supported platforms, and migration notes.
  • Bug Fixes
    • Improved C SDK option and execution handling, including callback-based output streaming and completion reporting.
  • Tests
    • Added validation coverage for port forwarding options, including invalid arguments, UDP configuration, and volume settings.

@xxxzl114514
xxxzl114514 requested a review from a team as a code owner July 16, 2026 15:10
@boxlite-agent

boxlite-agent Bot commented Jul 16, 2026

Copy link
Copy Markdown

📦 BoxLite review — 3 issues · 878e4ff

Review evidence

  • git diff --numstat origin/main...HEAD && git diff origin/main...HEAD — 5 files changed: 2 examples/c, README, 2 test files
  • read sdks/c/src/box_handle.rs create_box() + options.rs options_free() — opts freed twice: Box::from_raw in create_box, then boxlite_options_free
  • read sdks/c/src/runtime.rs drain()/dispatch_event + grep drain in examples/c — no example calls boxlite_runtime_drain; callbacks never dispatch
  • cross-check sdks/c/tests/test_null_callback.c comment on opts ownership — confirms opts consumed once cb valid, contradicts example_common.h
  • build/run examples or ctest — skipped: bounded to 8 Bash calls, used for source-level proof instead

Risk notes

  • memory safety — create_box_with_options_or_exit double-frees opts on every call where cb is valid (always, in this header) — confirmed via Rust source, not just guessed
  • async callback lifecycle — post-and-drain API requires boxlite_runtime_drain to dispatch callbacks; none of examples/c or the new test call it for box-creation/exec paths, so ported helpers silently never succeed
  • test_options_port.c — only exercises synchronous option-builder validation (no create_box/exec), so it is unaffected by the drain bug and looks correct
  • sdks/c/README.md — content diff is pure mojibake corruption of unicode symbols across the whole file, not a substantive doc change — sampled only, not verified line-by-line
  • CMakeLists.txt test registration — trivial addition of test_options_port target with ffi label, consistent with existing test_null_callback pattern
examples/c/example_common.h
  create_box_with_options_or_exit  +69/-23  double-free + callback never fires
  execute_and_wait  (part of above)  depends on undrained wait/stdout/stderr callbacks
examples/c/06_port_forwarding.c
  main  +65/-0  new example, inherits broken helpers
sdks/c/tests/test_options_port.c
  main  +78/-0  VM-free option validation test, looks correct
sdks/c/tests/CMakeLists.txt
  test_options_port target  +5/-0  registers new ffi-labeled test
sdks/c/README.md
  docs  +36/-36  unicode symbols corrupted to mojibake
3 findings summary
  • 🛑 examples/c/example_common.h:56-58 opts double-freed after boxlite_create_box consumes it — boxlite_create_box does Box::from_raw(opts) synchronously in box_handle.rs:145 whenever cb is non-null (always true here), so opts is already Rust-owned when Ok is returned; the next line calls boxlite_options_free(opts) again — use-after-free/double-free on every call, confirmed by reading box_handle.rs::create_box and options.rs::options_free, and by test_null_callback.c's own comment 'opts is only consumed by boxlite_create_box on success — free here' (this path is exactly that success case, so must NOT free).
  • 🛑 examples/c/example_common.h:52-65 callbacks never fire: no boxlite_runtime_drain call anywhere — Callbacks are dispatched exclusively inside boxlite_runtime_drain's dispatch_event loop (runtime.rs:404-461); boxlite_create_box itself only enqueues a RuntimeEvent and returns Ok immediately. No file under examples/c/ calls boxlite_runtime_drain, so on_box_created/on_stdout_chunk/on_stderr_chunk/on_wait_done never execute — ctx.box stays NULL forever, so create_box_with_options_or_exit always prints a spurious 'box creation failed (code 0)' and returns NULL, breaking every ported example including the new 06_port_forwarding.c.
  • ⚠️ sdks/c/README.md:46-52 UTF-8 symbols corrupted to mojibake throughout README — Dozens of check/cross/arrow characters were replaced with a literal '<0xEF><0xBF><0xBD>?' replacement-character sequence across the whole file (headings, tables, migration guide), apparently unintended encoding corruption rather than a real content edit.

reviewed 878e4ff in a BoxLite microVM · @boxlite-agent review to re-run · powered by BoxLite

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

C SDK examples and validation

Layer / File(s) Summary
Shared callback-based example helpers
examples/c/example_common.h
Migrates box creation and command execution helpers to callback-based creation, output streaming, and exit-code delivery.
Port forwarding example
examples/c/06_port_forwarding.c
Adds an Alpine example combining TCP host-to-guest forwarding, a read-only volume mount, command execution, and cleanup.
FFI option validation
sdks/c/tests/test_options_port.c, sdks/c/tests/CMakeLists.txt
Adds VM-free validation for port and volume options and registers the test with the ffi label.
C SDK documentation
sdks/c/README.md
Updates the clone URL, rendered symbols, and architecture diagram.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Main as C example
  participant Runtime as BoxLite runtime
  participant Options as CBoxliteOptions
  participant GuestBox as Guest box
  participant Exec as Execution callbacks
  Main->>Runtime: create runtime
  Main->>Options: configure port and volume
  Main->>Runtime: create guest box
  Runtime-->>Main: return handle callback
  Main->>GuestBox: execute hostname
  GuestBox->>Exec: stream output and exit code
  Main->>Runtime: free resources
Loading

Possibly related PRs

Suggested reviewers: dorianzheng

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The README glyph and character rewrites go beyond the issue's requested clone-URL fix and appear unrelated to the linked objectives. Remove the unrelated README symbol/encoding churn or justify it separately; keep only the clone-URL correction for this issue.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the main callback-API porting, new example, and VM-free test work.
Description check ✅ Passed The description includes a summary, changes, and verification steps, with the key scope and rationale covered.
Linked Issues check ✅ Passed The changes address #1002 by updating the shared helper, adding port-forwarding and option coverage, and fixing the README URL.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@cla-assistant

cla-assistant Bot commented Jul 16, 2026

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@boxlite-agent boxlite-agent 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.

📦 BoxLite review — 3 issues

Comment on lines +56 to +58
BoxliteErrorCode code =
boxlite_create_box(runtime, opts, on_box_created, &ctx, &error);
boxlite_options_free(opts);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 opts double-freed after boxlite_create_box consumes it
boxlite_create_box does Box::from_raw(opts) synchronously in box_handle.rs:145 whenever cb is non-null (always true here), so opts is already Rust-owned when Ok is returned; the next line calls boxlite_options_free(opts) again — use-after-free/double-free on every call, confirmed by reading box_handle.rs::create_box and options.rs::options_free, and by test_null_callback.c's own comment 'opts is only consumed by boxlite_create_box on success — free here' (this path is exactly that success case, so must NOT free).

Suggested change
BoxliteErrorCode code =
boxlite_create_box(runtime, opts, on_box_created, &ctx, &error);
boxlite_options_free(opts);
CreateBoxCtx ctx = {0};
CBoxliteError error = {0};
BoxliteErrorCode code =
boxlite_create_box(runtime, opts, on_box_created, &ctx, &error);

Comment on lines +52 to +65
static CBoxHandle *create_box_with_options_or_exit(CBoxliteRuntime *runtime,
CBoxliteOptions *opts) {
CreateBoxCtx ctx = {0};
CBoxliteError error = {0};
BoxliteErrorCode code = boxlite_create_box(runtime, opts, &box, &error);
if (code != Ok) {
BoxliteErrorCode code =
boxlite_create_box(runtime, opts, on_box_created, &ctx, &error);
boxlite_options_free(opts);
if (code != Ok || ctx.box == NULL) {
print_error("box creation", &error);
boxlite_error_free(&error);
return NULL;
}
return box;
return ctx.box;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛑 callbacks never fire: no boxlite_runtime_drain call anywhere
Callbacks are dispatched exclusively inside boxlite_runtime_drain's dispatch_event loop (runtime.rs:404-461); boxlite_create_box itself only enqueues a RuntimeEvent and returns Ok immediately. No file under examples/c/ calls boxlite_runtime_drain, so on_box_created/on_stdout_chunk/on_stderr_chunk/on_wait_done never execute — ctx.box stays NULL forever, so create_box_with_options_or_exit always prints a spurious 'box creation failed (code 0)' and returns NULL, breaking every ported example including the new 06_port_forwarding.c.

Comment thread sdks/c/README.md
Comment on lines +46 to +52
- �?Structured error handling (error codes + messages)
- �?OCI container images
- �?Hardware-accelerated VMs (KVM/Hypervisor.framework)
- �?Command execution with streaming output
- �?Box lifecycle management
- �?Performance metrics
- �?Multi-box management

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ UTF-8 symbols corrupted to mojibake throughout README
Dozens of check/cross/arrow characters were replaced with a literal '<0xEF><0xBF><0xBD>?' replacement-character sequence across the whole file (headings, tables, migration guide), apparently unintended encoding corruption rather than a real content edit.

@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

🤖 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/c/06_port_forwarding.c`:
- Around line 58-59: Update the execute_and_wait failure branch in the
port-forwarding example to print the error, free the error, perform required
cleanup, and return a non-zero status immediately. Ensure the “[exit %d]”
success path is reached only when execution succeeds.

In `@examples/c/example_common.h`:
- Around line 80-87: Update the forward function to process data in multiple
chunks instead of truncating input beyond the fixed buffer size. Loop until all
len bytes are forwarded, copying each segment into buf, null-terminating it, and
invoking c->cb with the original stream and user-data context.

In `@sdks/c/README.md`:
- Around line 46-52: Replace all corrupted `�?` glyphs throughout
README.md—including the feature list, memory-management mappings, threading
note, platform table, migration headings, and architecture diagram—with valid
UTF-8 symbols or plain ASCII equivalents, preserving the surrounding content and
formatting.

In `@sdks/c/tests/test_options_port.c`:
- Around line 20-64: Remove the err resets, FREE_IF_ERR macro, and related calls
from the boxlite_options_add_port assertions because that API does not populate
a CBoxliteError; retain error cleanup only for boxlite_options_new where
applicable. Rename the status variable c to code throughout main, including its
declaration, assignments, checks, and messages, while preserving the existing
result assertions.
🪄 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: a631587b-55ad-44cc-8af5-bfe4d336ef58

📥 Commits

Reviewing files that changed from the base of the PR and between 2411669 and 878e4ff.

📒 Files selected for processing (5)
  • examples/c/06_port_forwarding.c
  • examples/c/example_common.h
  • sdks/c/README.md
  • sdks/c/tests/CMakeLists.txt
  • sdks/c/tests/test_options_port.c

Comment on lines +58 to +59
if (code != Ok) { print_error("hostname", &error); boxlite_error_free(&error); }
printf("\n[exit %d]\n", exit_code);

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

Exit with an error status on execution failure.

If execute_and_wait fails, the example logs the error but proceeds to incorrectly print [exit 0] and eventually returns a 0 exit code. The example should clean up and return a non-zero exit code to accurately reflect the failure.

🛠️ Proposed fix
-  if (code != Ok) { print_error("hostname", &error); boxlite_error_free(&error); }
-  printf("\n[exit %d]\n", exit_code);
+  if (code != Ok) {
+    print_error("hostname", &error);
+    boxlite_error_free(&error);
+    boxlite_free_string(box_id);
+    boxlite_runtime_free(runtime);
+    return 1;
+  }
+  printf("\n[exit %d]\n", exit_code);
📝 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
if (code != Ok) { print_error("hostname", &error); boxlite_error_free(&error); }
printf("\n[exit %d]\n", exit_code);
if (code != Ok) {
print_error("hostname", &error);
boxlite_error_free(&error);
boxlite_free_string(box_id);
boxlite_runtime_free(runtime);
return 1;
}
printf("\n[exit %d]\n", exit_code);
🤖 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/c/06_port_forwarding.c` around lines 58 - 59, Update the
execute_and_wait failure branch in the port-forwarding example to print the
error, free the error, perform required cleanup, and return a non-zero status
immediately. Ensure the “[exit %d]” success path is reached only when execution
succeeds.

Comment on lines +80 to +87
static void forward(const uint8_t *data, size_t len, int is_stderr, ExecCtx *c) {
if (c->cb == NULL || len == 0) return;
char buf[4096];
size_t n = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
memcpy(buf, data, n);
buf[n] = '\0';
c->cb(buf, is_stderr, c->user_data);
}

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 | 🔴 Critical | ⚡ Quick win

Prevent data truncation for large stream chunks.

If the incoming data chunk is larger than 4095 bytes, the remaining bytes are silently dropped. Loop over the incoming data to ensure the entire chunk is forwarded.

🐛 Proposed fix to loop over the buffer
 static void forward(const uint8_t *data, size_t len, int is_stderr, ExecCtx *c) {
   if (c->cb == NULL || len == 0) return;
   char buf[4096];
-  size_t n = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
-  memcpy(buf, data, n);
-  buf[n] = '\0';
-  c->cb(buf, is_stderr, c->user_data);
+  size_t offset = 0;
+  while (offset < len) {
+    size_t chunk = (len - offset) < (sizeof(buf) - 1) ? (len - offset) : (sizeof(buf) - 1);
+    memcpy(buf, data + offset, chunk);
+    buf[chunk] = '\0';
+    c->cb(buf, is_stderr, c->user_data);
+    offset += chunk;
+  }
 }
📝 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
static void forward(const uint8_t *data, size_t len, int is_stderr, ExecCtx *c) {
if (c->cb == NULL || len == 0) return;
char buf[4096];
size_t n = len < sizeof(buf) - 1 ? len : sizeof(buf) - 1;
memcpy(buf, data, n);
buf[n] = '\0';
c->cb(buf, is_stderr, c->user_data);
}
static void forward(const uint8_t *data, size_t len, int is_stderr, ExecCtx *c) {
if (c->cb == NULL || len == 0) return;
char buf[4096];
size_t offset = 0;
while (offset < len) {
size_t chunk = (len - offset) < (sizeof(buf) - 1) ? (len - offset) : (sizeof(buf) - 1);
memcpy(buf, data + offset, chunk);
buf[chunk] = '\0';
c->cb(buf, is_stderr, c->user_data);
offset += chunk;
}
}
🤖 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/c/example_common.h` around lines 80 - 87, Update the forward
function to process data in multiple chunks instead of truncating input beyond
the fixed buffer size. Loop until all len bytes are forwarded, copying each
segment into buf, null-terminating it, and invoking c->cb with the original
stream and user-data context.

Comment thread sdks/c/README.md
Comment on lines +46 to +52
- �?Structured error handling (error codes + messages)
- �?OCI container images
- �?Hardware-accelerated VMs (KVM/Hypervisor.framework)
- �?Command execution with streaming output
- �?Box lifecycle management
- �?Performance metrics
- �?Multi-box management

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='sdks/c/README.md'

echo '--- file type ---'
file "$file" || true

echo
echo '--- line numbers around reported ranges ---'
sed -n '40,60p;310,330p;650,675p;715,725p;790,805p;845,855p;870,880p;998,1015p' "$file" | cat -n

echo
echo '--- suspect characters (show unicode code points via Python) ---'
python3 - <<'PY'
from pathlib import Path
p = Path('sdks/c/README.md')
text = p.read_text(encoding='utf-8', errors='replace')
targets = ['�', '�?']
for i, line in enumerate(text.splitlines(), 1):
    if '�' in line:
        print(f'LINE {i}: {line}')
        print('  codepoints:', ' '.join(f'U+{ord(ch):04X}' for ch in line))
PY

echo
echo '--- raw bytes around first occurrence ---'
python3 - <<'PY'
from pathlib import Path
data = Path('sdks/c/README.md').read_bytes()
needle = '�'.encode('utf-8', 'replace')  # placeholder; not used
# search for utf-8 replacement char bytes EF BF BD
pat = b'\xef\xbf\xbd'
idx = data.find(pat)
print('first replacement-char-byte-index:', idx)
if idx != -1:
    start = max(0, idx - 80)
    end = min(len(data), idx + 160)
    chunk = data[start:end]
    print(chunk)
    print('hex:', chunk.hex(' '))
PY

Repository: boxlite-ai/boxlite

Length of output: 18589


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='sdks/c/README.md'

echo '--- line numbers around reported ranges ---'
sed -n '40,60p;310,330p;650,675p;715,725p;790,805p;845,855p;870,880p;998,1015p' "$file" | cat -n

echo
echo '--- suspect characters (show unicode code points via Python) ---'
python3 - <<'PY'
from pathlib import Path
p = Path('sdks/c/README.md')
text = p.read_text(encoding='utf-8', errors='replace')
for i, line in enumerate(text.splitlines(), 1):
    if '�' in line:
        print(f'LINE {i}: {line}')
        print('  codepoints:', ' '.join(f'U+{ord(ch):04X}' for ch in line))
PY

echo
echo '--- raw bytes around first occurrence ---'
python3 - <<'PY'
from pathlib import Path
data = Path('sdks/c/README.md').read_bytes()
pat = b'\xef\xbf\xbd'
idx = data.find(pat)
print('first replacement-char-byte-index:', idx)
if idx != -1:
    start = max(0, idx - 80)
    end = min(len(data), idx + 160)
    chunk = data[start:end]
    print(chunk)
    print('hex:', chunk.hex(' '))
PY

Repository: boxlite-ai/boxlite

Length of output: 18532


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='sdks/c/README.md'

echo '--- file type ---'
file "$file" || true

echo
echo '--- reported ranges ---'
for range in '40,60p' '310,330p' '650,675p' '715,725p' '790,805p' '845,855p' '870,880p' '998,1015p'; do
  echo "### sed -n $range"
  sed -n "$range" "$file" | cat -n
done

echo
echo '--- lines containing U+FFFD replacement char ---'
python3 - <<'PY'
from pathlib import Path
text = Path('sdks/c/README.md').read_text(encoding='utf-8', errors='replace')
for i, line in enumerate(text.splitlines(), 1):
    if '\ufffd' in line:
        print(f'{i}: {line}')
PY

echo
echo '--- byte scan for UTF-8 replacement char sequence EF BF BD ---'
python3 - <<'PY'
from pathlib import Path
data = Path('sdks/c/README.md').read_bytes()
pat = b'\xef\xbf\xbd'
count = data.count(pat)
print('count:', count)
if count:
    idx = data.find(pat)
    print('first_index:', idx)
    start = max(0, idx - 64)
    end = min(len(data), idx + 128)
    chunk = data[start:end]
    print(chunk.hex(' '))
PY

Repository: boxlite-ai/boxlite

Length of output: 7175


Fix the corrupted README glyphs. Multiple sections still render as �? — the feature list, memory-management mappings, threading note, platform table, migration headings, and architecture diagram. Replace them with valid UTF-8 glyphs or plain ASCII equivalents.

🤖 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 `@sdks/c/README.md` around lines 46 - 52, Replace all corrupted `�?` glyphs
throughout README.md—including the feature list, memory-management mappings,
threading note, platform table, migration headings, and architecture
diagram—with valid UTF-8 symbols or plain ASCII equivalents, preserving the
surrounding content and formatting.

Comment on lines +20 to +64
// Free `err` only when a call failed; matches the README convention and keeps
// the test free of double-free concerns across successive assertions.
#define FREE_IF_ERR(code, err) do { if ((code) != Ok) boxlite_error_free(&(err)); } while (0)

int main(void) {
CBoxliteOptions *opts = NULL;
CBoxliteError err = {0};

// Build an options object — no runtime, no VM, no I/O.
BoxliteErrorCode c = boxlite_options_new("alpine:3.19", &opts, &err);
CHECK(c == Ok, "boxlite_options_new succeeds");
if (c != Ok) { boxlite_error_free(&err); return 1; }

// host_port = 0 means "same as guest_port" (per boxlite.h) — valid.
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, /*host_port*/ 0, /*guest_port*/ 8080,
BoxlitePortProtocolTcp, /*host_ip*/ NULL);
CHECK(c == Ok, "add_port(host=0, guest=8080, Tcp, NULL) -> Ok");
FREE_IF_ERR(c, err);

// Explicit host bind + UDP — valid.
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 9090, 90, BoxlitePortProtocolUdp, "127.0.0.1");
CHECK(c == Ok, "add_port(9090, 90, Udp, 127.0.0.1) -> Ok");
FREE_IF_ERR(c, err);

// guest_port == 0 is invalid (guest_port required, 1..65535).
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 0, 0, BoxlitePortProtocolTcp, NULL);
CHECK(c == InvalidArgument, "add_port guest_port=0 -> InvalidArgument");
FREE_IF_ERR(c, err);

// NULL opts must be rejected, not crash.
err = (CBoxliteError){0};
c = boxlite_options_add_port(NULL, 8080, 80, BoxlitePortProtocolTcp, NULL);
CHECK(c == InvalidArgument, "add_port NULL opts -> InvalidArgument");
FREE_IF_ERR(c, err);

// Non-UTF-8 host_ip must be rejected (header contract).
const char bad_utf8[] = { (char)0xff, (char)0xfe, 0 };
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 8080, 80, BoxlitePortProtocolTcp, bad_utf8);
CHECK(c == InvalidArgument, "add_port non-UTF-8 host_ip -> InvalidArgument");
FREE_IF_ERR(c, err);

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

Remove unnecessary error-freeing logic and use meaningful variable names.

boxlite_options_add_port does not take a CBoxliteError output parameter. Resetting err to {0} and attempting to free it via FREE_IF_ERR is logically incorrect and misleading, as the error struct is never populated by these calls. As per coding guidelines, use meaningful variable and function names that clearly describe their purpose by renaming c to code (or status).

♻️ Proposed fixes
-// Free `err` only when a call failed; matches the README convention and keeps
-// the test free of double-free concerns across successive assertions.
-#define FREE_IF_ERR(code, err) do { if ((code) != Ok) boxlite_error_free(&(err)); } while (0)
-
 int main(void) {
     CBoxliteOptions *opts = NULL;
     CBoxliteError err = {0};
 
     // Build an options object — no runtime, no VM, no I/O.
-    BoxliteErrorCode c = boxlite_options_new("alpine:3.19", &opts, &err);
-    CHECK(c == Ok, "boxlite_options_new succeeds");
-    if (c != Ok) { boxlite_error_free(&err); return 1; }
+    BoxliteErrorCode code = boxlite_options_new("alpine:3.19", &opts, &err);
+    CHECK(code == Ok, "boxlite_options_new succeeds");
+    if (code != Ok) { boxlite_error_free(&err); return 1; }
 
     // host_port = 0 means "same as guest_port" (per boxlite.h) — valid.
-    err = (CBoxliteError){0};
-    c = boxlite_options_add_port(opts, /*host_port*/ 0, /*guest_port*/ 8080,
-                                 BoxlitePortProtocolTcp, /*host_ip*/ NULL);
-    CHECK(c == Ok, "add_port(host=0, guest=8080, Tcp, NULL) -> Ok");
-    FREE_IF_ERR(c, err);
+    code = boxlite_options_add_port(opts, /*host_port*/ 0, /*guest_port*/ 8080,
+                                    BoxlitePortProtocolTcp, /*host_ip*/ NULL);
+    CHECK(code == Ok, "add_port(host=0, guest=8080, Tcp, NULL) -> Ok");
 
     // Explicit host bind + UDP — valid.
-    err = (CBoxliteError){0};
-    c = boxlite_options_add_port(opts, 9090, 90, BoxlitePortProtocolUdp, "127.0.0.1");
-    CHECK(c == Ok, "add_port(9090, 90, Udp, 127.0.0.1) -> Ok");
-    FREE_IF_ERR(c, err);
+    code = boxlite_options_add_port(opts, 9090, 90, BoxlitePortProtocolUdp, "127.0.0.1");
+    CHECK(code == Ok, "add_port(9090, 90, Udp, 127.0.0.1) -> Ok");
 
     // guest_port == 0 is invalid (guest_port required, 1..65535).
-    err = (CBoxliteError){0};
-    c = boxlite_options_add_port(opts, 0, 0, BoxlitePortProtocolTcp, NULL);
-    CHECK(c == InvalidArgument, "add_port guest_port=0 -> InvalidArgument");
-    FREE_IF_ERR(c, err);
+    code = boxlite_options_add_port(opts, 0, 0, BoxlitePortProtocolTcp, NULL);
+    CHECK(code == InvalidArgument, "add_port guest_port=0 -> InvalidArgument");
 
     // NULL opts must be rejected, not crash.
-    err = (CBoxliteError){0};
-    c = boxlite_options_add_port(NULL, 8080, 80, BoxlitePortProtocolTcp, NULL);
-    CHECK(c == InvalidArgument, "add_port NULL opts -> InvalidArgument");
-    FREE_IF_ERR(c, err);
+    code = boxlite_options_add_port(NULL, 8080, 80, BoxlitePortProtocolTcp, NULL);
+    CHECK(code == InvalidArgument, "add_port NULL opts -> InvalidArgument");
 
     // Non-UTF-8 host_ip must be rejected (header contract).
     const char bad_utf8[] = { (char)0xff, (char)0xfe, 0 };
-    err = (CBoxliteError){0};
-    c = boxlite_options_add_port(opts, 8080, 80, BoxlitePortProtocolTcp, bad_utf8);
-    CHECK(c == InvalidArgument, "add_port non-UTF-8 host_ip -> InvalidArgument");
-    FREE_IF_ERR(c, err);
+    code = boxlite_options_add_port(opts, 8080, 80, BoxlitePortProtocolTcp, bad_utf8);
+    CHECK(code == InvalidArgument, "add_port non-UTF-8 host_ip -> InvalidArgument");
📝 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
// Free `err` only when a call failed; matches the README convention and keeps
// the test free of double-free concerns across successive assertions.
#define FREE_IF_ERR(code, err) do { if ((code) != Ok) boxlite_error_free(&(err)); } while (0)
int main(void) {
CBoxliteOptions *opts = NULL;
CBoxliteError err = {0};
// Build an options object — no runtime, no VM, no I/O.
BoxliteErrorCode c = boxlite_options_new("alpine:3.19", &opts, &err);
CHECK(c == Ok, "boxlite_options_new succeeds");
if (c != Ok) { boxlite_error_free(&err); return 1; }
// host_port = 0 means "same as guest_port" (per boxlite.h) — valid.
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, /*host_port*/ 0, /*guest_port*/ 8080,
BoxlitePortProtocolTcp, /*host_ip*/ NULL);
CHECK(c == Ok, "add_port(host=0, guest=8080, Tcp, NULL) -> Ok");
FREE_IF_ERR(c, err);
// Explicit host bind + UDP — valid.
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 9090, 90, BoxlitePortProtocolUdp, "127.0.0.1");
CHECK(c == Ok, "add_port(9090, 90, Udp, 127.0.0.1) -> Ok");
FREE_IF_ERR(c, err);
// guest_port == 0 is invalid (guest_port required, 1..65535).
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 0, 0, BoxlitePortProtocolTcp, NULL);
CHECK(c == InvalidArgument, "add_port guest_port=0 -> InvalidArgument");
FREE_IF_ERR(c, err);
// NULL opts must be rejected, not crash.
err = (CBoxliteError){0};
c = boxlite_options_add_port(NULL, 8080, 80, BoxlitePortProtocolTcp, NULL);
CHECK(c == InvalidArgument, "add_port NULL opts -> InvalidArgument");
FREE_IF_ERR(c, err);
// Non-UTF-8 host_ip must be rejected (header contract).
const char bad_utf8[] = { (char)0xff, (char)0xfe, 0 };
err = (CBoxliteError){0};
c = boxlite_options_add_port(opts, 8080, 80, BoxlitePortProtocolTcp, bad_utf8);
CHECK(c == InvalidArgument, "add_port non-UTF-8 host_ip -> InvalidArgument");
FREE_IF_ERR(c, err);
int main(void) {
CBoxliteOptions *opts = NULL;
CBoxliteError err = {0};
// Build an options object — no runtime, no VM, no I/O.
BoxliteErrorCode code = boxlite_options_new("alpine:3.19", &opts, &err);
CHECK(code == Ok, "boxlite_options_new succeeds");
if (code != Ok) { boxlite_error_free(&err); return 1; }
// host_port = 0 means "same as guest_port" (per boxlite.h) — valid.
code = boxlite_options_add_port(opts, /*host_port*/ 0, /*guest_port*/ 8080,
BoxlitePortProtocolTcp, /*host_ip*/ NULL);
CHECK(code == Ok, "add_port(host=0, guest=8080, Tcp, NULL) -> Ok");
// Explicit host bind + UDP — valid.
code = boxlite_options_add_port(opts, 9090, 90, BoxlitePortProtocolUdp, "127.0.0.1");
CHECK(code == Ok, "add_port(9090, 90, Udp, 127.0.0.1) -> Ok");
// guest_port == 0 is invalid (guest_port required, 1..65535).
code = boxlite_options_add_port(opts, 0, 0, BoxlitePortProtocolTcp, NULL);
CHECK(code == InvalidArgument, "add_port guest_port=0 -> InvalidArgument");
// NULL opts must be rejected, not crash.
code = boxlite_options_add_port(NULL, 8080, 80, BoxlitePortProtocolTcp, NULL);
CHECK(code == InvalidArgument, "add_port NULL opts -> InvalidArgument");
// Non-UTF-8 host_ip must be rejected (header contract).
const char bad_utf8[] = { (char)0xff, (char)0xfe, 0 };
code = boxlite_options_add_port(opts, 8080, 80, BoxlitePortProtocolTcp, bad_utf8);
CHECK(code == InvalidArgument, "add_port non-UTF-8 host_ip -> InvalidArgument");
🤖 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 `@sdks/c/tests/test_options_port.c` around lines 20 - 64, Remove the err
resets, FREE_IF_ERR macro, and related calls from the boxlite_options_add_port
assertions because that API does not populate a CBoxliteError; retain error
cleanup only for boxlite_options_new where applicable. Rename the status
variable c to code throughout main, including its declaration, assignments,
checks, and messages, while preserving the existing result assertions.

Source: Coding guidelines

@DorianZheng

Copy link
Copy Markdown
Member

Hi @xxxzl114514. Thanks for filing this PR. Please take a look at the comments

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.

C SDK: example_common.h no longer compiles against the post-and-drain callback API; boxlite_options_add_port/add_volume have no coverage

2 participants