Skip to content

Add typed native handle drops - #27

Merged
loreste merged 4 commits into
loreste:mainfrom
attahn:fix/typed-native-handle-drops
Jul 29, 2026
Merged

Add typed native handle drops#27
loreste merged 4 commits into
loreste:mainfrom
attahn:fix/typed-native-handle-drops

Conversation

@attahn

@attahn attahn commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Adds typed ownership for runtime-managed native handles while keeping foreign handles on their explicit close paths.

Interface boxes are reference-counted and use compiler-generated recursive destructors. HTTP requests are deep-cloned, struct-key maps release owned keys and values across their lifecycle, and unsupported metadata layouts now fail at compile time.

Verified with:

  • cargo test --bin mako — 136 passed
  • Native owned-handle fixture under AddressSanitizer and LeakSanitizer
  • Native struct-key map suite under AddressSanitizer

Summary by CodeRabbit

  • Bug Fixes
    • Improved typed cleanup for owned opaque handles, including interfaces, HTTP requests, and nested struct data.
    • Fixed struct-keyed map behavior on copy/overwrite/delete/clear/final release, including deep cloning/dropping of typed values and keys.
    • Preserved explicit-close behavior for foreign handles; adjusted native pointer-slice slicing to use non-owning views.
  • Tests
    • Added native ownership coverage via a new owned-handle drop example/test.
    • Expanded Linux LeakSanitizer/AddressSanitizer validation and stricter sanitizer gate failures.
  • Documentation
    • Updated memory-safety guidance and marked the typed opaque-drop roadmap item as complete.

@attahn

attahn commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai[bot] please review

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The change adds typed owned-opaque values for interfaces and HTTP requests, emits backend-specific clone/drop operations, deepens struct-key map ownership handling, adds native ownership fixtures and sanitizer coverage, and updates memory-safety documentation.

Changes

Native ownership model

