Skip to content

fix: own legend label bytes in LegendEntryLabel forwarder (macOS issue #9)#21

Merged
borisbat merged 1 commit into
masterfrom
bbatkin/issue9-legend-label-clone
Jun 7, 2026
Merged

fix: own legend label bytes in LegendEntryLabel forwarder (macOS issue #9)#21
borisbat merged 1 commit into
masterfrom
bbatkin/issue9-legend-label-clone

Conversation

@borisbat

@borisbat borisbat commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #9 — on macOS the Y2-routed series' legend label came back empty in the v2 plot snapshot. Root-caused as a dangling internal-pointer alias, not a label-resolution lag.

LegendEntryLabel (src/dasIMPLOT.main.cpp) returned the raw const char* from ImPlot::...GetLegendLabel(i) = Legend.Labels.Buf.Data + NameOffset, a pointer straight into ImPlot's per-frame Legend.Labels buffer. daslang aliases a const char* return (no copy), and capture_legend stored the alias. The buffer is valid at capture time (post-EndPlot), but the snapshot is serialized a frame later over the live API — by then the next frame's BeginPlot/SetupFinishLegend.Reset() has cleared and (on macOS) realloc'd the buffer, leaving the das string dangling. Windows/Linux kept the same allocation and rewrote identical bytes in place, so the alias still read correctly there; macOS surfaced empty/garbage labels.

Diagnosing on a macOS box showed it's worse than "empty": both labels came back as garbage bytes, not just the Y2 one — the test only asserted pressure, so temp's corruption went unnoticed. Same root cause.

Fix (C++ wrapper, not the das caller)

We own the LegendEntryLabel forwarder (it's hand-written, unlike the generated bindings), so the right place to own the bytes is the wrapper itself — matching the rest of the string-returning API. Threaded das::Context* / das::LineInfoArg* and return context->allocateString(...), so the returned das string owns a heap copy that stays valid regardless of ImPlot's buffer churn. Same idiom as dasImgui's text_range_string / ImGTB_Slice.

(First pass cloned on the das side with clone_string in capture_legend; moved to the wrapper per review — keeps callers from having to remember to clone, consistent with the rest of the API.)

Also removed the macOS skip in test_multi_axes — the pressure/Y2 legend assertion now runs on all platforms.

Verification (macOS, headless, CI-equivalent)

Reproduced and fixed in a fresh workspace mirroring tests.yml exactly (own daslang-src + dasImgui + dasImguiImplot, Ninja Release build, daspkg install --global both packages):

  • Before: forcing the skipped assertion → wait_for_series_shown(... "pressure" ...) times out; raw legend dump shows garbage labels for both series.
  • After: labels resolve to temp / pressure; full integration suite passes 13/13 (--isolated-mode --isolated-mode-threads 4 --headless), including the de-skipped test_multi_axes.
  • Both changed .das files lint clean.

Audit (same internal-pointer pattern elsewhere)

Swept every native string return that das might store:

  • LegendEntryLabel — the one live bug. Fixed here.
  • GetColormapName (generated binding) — same internal-ImGuiTextBuffer pattern (Text.Buf.Data + TextOffsets[cmap]), but no das caller stores it today → latent only. If it's ever wired to das, give it the same allocateString treatment.
  • GetStyleColorName / GetMarkerName — static string literals → safe.
  • dasImgui text_range_string / ImGTB_Slice — already use context->allocateString(...) → safe; combo-getter callbacks are transient → safe.

🤖 Generated with Claude Code

…#9)

LegendEntryLabel returned the raw const char* from ImPlot's GetLegendLabel,
which points straight into the per-frame Legend.Labels buffer. daslang aliases
a const char* return (no copy), so the captured das string pointed into that
buffer. The buffer is valid at capture time (post-EndPlot) but the snapshot
serializes a frame later, by when the next BeginPlot/SetupFinish has reset and
(on macOS) realloc'd it, leaving the string dangling — empty/garbage labels.
Win/Linux kept the same allocation and rewrote identical bytes in place, so the
alias still read correctly there.

Fix in the forwarder (which we own, unlike the generated bindings): thread
das::Context*/LineInfoArg* and return context->allocateString(...) so the das
string owns its bytes and stays valid regardless of the buffer's later fate.
Mirrors the rest of the string-returning API (dasImgui's text_range_string /
ImGTB_Slice). Drops the macOS skip in test_multi_axes — the pressure/Y2 legend
assertion now runs on all platforms. Full integration suite passes 13/13
headless on macOS.

Closes #9

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@borisbat borisbat force-pushed the bbatkin/issue9-legend-label-clone branch from 355f3d4 to 95d95c6 Compare June 7, 2026 05:05
@borisbat borisbat changed the title fix: clone_string legend labels in capture_legend (macOS issue #9) fix: own legend label bytes in LegendEntryLabel forwarder (macOS issue #9) Jun 7, 2026
@borisbat borisbat merged commit a1d36c3 into master Jun 7, 2026
5 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.

macOS: Y2-routed series' legend label not captured in the plot snapshot (empty)

1 participant