Skip to content

feat: expose HLIL derived-string-reference (get/set/remove) parity#36

Merged
tinysec merged 1 commit into
masterfrom
feat/hlil-derived-string-reference
Jul 8, 2026
Merged

feat: expose HLIL derived-string-reference (get/set/remove) parity#36
tinysec merged 1 commit into
masterfrom
feat/hlil-derived-string-reference

Conversation

@tinysec

@tinysec tinysec commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

Closes HIGH-severity missing-capi gap #16. Three HLIL derived-string P/Invokes were declared but had no managed wrappers, leaving the surface unreachable vs Python.

Wrapper P/Invoke Python
HighLevelILInstruction.DerivedStringReference BNGetHighLevelILDerivedStringReferenceForExpr derived_string_reference (highlevelil.py:978)
HighLevelILFunction.SetDerivedStringReferenceForExpr BNSetHighLevelILDerivedStringReferenceForExpr set_derived_string_reference_for_expr (highlevelil.py:3014)
HighLevelILFunction.RemoveDerivedStringReferenceForExpr BNRemoveHighLevelILDerivedStringReferenceForExpr remove_derived_string_reference_for_expr (highlevelil.py:3023)

Ownership notes

  • Getter: Python's _from_core_struct(str, owned=True) takes ownership of the BNStringRef* and frees it on GC. The C# projection reads the text eagerly, so it frees raw.value itself via BNFreeStringRef — closing the leak Python defers to GC.
  • Setter: Python passes owned=False (its StringRef outlives the call). Core copies/add-refs the string, so the setter creates a fresh BNStringRef*, hands the struct to core, and frees it immediately after — balancing its own allocation. The value is rebuilt because DerivedString reads eagerly and does not retain the original handle.
  • custom_type is not surfaced (the C# projection drops the recognizer handle, matching Python's read shape); documented on DerivedString.ToNativeEx.

E2E tests (harness repo)

HLILDerivedStringReferenceTests (2 tests):

  • GetReadsRealDerivedStringsAndNullsAcrossEveryExpression — sweeps every HLIL expression; cat.bndb analysis produces a real derived string (code-string location), so the found=true path is exercised on genuine analysis output, not just Set-attached refs.
  • SetGetRemoveRoundTripsOnFirstExpression — set "e2e_roundtrip" → get returns it → remove → null.

Full harness regression 58/58.

Closes HIGH missing-capi gap #16. The binding declared the three
derived-string P/Invokes (BNGet/BNSet/BNRemoveHighLevelILDerivedStringReferenceForExpr)
but exposed no managed surface, so HLIL derived strings were unreachable
vs Python (highlevelil.py:978 derived_string_reference, :3014
set_derived_string_reference_for_expr, :3023 remove_derived_string_reference_for_expr).

Add:
- HighLevelILInstruction.DerivedStringReference property (DerivedString?,
  null when the expression carries no reference). The getter owns the
  BNStringRef* core returns (Python uses owned=True; its StringRef frees
  on GC), so the binding frees it after the eager read.
- HighLevelILFunction.SetDerivedStringReferenceForExpr(expr, DerivedString),
  rebuilding the value as a fresh BNStringRef* (the projection reads eagerly
  and does not retain the core handle) and freeing it after the call; core
  copies/add-refs the string, matching Python's owned=False borrow.
- HighLevelILFunction.RemoveDerivedStringReferenceForExpr(expr).
- DerivedString.ToNativeEx() + a public value-only ctor so callers can
  construct a reference to attach.
@tinysec tinysec merged commit 539cbf3 into master Jul 8, 2026
1 check passed
@tinysec tinysec deleted the feat/hlil-derived-string-reference branch July 8, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant