Skip to content

Return const references from member accessors#46

Merged
dimitrivlachos merged 1 commit into
mainfrom
accessors
May 18, 2026
Merged

Return const references from member accessors#46
dimitrivlachos merged 1 commit into
mainfrom
accessors

Conversation

@dimitrivlachos
Copy link
Copy Markdown
Collaborator

This PR fixes Detector::panels() returning std::vector<Panel> by
value, which created dangling references for callers binding to
panels()[0] and deep-copied the heavy panel vector on every call. It
also audits the remaining DX2 accessors so we don't hit the same silent,
non-error-throwing issue elsewhere.

Changes:

  • Changes Detector::panels() (include/dx2/detector.hpp,
    dx2/detector.cxx) from std::vector<Panel> to const std::vector<Panel> &, the idiomatic accessor form. This removes both
    the dangling-reference issue and the per-call deep copy of every heavy
    Panel.
  • Audits every accessor across beam, crystal, goniometer, scan,
    experiment, imagesequence and reflection for the same pattern.
    Three further member-backed accessors returned heap-owning types by
    value and are now const references:
    • Crystal::get_unit_cell() now returns const gemmi::UnitCell &
    • Crystal::get_space_group() now returns const gemmi::SpaceGroup &
    • ImageSequence::filename() now returns const std::string &
  • Intentionally leaves the remaining accessors returning by value, with
    rationale:
    • Computed accessors (Beam::get_s0(), Scan::get_oscillation(),
      etc.) construct a new value and must return by value.
    • Small trivially-copyable value types returned from members (Eigen
      fixed-size Matrix3d/Vector3d, std::array<…,2>, double) are
      stack-only, cheap to copy, and carry no dangling-subobject risk in
      practice.
    • ReflectionTable vector accessors and
      Experiment::identifier()/goniometer() already use the
      const-reference form.
  • No call sites in the repo or tests bound a reference to or mutated the
    return of the changed accessors, so there were no call-site changes.
    Existing gemmi::UnitCell cell = crystal.get_unit_cell(); copies
    remain valid.

This removes a runtime-only, non-deterministic memory-corruption class
from the detector model and brings the rest of the DX2 accessors into a
consistent, audited state.

Closes #45.

Detector::panels() returned std::vector<Panel> by value. Binding a
reference to panels()[0] bound to a subobject of that temporary, which
is destroyed at the end of the statement, leaving the panel dangling
for its whole lifetime. It compiled without warnings and failed only
non-deterministically at runtime (observed downstream as corrupt
Panel::get_origin() values). It also deep-copied the whole panel
vector on every call.

Change Detector::panels() to return const std::vector<Panel> &, and
audit the remaining DX2 accessors for the same pattern. Crystal::
get_unit_cell(), Crystal::get_space_group() and ImageSequence::
filename() returned heap-owning members by value and are now const
references too. Computed accessors and small trivially-copyable value
types are intentionally left returning by value.
Copy link
Copy Markdown
Contributor

@jbeilstenedmands jbeilstenedmands left a comment

Choose a reason for hiding this comment

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

Thanks for performing this audit 👍

@dimitrivlachos dimitrivlachos merged commit a1ba93a into main May 18, 2026
5 checks passed
@dimitrivlachos dimitrivlachos deleted the accessors branch May 18, 2026 17:10
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.

Detector::panels() returns std::vector<Panel> by value creating dangling references

2 participants