Layer / File(s) Summary
Owned opaque IR and ownership lowering
src/native_ir.rs
Adds owned interface and HTTP request types, typed drop helpers, ownership-aware conversions, iteration behavior, and intrinsic lowering.
Typed struct-key map operations
src/native_runtime.c, src/native_ir.rs
Adds metadata-driven cloning and dropping for struct-key map keys and values across mutation, copying, clearing, extraction, equality, and destruction.
Native ABI and backend support
runtime/native_bridge.c, src/llvm_codegen.rs, src/native_codegen.rs
Adds reference-counted interface boxes and HTTP request clone/drop shims, and emits typed owned-opaque clone/drop calls in both native backends.
Ownership validation and memory-safety coverage
examples/native/owned_handle_drop/*, scripts/memory-safety-gate.sh, src/native_ir.rs, CHANGELOG.md, docs/*
Adds ownership fixtures and compiler tests, runs native LeakSanitizer and AddressSanitizer cases, and documents the typed-drop behavior.

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

Sequence Diagram(s)

sequenceDiagram
  participant NativeIR
  participant NativeCodegen
  participant NativeBridge
  participant NativeRuntime
  participant OwnershipFixture
  NativeIR->>NativeCodegen: emit typed clone/drop operations
  NativeCodegen->>NativeBridge: invoke interface or HTTP request helpers
  NativeCodegen->>NativeRuntime: invoke typed struct-map operations
  NativeBridge->>NativeBridge: release owned payloads
  NativeRuntime->>NativeRuntime: clone or drop map keys and values
  OwnershipFixture->>NativeIR: compile ownership scenarios
Loading

Suggested reviewers: loreste

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding typed drops for native handles.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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
🧪 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.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

@attahn I’ll review the changes in #27, with particular attention to ownership transfer, reference counting, recursive destruction, and native-handle cleanup paths.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
src/llvm_codegen.rs (1)

945-991: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Inst::NullHeap has no OwnedOpaque arm.

Lines 94 and 358 add OwnedOpaque to the pointer-ABI groups, but NullHeap still falls into the _ => error arm, so any IR that null-initializes an owned-opaque field (inactive enum slot, zero-value struct field) would fail codegen with "NullHeap only applies to heap types". Cheap to include it in the null-pointer group if that path is reachable.

🛡️ Proposed change
                 | Type::ShareInt
                 | Type::FloatSlice
                 | Type::ByteSlice
+                | Type::OwnedOpaque(_)
                 | Type::BoolSlice => {
                     context.ptr_type(Default::default()).const_null().into()
                 }
🤖 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/llvm_codegen.rs` around lines 945 - 991, Update the Inst::NullHeap type
match to include Type::OwnedOpaque in the existing null-pointer group alongside
the other pointer-ABI heap types. Preserve the current null-pointer value
construction and unsupported-type error behavior for all other types.
🧹 Nitpick comments (3)
src/native_ir.rs (1)

30306-30337: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Name the drop-kind codes shared with the runtime.

The 1..=8 codes here form an untyped contract with runtime/native_runtime.c. The OwnedOpaque arms are spelled out per OpaqueKind, so a new opaque kind is a compile error — good — but any new heap-carrying MapValKind silently falls into _ => (0, None), i.e. "no drop", which leaks rather than failing loudly.

Consider a small #[repr(i64)] enum ValueDropKind (or const set) mirrored by named constants on the C side so the two sides can't drift.

🤖 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/native_ir.rs` around lines 30306 - 30337, Replace the untyped drop-kind
literals in struct_map_value_meta with a shared, explicitly named ValueDropKind
representation covering codes 1 through 8, and mirror those named values in
runtime/native_runtime.c. Ensure every heap-carrying MapValKind is matched
explicitly so unsupported additions fail at compile time instead of falling
through to the no-drop code 0.
runtime/native_runtime.c (2)

1616-1623: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mako_native_struct_drop_typed is a pure alias for mako_native_struct_key_free.

If the only purpose is an exported name for the compiler-emitted drop, consider documenting that explicitly (or exporting mako_native_struct_key_free with a comment) so future readers don't add divergent behavior to one of the two.

🤖 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 `@runtime/native_runtime.c` around lines 1616 - 1623, Document that
mako_native_struct_drop_typed intentionally aliases mako_native_struct_key_free
for the compiler-emitted drop operation, and clarify that it must remain
behaviorally identical rather than gaining separate logic.

1637-1655: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

mako_native_struct_slice_clone_ptr and mako_native_ptr_slice_clone_typed are the same function modulo the element callback.

Same for the two drop variants at Lines 1657-1672 and 1785-1801. struct_slice_clone_ptr is just ptr_slice_clone_typed with value_kind == 2; the typed variants could subsume both.

♻️ Sketch
-MakoNativeStructSlice *mako_native_struct_slice_clone_ptr(
-    const MakoNativeStructSlice *in, int64_t nfields, int64_t str_mask,
-    int64_t nest_mask, int64_t nest_nf_pack, int64_t nest_sm_pack
-) { ... }
+/* struct element slices are just typed slices with value_kind == 2 */
+#define mako_native_struct_slice_clone_ptr(in, nf, sm, nm, nnf, nsm) \
+    mako_native_ptr_slice_clone_typed((in), 2, (nf), (sm), (nm), (nnf), (nsm))

Also applies to: 1763-1783

🤖 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 `@runtime/native_runtime.c` around lines 1637 - 1655, Consolidate the
duplicated cloning and dropping logic by making
mako_native_struct_slice_clone_ptr delegate to mako_native_ptr_slice_clone_typed
with value_kind 2, and apply the same delegation to the corresponding struct and
typed drop functions. Preserve all existing arguments, ownership behavior,
allocation handling, and element-cloning semantics while removing the duplicate
implementations.
🤖 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 `@CHANGELOG.md`:
- Around line 118-126: Update the earlier native-backend paragraph in the
changelog to scope its sanitizer limitation accurately, removing or qualifying
the claim that sanitizers never run and that --sanitize is universally rejected.
Keep the newer statement about native fixtures passing under LeakSanitizer and
AddressSanitizer consistent with the revised wording.

In `@scripts/memory-safety-gate.sh`:
- Around line 124-127: Replace the fixed /tmp/mako-ms-native-asan.out path in
the native ASan test block with a securely created mktemp file, reuse that path
for both command output redirection and the tail command, and clean it up after
use.

In `@src/native_codegen.rs`:
- Line 1730: Update the new declarations in the surrounding match arms,
including the lines calling module.declare_func_in_func, to pass the function
builder in the same non-borrowed form used by neighboring arms. Remove the
unnecessary borrow while preserving the existing function declaration behavior
and avoiding Clippy’s needless_borrow warning.

---

Outside diff comments:
In `@src/llvm_codegen.rs`:
- Around line 945-991: Update the Inst::NullHeap type match to include
Type::OwnedOpaque in the existing null-pointer group alongside the other
pointer-ABI heap types. Preserve the current null-pointer value construction and
unsupported-type error behavior for all other types.

---

Nitpick comments:
In `@runtime/native_runtime.c`:
- Around line 1616-1623: Document that mako_native_struct_drop_typed
intentionally aliases mako_native_struct_key_free for the compiler-emitted drop
operation, and clarify that it must remain behaviorally identical rather than
gaining separate logic.
- Around line 1637-1655: Consolidate the duplicated cloning and dropping logic
by making mako_native_struct_slice_clone_ptr delegate to
mako_native_ptr_slice_clone_typed with value_kind 2, and apply the same
delegation to the corresponding struct and typed drop functions. Preserve all
existing arguments, ownership behavior, allocation handling, and element-cloning
semantics while removing the duplicate implementations.

In `@src/native_ir.rs`:
- Around line 30306-30337: Replace the untyped drop-kind literals in
struct_map_value_meta with a shared, explicitly named ValueDropKind
representation covering codes 1 through 8, and mirror those named values in
runtime/native_runtime.c. Ensure every heap-carrying MapValKind is matched
explicitly so unsupported additions fail at compile time instead of falling
through to the no-drop code 0.
🪄 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: 68a90f0d-02f1-4d2f-add0-f373913c078f

📥 Commits

Reviewing files that changed from the base of the PR and between f428492 and a9b189b.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/MEMORY_SAFETY.md
  • docs/ROADMAP.md
  • examples/native/owned_handle_drop/native_owned_handle_drop_test.mko
  • runtime/native_bridge.c
  • runtime/native_runtime.c
  • scripts/memory-safety-gate.sh
  • src/llvm_codegen.rs
  • src/native_codegen.rs
  • src/native_ir.rs

Comment thread CHANGELOG.md
Comment on lines +124 to +127
if ! "$mako_bin" test "$repo_dir/$f" --backend native --sanitize address \
>/tmp/mako-ms-native-asan.out 2>&1; then
echo "memory-safety-gate: native ASan FAILED on $f" >&2
tail -20 /tmp/mako-ms-native-asan.out >&2 || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Use mktemp instead of a fixed /tmp path.

A predictable path is symlink/TOCTOU-prone on shared hosts; a pre-created symlink could redirect the log write.

🛡️ Proposed fix
+  ms_asan_log="$(mktemp)"
   for f in examples/native/owned_handle_drop/native_owned_handle_drop_test.mko \
            examples/testing/map_struct_key_test.mko; do
     if ! "$mako_bin" test "$repo_dir/$f" --backend native --sanitize address \
-         >/tmp/mako-ms-native-asan.out 2>&1; then
+         >"$ms_asan_log" 2>&1; then
       echo "memory-safety-gate: native ASan FAILED on $f" >&2
-      tail -20 /tmp/mako-ms-native-asan.out >&2 || true
+      tail -20 "$ms_asan_log" >&2 || true
📝 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 ! "$mako_bin" test "$repo_dir/$f" --backend native --sanitize address \
>/tmp/mako-ms-native-asan.out 2>&1; then
echo "memory-safety-gate: native ASan FAILED on $f" >&2
tail -20 /tmp/mako-ms-native-asan.out >&2 || true
ms_asan_log="$(mktemp)"
for f in examples/native/owned_handle_drop/native_owned_handle_drop_test.mko \
examples/testing/map_struct_key_test.mko; do
if ! "$mako_bin" test "$repo_dir/$f" --backend native --sanitize address \
>"$ms_asan_log" 2>&1; then
echo "memory-safety-gate: native ASan FAILED on $f" >&2
tail -20 "$ms_asan_log" >&2 || true
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 124-124: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/mako-ms-native-asan.out
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)


[warning] 126-126: Writing to or reading from a hardcoded, predictable path under /tmp is vulnerable to symlink and TOCTOU attacks: a local attacker can pre-create the file (or a symlink pointing elsewhere) and hijack or corrupt the contents. Generate a unique, unpredictable temporary file with mktemp instead, e.g. tmpfile="$(mktemp)" (or mktemp -d for directories) and reference "$tmpfile".
Context: /tmp/mako-ms-native-asan.out
Note: [CWE-377] Insecure Temporary File.

(predictable-tmp-file-bash)

🤖 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 `@scripts/memory-safety-gate.sh` around lines 124 - 127, Replace the fixed
/tmp/mako-ms-native-asan.out path in the native ASan test block with a securely
created mktemp file, reuse that path for both command output redirection and the
tail command, and clean it up after use.

Source: Linters/SAST tools

Comment thread src/native_codegen.rs
@attahn
attahn force-pushed the fix/typed-native-handle-drops branch from a9b189b to 5c29494 Compare July 26, 2026 15:38

@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: 3

🧹 Nitpick comments (2)
runtime/native_runtime.c (2)

2139-2143: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Kind 7 (interface) falls through to default: return 0 — make the intent explicit.

Every other kind in the 1-8 range is enumerated, so the omission reads as an oversight rather than a deliberate "interfaces compare by identity only" choice (which the a == b fast path at Line 2089 already provides). An explicit case 7: with a one-line comment would settle it.

🤖 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 `@runtime/native_runtime.c` around lines 2139 - 2143, Update the kind-dispatch
switch containing cases 1–8 to add an explicit case 7 before the default branch,
preserving its existing return-0 behavior and documenting that interfaces
compare by identity only; leave the existing a == b fast path and other kind
handlers unchanged.

1632-1636: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a shared header for these cross-TU prototypes.

These hand-written declarations must stay byte-identical with runtime/native_bridge.c (mako_native_iface_clone, mako_native_iface_drop, mako_native_http_request_{clone,eq,drop}_ptr). A divergence compiles cleanly and breaks only at link/run time. A shared header would let the compiler enforce it.

🤖 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 `@runtime/native_runtime.c` around lines 1632 - 1636, The
cross-translation-unit prototypes around mako_native_iface_clone,
mako_native_iface_drop, and mako_native_http_request_{clone,eq,drop}_ptr are
duplicated and can diverge. Move these declarations into a shared header,
include it from both runtime/native_runtime.c and runtime/native_bridge.c, and
remove the hand-written duplicates while preserving the existing signatures.
🤖 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 `@runtime/native_runtime.c`:
- Around line 2883-2896: Fix ownership handling in the maps_values_struct_key
result: do not mark the returned slice as owned while val_kind 3–6 elements
remain shared views. Either clone those kinds before storing them, or set
out->owned consistently so mako_native_ptr_slice_drop_typed skips borrowed
elements; preserve safe cleanup for all other value kinds.
- Around line 2775-2777: In runtime/native_runtime.c lines 2775-2777, update
mako_native_maps_keys_struct_ptr to check !m || !m->state before calling
mako_native_map_ii_len(m), returning an empty int slice through the existing
allocation path. Apply the same ordering fix in
mako_native_maps_values_struct_key at lines 2876-2884, preserving the
empty-slice early return and preventing dereference of an invalid map.
- Around line 2013-2039: Add an early return for dst == src at the start of the
struct-map copy/purge function, before the destination cleanup loop. Preserve
the existing behavior for distinct source and destination maps, including null
handling and the subsequent copy flow.

---

Nitpick comments:
In `@runtime/native_runtime.c`:
- Around line 2139-2143: Update the kind-dispatch switch containing cases 1–8 to
add an explicit case 7 before the default branch, preserving its existing
return-0 behavior and documenting that interfaces compare by identity only;
leave the existing a == b fast path and other kind handlers unchanged.
- Around line 1632-1636: The cross-translation-unit prototypes around
mako_native_iface_clone, mako_native_iface_drop, and
mako_native_http_request_{clone,eq,drop}_ptr are duplicated and can diverge.
Move these declarations into a shared header, include it from both
runtime/native_runtime.c and runtime/native_bridge.c, and remove the
hand-written duplicates while preserving the existing signatures.
🪄 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: 6f04c6b7-9c66-4bfb-b394-2944fbc66249

