ModelingData, ModelingAlgorithms - Extrema_ExtCC::Points() indexes an empty sequence on some parallel-curve results - #1445
Conversation
Filed as Open-Cascade-SAS/OCCT#1445. PR only, no companion issue, per okf/policies/upstream-occt-style.md and the precedent of 0018, 0019 and 0021: the fix was ready, so the PR description carries the repro and root cause a standalone issue would have. Verified applying cleanly to upstream master at b8f597c6 immediately before filing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@gsdali , it is better to synchronize |
|
Dear @gsdali please add GTest to cover your changes, you can follow the logic of all exited GTests. |
| void Extrema_ExtCC::Points(const int N, Extrema_POnCurv& P1, Extrema_POnCurv& P2) const | ||
| { | ||
| if (N < 1 || N > NbExt()) | ||
| // NbExt() counts mySqDist; some parallel-curve branches append a distance with no matching |
There was a problem hiding this comment.
Too much text which is unreadable without PR context, please concentrate the description in shorter form
| //! Returns the points of the Nth extremum distance. | ||
| //! P1 is on the first curve, P2 on the second one. | ||
| //! Exceptions | ||
| //! Standard_OutOfRange if N is not in [1, NbExt()], or if N has a distance but no |
There was a problem hiding this comment.
Too much text which is unreadable without PR context, please concentrate the description in shorter form
|
I've seen the comment, we've just worked through our release against 8.0.1 and can now take a look at this. |
…bExt() NbExt() counts mySqDist; Points() reads mypoints but checked the request against NbExt(). Some PrepareParallelResult branches append a distance with no matching point pair, because an equidistant family has no unique closest pair, so Points(1) could index mypoints past its length. Under a build configured with BUILD_RELEASE_DISABLE_EXCEPTIONS the NCollection_Sequence bounds check is compiled out and the result is a segmentation fault. NbExt() is deliberately unchanged: LowerDistance() relies on a real distance in exactly that parallel case. Also adds the IsParallel() convenience Geom2dAPI_ExtremaCurveCurve was missing relative to its 3D sibling. Behavior-neutral. Also synchronizes Extrema_ExtCC2d::Points() with the same check style (2 * N > mypoints.Length() instead of N > mynbext), per review. Measured before changing: unlike the 3D sibling, mynbext and mypoints.Length()/2 are an invariant in this class (both Results() overloads only ever touch mynbext, mySqDist and mypoints together), so this is a no-op - confirmed with a standalone equivalence probe across four fixtures (intersecting, parallel overlapping/disjoint/touching), byte-identical output old vs. new check. Geom2dAPI_ExtremaCurveCurve.hxx's new IsParallel() gets a matching doc comment. Tests: added Extrema_ExtCC_Test.cxx (parallel overlapping and unbounded cases throw instead of crashing; parallel disjoint and ordinary intersecting cases are unaffected) and Extrema_ExtCC2d_Test.cxx (the consistency edit plus Geom2dAPI_ExtremaCurveCurve::IsParallel()) in src/ModelingData/TKGeomBase/GTests/. Verified both ways: against the current pinned kernel (predates this fix), Points() on the two parallel-crash fixtures SIGSEGVs; linked with the patched translation units ahead of the archive, all 7 tests pass.
db9df09 to
f2f7bf5
Compare
|
Thanks both — pushed an update. @gkv311 on synchronizing @dpasukhi: added |
Extrema_ExtCC::NbExt()counts one container (mySqDist);Extrema_ExtCC::Points()reads adifferent one (
mypoints), but bounds-checks the request againstNbExt():Several branches of
PrepareParallelResult(called whenever the two curves are found to beparallel) append a distance to
mySqDistwith no matching pair appended tomypoints, because inthose branches there is no discrete answer to give: the curves are parallel over a continuous range
(or unbounded), every point in it is equally close, and there is no unique "the" closest pair, only
a distance.
NbExt()reports1in exactly the cases this happens, soPoints(1)indexesmypointsat an index past its actual length.Points()'s own bounds check does not catch this because it checks the wrong container's length(
NbExt(), notmypoints.Length()). The check that would catch it sits one level down, insideNCollection_Sequence::Value(size_t):Under a Release build configured with
BUILD_RELEASE_DISABLE_EXCEPTIONS(-DNo_Exception), thatmacro-based check compiles to nothing, and indexing a zero-length sequence walks a null node and
dereferences it: a segmentation fault, not a C++ exception. Confirmed with a standalone reproducer
linked directly against a
No_Exception-configured build.GeomAPI_ExtremaCurveCurve::Points(), the public wrapper most callers actually use, has theidentical shape one level up: its own bounds check is also built from
Standard_OutOfRange_Raise_ifand is also compiled away underNo_Exception, so nothing standsbetween a caller of the public API and the crash in that configuration.
Fix
Bound
Points()against the container it actually reads:NbExt()is left unchanged: it is also used bySquareDistance(), and legitimate callers(
GeomAPI_ExtremaCurveCurve::LowerDistance()) rely on getting a real distance back in exactly theparallel-distance-only case this fix's
Points()now refuses — the perpendicular distance betweentwo parallel lines is a well-defined number even though there is no unique closest point pair.
Redefining
NbExt()to trackmypointsinstead would have broken that caller; this was verified bytracing
LowerDistance()'s call path (SquareDistance(myIndex), which bounds againstNbExt()too) before deciding, not assumed. A short
//! Exceptionsline is added to the header declaration.Also includes a companion, behavior-neutral one-line addition to
Geom2dAPI_ExtremaCurveCurve,which does not have this defect (its own
NbExtrema()already reports0in the equivalentparallel case, confirmed by measurement) but was missing the
IsParallel()convenience its 3Dsibling
GeomAPI_ExtremaCurveCurvehas:(
Extrema_ExtCC2d::IsParallel()was already public and already reachable through the existingExtrema()accessor, so this is a convenience, not a new capability.)Reproducer
Four fixtures, run through both
GeomAPI_ExtremaCurveCurve(the typical entry point) andExtrema_ExtCCdirectly:crashes.
a real unique nearest-point answer, unaffected by this fix.
Geom_Lines — crashes.not crash, has a real unique answer, unaffected by this fix. (Included because a first read of
the source suggested this might be a case where
IsParallel()is true but a point pair stillexists; measuring it showed the source resets
IsParallel()to false here, so it is not such acase — reported since the reasoning is useful even though it turned out not to be a
counterexample.)
IsParallel()NbExtrema()Points(1)beforePoints(1)after11Standard_OutOfRange0111Standard_OutOfRange01LowerDistance()/Distance(1)return the identical values before and after in every fixture(measured, since this fix does not touch
mySqDistorNbExt()).Validation
Compiled the patched
Extrema_ExtCC.cxxstandalone with-DNDEBUG -DNo_Exception(matching aRelease, exceptions-disabled configuration) and linked it ahead of the stock archive. Fixtures 1 and
3 go from a deterministic SIGSEGV (raw exit 139) to a caught
Standard_OutOfRange; fixtures 2 and 4are byte-identical before and after, in both the returned points and the distance values. Full
before/after transcripts and the reproducer source are in the downstream OCCTSwift wrapper's
Scripts/repro/636-extrema-parallel/(lands onmainwith that wrapper's v2.0.0)(issue #636 there).