Skip to content

chore(#755): re-cut carried patch 0018 for OCCT#1417's review - #759

Merged
gsdali merged 3 commits into
refactor/381-pass1bfrom
chore/755-occt1417-review
Aug 7, 2026
Merged

chore(#755): re-cut carried patch 0018 for OCCT#1417's review#759
gsdali merged 3 commits into
refactor/381-pass1bfrom
chore/755-occt1417-review

Conversation

@gsdali

@gsdali gsdali commented Aug 7, 2026

Copy link
Copy Markdown
Collaborator

What & why

Maintainer gkv311 reviewed
our upstream OCCT PR (Scripts/patches/0018-*, filed from #555) and asked for three changes. Two
were mechanical: compare SquareDistance() against a tolerance squared once outside the loop
instead of calling Distance() inside it, and rename the Perform parameter theTol3d to theTol
since the same template also instantiates on Adaptor2d_Curve2d. The third needed a decision, not
just compliance: whether a degenerate point count should start throwing Standard_ConstructionError
in every build, or answer not-done in every build, to stop duplicating
Standard_ConstructionError_Raise_if (an assert-grade macro this project's own build compiles away
via BUILD_RELEASE_DISABLE_EXCEPTIONS=ON).

This PR re-cuts the carried patch to match the review, re-verifies it (override-link, not a full
kernel rebuild), and prepares (but does not push or post) the follow-up upstream commit and reply.

Closes #755

The decision on point 1, with the measurement

Measured before choosing, per the issue's own instruction:

  • Every bridge call site that constructs GCPnts_UniformAbscissa/GCPnts_QuasiUniformAbscissa
    with a caller-supplied count already wraps the construction in catch (...).
    Grepped
    Sources/OCCTBridge/src/*.mm for both classes: 9 direct construction sites
    (OCCTBridge_Curve3D.mm lines 835, 1135, 1724, 3076, 3092, 3109, 3126, 5743;
    OCCTBridge_Geom2d.mm line 5237). 8 are directly inside try { ... } catch (...) { return 0/nullptr/false; }. The 9th (OCCTBridge_Curve3D.mm:5741, the static helper
    sampleAdaptorUniform) is not itself wrapped, but its comment says so explicitly ("Callers keep
    their own try/catch ... so a thrown Standard_Failure/StdFail_NotDone can never cross the extern
    "C" boundary") and both its callers (OCCTBridge_Curve3D.mm:5806, :5858) are:
    try { return sampleAdaptorUniform(...); }. So all 11 reachable paths (9 + 2 via the helper) sit
    inside a catch (...) today.
  • Tests/OCCTCurveTests/Issue558SamplingCountBoundsTests.swift asserts the not-done/empty-result
    contract for exactly this input.
    curve3DRequests, curve2DRequests, edgeRequests,
    uniformAbscissaRequests all iterate n in [-1, 0, 1, ...] (1 is the degenerate case this guard
    covers) and assert .count == 0 / nil.
  • Nothing in OCCT itself calls the count-based initialize() with an unvalidated count. Grepped
    Libraries/occt-src/src for both classes outside their own GCPnts package: 5 production call
    sites (PrsDim_AngleDimension.cxx, BRepFill_TrimShellCorner.cxx,
    BRepExtrema_ProximityValueTool.cxx, BRepBuilderAPI_Sewing.cxx,
    ShapeAnalysis_CanonicalRecognition.cxx, ProjLib_ComputeApproxOnPolarSurface.cxx) and one Draw
    test command (GeometryTest_CurveCommands.cxx). Every one either hardcodes the count
    (N = 40, NbOfPnts = 61, npt = 4/8) or clamps it before construction
    (std::max(2, aNbPoints), std::max(3, aNbSamplePoints), the Draw command's own
    if (aSrcNbPnts < 2) { ...; return 1; }). So today, choosing (a) over (b) would not have changed
    behavior for any caller found in the tree either, on top of not changing it for OCCTSwift.

Given both, a caller-visible degenerate-count result is identical today whether the kernel throws
(caught, converted to the empty/nil result) or returns not-done directly (the same empty/nil
result), so both options were safe for OCCTSwift's own contract, and the choice had to be made on a
different ground, not "which one breaks us."

Chose (b), replacing Standard_ConstructionError_Raise_if outright rather than adding an
unconditional throw beside it: it is the option that does not depend on No_Exception being
defined (we build with BUILD_RELEASE_DISABLE_EXCEPTIONS=ON, so option (a) would make a degenerate
count throw in our own build for the first time), and it is what #555 argued for originally, a
silent not-done rather than an exception.

Confirmed directly, not assumed: compiled the pre-review and revised patch without
No_Exception and constructed each class with a degenerate count. Pre-review patch: still throws
Standard_ConstructionError (the un-replaced Raise_if is live without No_Exception). Revised
patch: throws in neither build configuration.

GCPnts_UniformAbscissa(nbPoints=1):            THREW Standard_ConstructionError   (pre-review, no No_Exception)
GCPnts_QuasiUniformAbscissa(bezier, nbPoints=1): THREW Standard_ConstructionError  (pre-review, no No_Exception)
GCPnts_UniformAbscissa(nbPoints=1):             NO THROW, done=0                  (revised, no No_Exception)
GCPnts_QuasiUniformAbscissa(bezier, nbPoints=1): NO THROW, done=0                 (revised, no No_Exception)

Verification

Override-link, not a full rebuild (the issue's own guidance; a full Scripts/build-occt.sh is
~75 minutes and not warranted for a review-response patch). Reconstructed the pristine (pre-0018)
files by reverse-applying the existing patch (patch -p1 -R), confirmed the round-trip is exact
(re-applying the pre-review patch reproduces the current occt-src byte for byte), then hand-edited
the pristine files per the review and diffed to produce the revised patch. Compiled three variants
of both files (pristine / current unrevised / revised) with the production flags
(-DNDEBUG -DNo_Exception, matching BUILD_RELEASE_DISABLE_EXCEPTIONS=ON) and override-linked each
ahead of libOCCT-macos.a:

  • Scripts/repro/555-gcpnts-count-contract/repro_555_count.mm: byte-identical output between the
    current (unrevised) and revised override-linked binaries. Over-request 0/60 for both classes on
    the pathological ellipse; every degenerate count (0, 1, -3) on every one of 5 curve types returns
    done=0 for both classes, no crash.
  • Scripts/repro/555-gcpnts-count-contract/repro_555_equivalence.mm (17 curve types x point counts
    2-200 x both classes, 6766 configurations x 2 = 13532 lines): diffed the current and revised
    outputs directly, 0 lines differ.

This worktree has a local kernel: Libraries is a symlink to the shared checkout's Libraries/
(created for this task; .gitignored, not part of the diff), which has occt-src and a rebuilt
OCCT.xcframework already carrying the pre-review 0018. OCCTSWIFT_LOCAL=1 was set for every
swift build/swift test invocation below. The rebuilt xcframework itself was not rebuilt with
the revised patch in this PR (that is a separate, deliberately-deferred step per the release
process); the override-link technique is what lets the revised kernel source be measured without
that rebuild.

Swift-side (against the current, unrevised-kernel Libraries/OCCT.xcframework; this PR touches
no bridge or Swift source, so this checks for regressions from the patch-file/doc changes only):

  • swift build --target OCCTCurveTests: clean (pre-existing unrelated warnings only).
  • swift test --filter "Issue558SamplingCountBounds": 21/21 pass.
  • swift test --filter "QuasiUniformAbscissaTests|GCPntsQuasiUniformTests|UniformAbscissaTests":
    7/7 pass.
  • Full swift test: 5462 tests in 1423 suites, all pass.

Gate scripts (all five, plus the four --self-tests, plus the census and merge-history audit
--self-tests, per CLAUDE.md's "Static Gate Scripts"):

script result
check-bridge-index.py 0 stale, 0 misfiled
check-bridge-index.py --self-test 18/18
check-null-handle-guards.py clean (24 ALLOWED exemptions, unchanged)
check-null-handle-guards.py --self-test 24/24
check-docs-defaults.py 0 drifted
check-docs-defaults.py --self-test 13/13
derive-bridge-header-split.py --verify 0 ambiguous/unmapped/misfiled
derive-bridge-header-split.py --self-test 8/8
count-operations.py (no --self-test, see CLAUDE.md) README/API_REFERENCE totals match
census-unmeasured-values.py --self-test 10/10
check-changelog-transcription.py --self-test 11/11

No new test or --self-test case is added in this PR (see Checklist), so
okf/policies/prove-the-test-fails.md has nothing new to
apply here; the equivalence sweep above is the "prove it did not move" evidence for a diff that
does not add coverage of its own.

CHANGELOG entry

None. This revises an internal carried OCCT source patch
(Scripts/patches/0018-GCPnts-degenerate-count-and-duplicate-end-point-555.patch) in response to
upstream review, before the corresponding kernel rebuild ships. No OCCTSwift public API or behavior
changes: verified by the equivalence sweep above (0 of 13532 lines differ from the pre-review
patch) and the full swift test run (5462 tests, all pass, against the unrebuilt kernel this PR
does not touch).

SemVer impact

NONE. No public Swift API changes, no OCCTSwift-observable behavior changes. The touched files are
an internal carried-patch text file, its own README section, a reproducer's README, and new
prepared-but-unpushed upstream artifacts.

Checklist

  • New or changed behavior is covered by a unit test in the same PR: N/A, no OCCTSwift-observable
    behavior changes (see "Verification" and the equivalence sweep).
  • Every new test and every new --self-test case was run once with its subject broken: N/A, no
    new test or --self-test case is added in this PR.
  • The CHANGELOG entry above is complete, and docs/CHANGELOG.md is not in this diff.
  • The SemVer impact above is stated, and docs/SEMVER.md is not in this diff.

Notes for the reviewer

  • Hard constraints observed: nothing was pushed to gsdali/OCCT, nothing was posted to
    OCCT#1417. The prepared follow-up commit
    (git am-verified against a throwaway repo built from the reconstructed pristine files) and the
    drafted reply are both under
    Scripts/repro/555-gcpnts-count-contract/upstream/,
    ready for a human to push/post.
  • The rebuilt Libraries/OCCT.xcframework still carries the pre-review 0018. Rebuilding it with
    the revised patch is a separate step (a real Scripts/build-occt.sh run, ~75 minutes), deliberately
    out of scope here per the issue; the override-link measurements above are what stand in for that
    rebuild until it happens, most likely alongside a future kernel re-pin.
  • Point 3's "shadowing" check: verified Perform is a free static function at namespace scope,
    not a member of GCPnts_UniformAbscissa, so renaming its theTol3d parameter to theTol
    introduces no shadowing against either initialize() overload's own theTol parameter (both call
    sites already pass theTol straight through as a plain expression, not through an intermediate
    local of the same name).
  • Scripts/patches/README.md's 0018 section and the repro's own README.md are both updated in
    place (not a new patch number), matching this project's existing convention for a revised carried
    patch (e.g. the 0011/#363 and 0016/#374 entries).

gsdali and others added 3 commits August 7, 2026 18:56
gkv311 reviewed our upstream PR and asked for three changes: SquareDistance()
instead of Distance() in the loop, theTol3d renamed to theTol (the template
also instantiates on Adaptor2d_Curve2d), and a choice between two ways to stop
duplicating Standard_ConstructionError_Raise_if for a degenerate point count.

Measured before choosing on the third point: every bridge call site that
constructs GCPnts_UniformAbscissa/GCPnts_QuasiUniformAbscissa with a
caller-supplied count already wraps the construction in catch (...), and
Issue558SamplingCountBoundsTests asserts the not-done/empty-result contract
for exactly this input across all of them, so an unconditional throw and an
unconditional not-done are observably identical for our own contract. Took
the not-done option: it does not depend on No_Exception being defined, which
is the option #555 argued for in the first place.

Re-verified via override-link (production flags, -DNDEBUG -DNo_Exception):
the 232/6766-configuration equivalence sweep and the degenerate-count sweep
both report the same verdict as before this revision.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…validated

Answers the issue's third measurement question directly: grepped occt-src for
every count-based GCPnts_UniformAbscissa/GCPnts_QuasiUniformAbscissa call site
outside the GCPnts package itself. All 5 production sites and the 1 Draw test
command already hardcode or clamp/reject the count before construction, so
choosing (a) over (b) would not have changed behavior for any caller found in
the tree, on top of not changing it for OCCTSwift's own bridge.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…orts

Two edits to outward-facing text.

The opener described our own repro path and a force-push we have not done. A maintainer needs
neither; the branch state speaks for itself once it is pushed.

The substantive one: having checked every in-tree construction site, the finding is not that
option (a) "would not change observed behavior for any caller" but that no caller can reach the
degenerate branch at all, because each hardcodes or rejects the count first, `uniformAbscissa`'s
Draw handler included with its own `if (nocp < 2) return 1;`. That makes the choice between the two
options free of consequence for OCCT itself, which is a stronger and more useful thing to tell a
reviewer than a statement about callers.
@gsdali
gsdali merged commit 809f148 into refactor/381-pass1b Aug 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.

1 participant