Repository navigation
fix: zero-init TGS empty_trigrid_node_ + AOS Point/AOSNode (root-cause CI flakes) - #28
Merged
Merged
Conversation
… Point/AOSNode
Two related uninit-memory bugs that caused 1-1.5% run-to-run mismatches
in the Python smoke tests on the GitHub Ubuntu runners. Both have the
same shape: a class member declared without an initializer, copied into
a working buffer that the algorithm then assumes is zeroed.
(1) TGS — initTriGridField had four no-op expression statements where
it intended to zero-initialize the Eigen members of empty_trigrid_node_:
empty_trigrid_node_.normal; // evaluates, drops the lvalue
empty_trigrid_node_.mean_pt;
empty_trigrid_node_.singular_values;
empty_trigrid_node_.eigen_vectors;
Eigen vectors/matrices do not zero-init by default; the four members
held stack garbage. empty_trigrid_node_ was then copied into every
cell of trigrid_field_, seeding the whole field with that garbage.
Replaced with explicit Eigen::Vector3f::Zero() / Matrix3f::Zero()
assignments.
(2) AOS — `class Point` declared `int label;` (and `float x,y,z; uint idx;`)
without in-class initializers. range_mat_ entries got assigned
fresh Point copies in sphericalProjection, leaking uninit `label`
fields into the working set. `class AOSNode` had the same problem
on its `start` / `end` members. Combined, they made cluster
filtering decisions in labelPointcloud run-to-run nondeterministic
(~1-1.5% of points flipped between "in some cluster" and "no cluster"
on consecutive segment_objects calls with identical input).
Fixed with brace-init (`label{0}`, `start = 0`, etc.).
Verified:
* cpp/tests/regression_kitti against cpp/tests/data/kitti00_000000_gold.bin
-> REGRESSION PASS: bit-identical (374008 bytes). The KITTI sequence 00
frame 0 output with seed=42 is byte-for-byte the same as the gold dump
captured pre-fix. Both fixes therefore *only* remove nondeterminism;
the deterministic majority result that was already produced is
preserved.
* python/tests/test_smoke.py::test_xyz_and_xyzi_inputs_both_accepted
and python/tests/test_smoke.py::test_class_api_matches_convenience_function
ran 30 times back-to-back on macOS arm64 — 30/30 passed (the two had
been flaking ~1-1.5% on the Ubuntu CI runners).
* Full pytest python/tests/ -> 7/7 passed.
LimHyungTae
force-pushed
the
fix/tgs-uninit-memory
branch
from
May 9, 2026 09:44
118604d to
9fb65e1
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two related uninit-memory bugs that caused 1-1.5% run-to-run mismatches in the Python smoke tests on the GitHub Ubuntu runners. Same shape in both: a class member declared without an initializer, copied into a working buffer that the algorithm then assumes is zeroed.
TGS
initTriGridFieldhad four no-op expression statements where it clearly intended to zero-initialize the Eigen members ofempty_trigrid_node_:Eigen vectors/matrices don't zero-init by default.
empty_trigrid_node_then got copied into every cell oftrigrid_field_, seeding the whole field with stack garbage.Fix: replace with explicit
Eigen::Vector3f::Zero()/Eigen::Matrix3f::Zero().AOS
class Pointdeclaredint label;(andfloat x,y,z;,uint idx;) without in-class initializers.range_mat_entries got assigned freshPointcopies insphericalProjection, leaking the uninitlabelfield into the working set.class AOSNodehad the same problem on itsstart/endmembers. Combined, they made cluster filtering decisions inlabelPointcloudrun-to-run nondeterministic — about 1-1.5% of points flipped between "in some cluster" and "no cluster" on consecutivesegment_objectscalls with identical input.Fix: brace-init (
label{0},start = 0, etc.).Why this is safe under the "never break the pipeline" rule
This is a correctness fix, not a behavior change. The existing code's output was already nondeterministic, so there's no single "original behavior" to preserve. What we want to preserve is the deterministic majority result, which is exactly what the algorithm produces in the common (non-uninit-read) path.
Verified on macOS arm64:
So the algorithm's output on KITTI sequence 00 frame 0 (with AOS seed=42) is byte-for-byte the same as the gold dump captured pre-fix. The fix moves the previously-stochastic minority into the same canonical answer the deterministic majority was already producing.
test_xyz_and_xyzi_inputs_both_acceptedandtest_class_api_matches_convenience_functionran 30 times back-to-back on macOS — 30/30 passed for both (each was flaking ~1-1.5% on the Ubuntu runner before this commit).Full
pytest python/tests/-> 7/7 passed.Test plan