📥 Commits

Reviewing files that changed from the base of the PR and between a9b189b and 0c9805f.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • docs/MEMORY_SAFETY.md
  • docs/ROADMAP.md
  • examples/native/owned_handle_drop/native_owned_handle_drop_test.mko
  • runtime/native_bridge.c
  • runtime/native_runtime.c
  • scripts/memory-safety-gate.sh
  • src/llvm_codegen.rs
  • src/native_codegen.rs
  • src/native_ir.rs
🚧 Files skipped from review as they are similar to previous changes (7)
  • CHANGELOG.md
  • docs/ROADMAP.md
  • docs/MEMORY_SAFETY.md
  • src/llvm_codegen.rs
  • examples/native/owned_handle_drop/native_owned_handle_drop_test.mko
  • runtime/native_bridge.c
  • src/native_ir.rs

Comment thread runtime/native_runtime.c
Comment on lines +2013 to 2039
int64_t nest_mask, int64_t nest_nf_pack, int64_t nest_sm_pack,
int64_t val_kind, int64_t val_nfields, int64_t val_str_mask,
int64_t val_nest_mask, int64_t val_nest_nf_pack, int64_t val_nest_sm_pack
) {
if (dst && dst->state) {
uint8_t *st = mako_native_map_ii_state(dst);
int64_t *dk = mako_native_map_ii_keys(dst);
int64_t *dv = mako_native_map_ii_vals(dst);
for (size_t i = 0; i < dst->cap; ++i) {
if (st[i] == MAKO_NMAP_FULL) {
mako_native_struct_key_free(
(void *)(intptr_t)dk[i], nfields, str_mask,
nest_mask, nest_nf_pack, nest_sm_pack
);
mako_native_struct_map_value_drop(
dv[i], val_kind, val_nfields, val_str_mask,
val_nest_mask, val_nest_nf_pack, val_nest_sm_pack
);
dk[i] = 0;
dv[i] = 0;
}
st[i] = MAKO_NMAP_EMPTY;
}
if (dst->lenp) *dst->lenp = 0;
dst->tombs = 0;
}
if (!src || !dst) return;

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 | 🟡 Minor | ⚡ Quick win

Add a dst == src short-circuit.

The purge loop frees every destination key/value and marks all buckets EMPTY before the copy loop runs. If dst == src, the copy loop then sees only EMPTY buckets and the map is silently wiped instead of left unchanged. Cheap to guard.

🛡️ Proposed fix
+    if (dst == src) return;
     if (dst && dst->state) {
         uint8_t *st = mako_native_map_ii_state(dst);
📝 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
int64_t nest_mask, int64_t nest_nf_pack, int64_t nest_sm_pack,
int64_t val_kind, int64_t val_nfields, int64_t val_str_mask,
int64_t val_nest_mask, int64_t val_nest_nf_pack, int64_t val_nest_sm_pack
) {
if (dst && dst->state) {
uint8_t *st = mako_native_map_ii_state(dst);
int64_t *dk = mako_native_map_ii_keys(dst);
int64_t *dv = mako_native_map_ii_vals(dst);
for (size_t i = 0; i < dst->cap; ++i) {
if (st[i] == MAKO_NMAP_FULL) {
mako_native_struct_key_free(
(void *)(intptr_t)dk[i], nfields, str_mask,
nest_mask, nest_nf_pack, nest_sm_pack
);
mako_native_struct_map_value_drop(
dv[i], val_kind, val_nfields, val_str_mask,
val_nest_mask, val_nest_nf_pack, val_nest_sm_pack
);
dk[i] = 0;
dv[i] = 0;
}
st[i] = MAKO_NMAP_EMPTY;
}
if (dst->lenp) *dst->lenp = 0;
dst->tombs = 0;
}
if (!src || !dst) return;
if (dst == src) return;
if (dst && dst->state) {
uint8_t *st = mako_native_map_ii_state(dst);
int64_t *dk = mako_native_map_ii_keys(dst);
int64_t *dv = mako_native_map_ii_vals(dst);
for (size_t i = 0; i < dst->cap; ++i) {
if (st[i] == MAKO_NMAP_FULL) {
mako_native_struct_key_free(
(void *)(intptr_t)dk[i], nfields, str_mask,
nest_mask, nest_nf_pack, nest_sm_pack
);
mako_native_struct_map_value_drop(
dv[i], val_kind, val_nfields, val_str_mask,
val_nest_mask, val_nest_nf_pack, val_nest_sm_pack
);
dk[i] = 0;
dv[i] = 0;
}
st[i] = MAKO_NMAP_EMPTY;
}
if (dst->lenp) *dst->lenp = 0;
dst->tombs = 0;
}
if (!src || !dst) return;
🤖 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 `@runtime/native_runtime.c` around lines 2013 - 2039, Add an early return for
dst == src at the start of the struct-map copy/purge function, before the
destination cleanup loop. Preserve the existing behavior for distinct source and
destination maps, including null handling and the subsequent copy flow.

Comment thread runtime/native_runtime.c
Comment on lines +2775 to +2777
int64_t n = (int64_t)mako_native_map_ii_len(m);
MakoNativeIntSlice *out = mako_native_int_slice_make_ptr((size_t)n, (size_t)n);
if (!m || !m->state || !n) 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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

NULL guard runs after m is already dereferenced in both new map-extraction helpers. Each function computes mako_native_map_ii_len(m) first and only then checks !m, so the guard is either dead code or arrives one dereference too late.

  • runtime/native_runtime.c#L2775-L2777: in mako_native_maps_keys_struct_ptr, hoist the !m || !m->state check above the mako_native_map_ii_len(m) call and return an empty int slice early.
  • runtime/native_runtime.c#L2876-L2884: in mako_native_maps_values_struct_key, hoist the same !m || !m->state check above the mako_native_map_ii_len(m) call at Line 2876, keeping the empty-slice allocation for the early return.
📍 Affects 1 file
  • runtime/native_runtime.c#L2775-L2777 (this comment)
  • runtime/native_runtime.c#L2876-L2884
🤖 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 `@runtime/native_runtime.c` around lines 2775 - 2777, In
runtime/native_runtime.c lines 2775-2777, update
mako_native_maps_keys_struct_ptr to check !m || !m->state before calling
mako_native_map_ii_len(m), returning an empty int slice through the existing
allocation path. Apply the same ordering fix in
mako_native_maps_values_struct_key at lines 2876-2884, preserving the
empty-slice early return and preventing dereference of an invalid map.

Comment thread runtime/native_runtime.c
Comment on lines +2883 to +2896
out->owned = 1;
if (!m || !m->state || !n) return out;
size_t j = 0;
for (size_t i = 0; i < m->cap; ++i) {
if (m->state[i] == MAKO_NMAP_FULL) {
int64_t value = m->vals[i];
/* Slice values remain shared views, matching ordinary maps_values. */
if (val_kind < 3 || val_kind > 6) {
value = mako_native_struct_map_value_clone(
value, val_kind, val_nfields, val_str_mask,
val_nest_mask, val_nest_nf_pack, val_nest_sm_pack
);
}
out->data[j++] = (void *)(intptr_t)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.

🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

out->owned = 1 contradicts leaving kinds 3-6 as shared views.

Elements for val_kind 3-6 are borrowed from the map, but the slice is marked owned. Dropping it through mako_native_ptr_slice_drop_typed (Line 1786) will deep-drop those borrowed slices, double-freeing map-owned storage. Either clone kinds 3-6 as well, or set owned = 0 for those kinds so the drop path skips element release.

#!/bin/bash
# Confirm how the returned slice from maps_values_struct_key is released.
rg -nP -C6 'maps_values_struct_key|ptr_slice_drop_typed'
🤖 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 `@runtime/native_runtime.c` around lines 2883 - 2896, Fix ownership handling in
the maps_values_struct_key result: do not mark the returned slice as owned while
val_kind 3–6 elements remain shared views. Either clone those kinds before
storing them, or set out->owned consistently so mako_native_ptr_slice_drop_typed
skips borrowed elements; preserve safe cleanup for all other value kinds.

@loreste
loreste merged commit bb06c26 into loreste:main Jul 29, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants