Skip to content

chore(#512) + fix(#518, #555, #522): rebuild the kernel, redesign 0016 after upstream review, add 0018 and 0019 - #519

Merged
gsdali merged 7 commits into
refactor/381-pass1bfrom
chore/512-rebuild-kernel-0017
Jul 31, 2026
Merged

chore(#512) + fix(#518, #555, #522): rebuild the kernel, redesign 0016 after upstream review, add 0018 and 0019#519
gsdali merged 7 commits into
refactor/381-pass1bfrom
chore/512-rebuild-kernel-0017

Conversation

@gsdali

@gsdali gsdali commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Four changes that have to ship in one kernel binary: the #512 rebuild, #518's redesign of patch 0016 after upstream review, #555's new patch 0018, and #522's new patch 0019. Closes nothing on its own, since the remaining items on #512's checklist are release-time steps, listed at the bottom.

What this does

Scripts/patches/0017-* (the #484 null-context guard for ShapeFix_ComposeShell::Perform(), ShapeFix_ComposeShell::SplitEdges() and ShapeUpgrade_WireDivide::Perform()) was carried but inert. build-occt.sh applies every carried patch before each cmake build, so a patch only reaches callers once Libraries/OCCT.xcframework is rebuilt from source. It is now rebuilt from V8_0_0_p1 + all 19 carried patches.

No API change and no behaviour change through this wrapper: both bridge call sites already call SetContext(new ShapeBuild_ReShape()), so nothing here could reach the crash before or after, and those workarounds stay in place (same PR1→PR2 pattern as #298/#341/#344/#349). The rebuild closes the crash for code reaching those OCCT classes through a path this package does not control.

Base

0017 arrived on refactor/381-pass1b when #511 merged partway through the rebuild, so there is no
stacking left. Rebased three times as #516 (#487), #517 (#488) and #509 (#485) landed in the base;
each conflicted on the same docs/CHANGELOG.md insertion point, resolved by keeping both entries with
#### items inside the Pass 1b group and these two ### sections after them.

Why the two commits are one PR

They produce a single OCCT.xcframework. Splitting them would mean publishing a checksum for a binary
that the second commit immediately replaces. #518's kernel change had to land in the same rebuild, so
it rides here rather than in a follow-up.

Verification

Everything was measured against the real rebuilt binary with no override-linked TUs. That distinction is the point of this PR: all prior #484 evidence was override-linked, which proves the patch compiles and works, not that it shipped.

Before the rebuild, V8_0_0_p1 + patches 0001-0016:

ShapeUpgrade_WireDivide::Perform   ctx=NO  :   *** KILLED BY SIGNAL 11 ***
ShapeFix_ComposeShell::Perform     ctx=NO  :   *** KILLED BY SIGNAL 11 ***

After, same source file, same compile command:

ShapeUpgrade_WireDivide::Perform   ctx=NO  : Perform done, wireNull=0
ShapeFix_ComposeShell::Perform     ctx=NO  : Perform=1 resultNull=0

The other six cases in repro_484_crash.mm are unchanged. repro_484_equivalence.mm: all four ctx=yes fingerprints byte-identical to the pre-rebuild values, and all four ctx=NO fingerprints now complete and match their ctx=yes counterpart.

The checklist expected those fingerprints to already be recorded in the reproducer's README. They were not, so I captured them against the old kernel before rebuilding and have now written them into that README as the reference values for any future rebuild:

WireDivide   planar       ctx=yes : type=5 n4=0 n5=1 n6=4 n7=8 brep=19494c8eaac44ef7 len=582
WireDivide   cylindrical  ctx=yes : type=5 n4=0 n5=1 n6=4 n7=8 brep=ea26831beb4fa5fb len=667
ComposeShell planar       ctx=yes : ok=1 type=4 n4=1 n5=1 n6=4 n7=8 brep=a06f56d38503cd48 len=752
ComposeShell cylindrical  ctx=yes : ok=1 type=4 n4=1 n5=1 n6=4 n7=8 brep=aa5c2d38eac7d8b3 len=699

Other gates:

Build hygiene

The checklist's two build traps were both checked rather than assumed:

  • occt-src contents. Modified in exactly the 47 files patches 0001-0017 touch, no stray files, no untracked probes, and all 17 reverse-apply cleanly. So the tree that got compiled is the pinned tag plus the carried patch set and nothing else, which matters because sessions do compile diagnostic probes into occt-src. All 17 logged as already applied (the source was patched by fix(#484): two upstream null-context SIGSEGVs patched, Face.fixed given a context, connectedFaces covered and de-first-of-N'd #511's override-link work), which is the expected wording, not a skipped patch.
  • Stale objects. The build was interrupted twice by session limits and resumed in place, exactly the case where a fresh-timestamped but stale artifact can appear. Objects for both patched TUs are newer than the patched sources in all three slices, and the resume ran from the same checkout that configured the build dir.

#518: Storage_Schema's scratch handle becomes a member, not a guarded global

Patch 0016 (#374) fixed two kernel races. Its Resource_Manager::Debug half (a file-scope static bool made std::atomic<bool>) is untouched. Reviewing our upstream PR OCCT#1399, maintainer gkv311 pointed out the Storage_Schema half needs no lock at all if ICurrentData() becomes a mutable Handle(Storage_Data) field on the class.

That checks out against the source, and I verified the premise before writing any code:

  • No Storage_Schema instance is ever shared. All four constructions in the tree are function-local (PCDM_StorageDriver::Write, PCDM_ReadWriter_1 at three sites). None is cached, unlike the driver instances Concurrent Save/SaveAs to the same format corrupts a shared, cached storage driver instance (SIGSEGV) #349 had to deal with.
  • Every callback re-entry lands on the same instance. Storage_CallBack::Add/Write/Read all take the driving schema as an argument, so the BindType/AddPersistent/PersistentToAdd calls made from callbacks go through *this.
  • Removing the statics breaks nothing. ICurrentData() and ISetCurrentData() were both private with no reference anywhere outside the class. Other Storage_Schema users in the tree (BinLDrivers, XmlMDF, StdLDrivers) touch only its statics CheckTypeMigration() and ICreationDate(), neither of which reads the current data.
  • mutable is required, because Write, Clear, HasTypeBinding, BindType, TypeBinding, AddPersistent and PersistentToAdd are all const.

The field is also strictly stronger on the failure #374 reported. Under the mutex, a throwaway Storage_Schema built by PCDM_ReadWriter_1 during an unrelated Open() still nullified an in-flight Write()'s current data, it just did so without a data race. Under the field it cannot reach another instance's data at all. It narrows exactly one thing, two threads driving the same instance, which no caller does.

One extra change, flagged rather than buried. The removed mutex was incidentally covering AddPersistent()'s static TCollection_AsciiString aTypeName, a second process-wide mutable object in the same class (the 374 reproducer's own README had noted it). The static is dropped: the value is assigned from the tName argument and read two lines later, so it only ever saved an allocation. Currently unreachable in-tree, since both concrete PCDM_StorageDriver subclasses override Write, but AddPersistent is public and a third-party subclass can reach it.

Patch number stays 0016, a corrected design rather than a new fix, matching the 0011 precedent from #341 to #363. The file is renamed to ...-Storage_Schema-per-instance-374.patch since the old name says mutex.

Verification, all against the rebuilt kernel:

One full-suite run during this validation hit a pre-existing timing flake in #300's stepRobustRepairCancellation, which reports .importFailed where it expects .cancelled about 1 run in 9 in isolation. Unrelated to either change here (Storage_Schema is only reachable from OCAF store/retrieve, not STEP import), so it is filed as #525 rather than absorbed, and the count above is from a clean run.

Upstream OCCT#1399 is updated to match. Force-pushed the redesigned single commit (retitled to OCCT's Category - Summary convention as Foundation Classes - ...), rewrote the PR description, and replied to gkv311's review, including an offer to split the aTypeName change into its own commit and to split Resource_Manager from Storage_Schema into two PRs if preferred. clang-format and the non-ASCII check are clean locally against OCCT's own .clang-format; CI is running.

The upstream branch's diff and our carried 0016 are the same change, verified by diffing them (identical apart from one hunk-header context label, which differs because our tree also carries 0012 in Resource_Manager.cxx). The myCurrentData doc comment is one line rather than three, per okf/policies/upstream-occt-style.md: mechanism only, rationale in the PR description. Trimming it changed a packaged header, so the artifact was repackaged and the checksum above is the result; the reproducers and the full suite were re-run against it.

#555: two GCPnts point-count defects, patched as 0018

GCPnts_UniformAbscissa / GCPnts_QuasiUniformAbscissa. #501 closed the OCCTSwift-reachable half of the first one at the bridge layer (merged as #544 while this was in progress, and unaffected by the patch); this is the kernel side, which every other OCCT consumer still had.

NbPoints() was not bounded by the requested count. The array is sized theNbPoints + 5 and the walk fills it until it reaches the end or runs out of room. The cause is a tolerance mismatch, not an off-by-one: Perform terminates on a parametric epsilon that Resolution() derives from the curve's largest derivative, so on an ellipse with major radius 1e6 and minor radius 1e-3 it is about 1e-13 where the locally correct value is about 1e-4. The walk stops 1.557e-08 short, refuses to call that done, takes one more step and snaps it to the end.

The probe added the fact that decides the fix: the surplus point is a duplicate, 1.175e-10 from its neighbour in 3D. So the fix is not to clamp the count, which would drop the exact end parameter and leave the distribution stopping short (precisely the defect #501 found in the one bridge function that already clamped), but to accept a point that coincides with the end in 3D within the caller's tolerance. The 3D tolerance is threaded in beside the parametric one, the end point is evaluated once outside the walk, and the distance test sits behind a cheap aUU2 - aUi < aDelta gate so it runs on the final step rather than every step.

A point count below 2 stored out of bounds. Both classes document theNbPoints >= 2 and enforce it with Standard_ConstructionError_Raise_if, which compiles to nothing under No_Exception, which is how the shipped Release kernel is built (#487). GCPnts_QuasiUniformAbscissa's Bezier/BSpline branch then allocated an empty array and unconditionally stored into index 1: uncatchable SIGSEGV, same class as #263/#310/#317/#318. Both classes now leave the object not done for such a count; the Raise_if stays, so an exceptions-enabled build throws exactly as before.

Verification, against the rebuilt kernel with no override-linked TUs:

  • 17 curve types x counts 2..200 x both classes, 6766 configurations: 232 lines change and they are exactly the 232 that were over-requesting. Everything else is byte-identical, across a line, four circles from radius 1e-6 to 1e7, a 5x2 ellipse, a hyperbola, a parabola, a 2-pole and a 4-pole Bezier, an 8-point and a 40-point BSpline, an offset circle, an offset BSpline and a trimmed circle. On the changed lines the last parameter is still exactly the end.
  • Over-request goes from 22 counts to 0; every degenerate count on every curve now returns IsDone() == false for both classes, in place of a SIGSEGV, a 5-point answer or a 1-point answer depending on which curve and class you called.
  • clang-format clean on both touched files, against OCCT's own config.

No OCCTSwift API changes behaviour: #544's bridge guards already rejected degenerate counts and clamped the overflow. The patch closes both for code reaching those OCCT classes through a path this package does not control. Its guards stay in place regardless, since they are what makes the bridge correct against an unpatched kernel.

Filed upstream as OCCT#1417, a fix PR with no companion reproducer issue, per the rule maintainer dpasukhi gave on OCCT#1409.

One trap is recorded in the reproducer README because it cost me a confusing run: building the override-linked TUs without -DNo_Exception brings the Raise_if back, so the degenerate cases abort with an uncaught Standard_ConstructionError rather than crashing (stock) or returning not-done (patched). It reads like a broken patch, and it is measuring a kernel nobody ships.

fix(#522): the Jacobi maxima that were never written, and the zero error that followed

New carried patch 0019, found while building #491's surface-approximation parity tests and folded in here because it needs the same binary.

A radius-10 sphere approximated at C0 and tolerance 1e-3 came back as a degree-1, 2-pole-in-U B-spline: a straight line across the full 2*pi of its longitude, deviating by the sphere's own diameter of 20, while isDone said the tolerance was met and maxError said 1.07e-4. A bicubic Bezier at C0 collapsed to a 2x2 bilinear patch reporting 4.08e-15 at every tolerance from 1e-1 down to 1e-7, so tightening the request changed nothing.

One line explains both. AdvApp2Var_ApproxF2var::mma2ce1_ partitions one scratch allocation into seven consecutive buffers, ipt4 for XMAXJU (the maxima of the U Jacobi polynomials) and ipt5 for XMAXJV, then fills both from ipt5:

AdvApp2Var_ApproxF2var::mma2jmx_(ndjacu, iordru, &wrkar_off[ipt5]);   /* -> should be ipt4 */
AdvApp2Var_ApproxF2var::mma2jmx_(ndjacv, iordrv, &wrkar_off[ipt5]);

XMAXJU is never written; mma2ce2_ reads whatever the allocation left at ipt4, in practice zeros. Every truncation error the approximator computes is |PATJAC(i,j)| * XMAXJU(i - 2*(IORDRU+1)) * XMAXJV(j - 2*(IORDRV+1)), so a zero XMAXJU zeroes every term. The interior error of every patch then evaluates to exactly 0, which means mma2ce2_'s tolerance test can never fire on it and MaxError() only ever describes the boundary iso-curves; and mma2er2_, asked for the lowest degree whose truncation error still fits, always answers NDMINU, the floor from the constraint order and the neighbouring isos, because every candidate scores 0.

Where that floor is low the fit collapses onto it. C0 gives IORDRU = 0, and a full sphere's V-boundary isos degenerate to its two poles, one coefficient each, so NDMINU is 1. C1 and C2 hide the collapse, their floor already being 8, but not the misreported error, which was never specific to C0. Degree collapse per se was never the defect — a cylinder trimmed in V legitimately fits at vDegree = 1, and does so before and after.

The write also overruns: mma2jmx_ writes ndjacu + 1 - 2*(IORDRU+1) doubles into a slot sized for the ndjacv equivalent, running into VECERR when MaxDegU >> MaxDegV. Benign, since VECERR is re-zeroed on entry to mma2ce2_, but out of bounds.

Fix: target ipt4 from the U call. One character. AdvApp2Var_Context's own two mma2jmx_ calls, the only others in the tree, already write to separate per-direction arrays.

Verification, against the rebuilt kernel with no override-linked TUs:

before after
real deviation > 10x reported maxError 12 of 98 0 of 98
real deviation > reported maxError at all 17 of 98 1 of 98
worst ratio of real deviation to reported error 3.4e13 1.0018

98 requests: 7 surface families x all 9 (uContinuity, vContinuity) combinations of C0/C1/C2 at tolerance 1e-3, plus C0/C0 across five tolerances, each comparing the reported error against the real maximum deviation over a 21x21 grid. The one row still over the line afterwards is a Bezier reproduced exactly, reporting 9.95221e-15 against a measured 9.96978e-15. Every reported error rises slightly, which is the interior contribution being counted for the first time; degrees rise only where the collapse was happening. Full before/after transcripts committed as sweep-before.txt / sweep-after.txt. The mechanism was confirmed directly by dumping the buffer through an -O0 override-link: xmaxju[8] = 0 0 0 0 0 0 0 0 stock, 0.9682 0.986 1.078 ... fixed.

This one does change OCCTSwift behaviour, unlike 0017 and 0018. Surface.approximated(tolerance:continuity:) and Surface.approxWithDetails(...) at C0 previously returned a collapsed surface and an error that described neither the input nor the output; both now return a usable fit and a maxError that bounds the real deviation. Issue491SurfaceApproxParityTests.maxErrorDescribesTheSharedFit had to exclude .c0 when it was written for exactly this reason; that exclusion is gone and it now checks every request. Four new tests in Issue522ApproxC0CollapseTests were run against a deliberately reverted kernel and all four fail there with the numbers above; the fifth, a V-linear cylinder control, passes either way.

The blast radius inside the kernel is wider than the C0 collapse. GeomFill_Sweep, BRepOffset_Offset, GeomLib, ShapeCustom_BSplineRestriction, ShapeConstruct and GeomConvert_1 construct a GeomConvert_ApproxSurface, ShapeCustom_ConvertToBSpline and ShapeUpgrade_UnifySameDomain reach one, and GeomPlate_MakeApprox drives AdvApp2Var_ApproxAFunc2Var directly. Most request C1 or C2, so the collapse could not reach them, but the always-zero interior error could — and ShapeConstruct::ConvertSurfaceToBSpline and ShapeCustom_BSplineRestriction both loop the requested continuity down to 0 on failure, then accept the result on MaxError() <= tol. ShapeCustom_ConvertToBSpline starts at C0 for any offset surface (ShapeCustom_ConvertToBSpline.cxx:148, a 1999 workaround for a hang) before handing off to the first of those. Two mentions are not callers: BRepFill_Sweep.cxx:1162 sits inside a /* */ block and BRepFill_Filling.cxx:712 is //-commented — a filename-level grep counts both, which is how an earlier revision of this description listed BRepFill_Sweep. Follow-ups filed per consumer.

Full swift test after: 4847 tests in 1347 suites, clean. Filed upstream as OCCT#1418, fix-only, based on b8f597c6; the touched file is byte-identical between upstream master and our V8_0_0_p1 pin. Reproducers, root-cause walkthrough and transcripts: Scripts/repro/522-approx-c0-collapse/.

Release-time steps, deliberately not in this PR

Package.swift's url:/checksum: pin is untouched on purpose. It has to resolve to a release asset that exists, and ci.yml runs swift build against it on every PR to refactor/**, so pointing it at an unpublished tag would 404 the PR. The bump belongs to the release commit that ships this branch:

  1. Attach OCCT.xcframework.zip to the new tag's release. Built artifact: 149,078,261 bytes, sha256 2ccb194e11a675ba6c2769f7a13821da1d75787aa7d8a37e07905ff433915d53. This is the post-GeomConvert_ApproxSurface at GeomAbs_C0 returns a degree-1 collapse while reporting IsDone and a maxError five orders of magnitude too small #522 binary; earlier checksums in this PR's history are superseded. It is sitting at Libraries/OCCT.xcframework.zip.
  2. Package.swift: bump the OCCT url: to that tag and checksum: to the sha256 above, and extend the carried-patch comment block (it currently ends at 0016) to name 0017.
  3. docs/CHANGELOG.md: add #484 to the kernel-patch list on the ## Current: line.

Until then the two consumer paths diverge on purpose: this checkout and every sibling repo path-depending on its Libraries/OCCT.xcframework already have the new kernel, while anything resolving the remote url: stays on v1.15.18.

Also in here

Three things the rebuild surfaced:

  • docs/guides/building-occt.md gains a "Shipping a rebuild" section. The rebuild-to-release sequence existed only as hand-written checklists inside issues, which is how the "bump both url: and checksum:" step gets dropped. It now covers verifying the patch set, verifying object freshness, resuming an interrupted build without rm -rfing the progress, proving the fix reached the binary, and packaging plus pinning.
  • Scripts/patches/README.md gains the 0016 (Resource_Manager::Debug and Storage_Schema::ICurrentData() races surfaced by #371 (upstream OCCT#1398) #374) entry it never got. That README documents every carried patch and jumped straight from 0015 to 0017.
  • Two stale workflow references corrected. ci.yml credited kernel rebuilds to a kernel-rebuild.yml that has never existed in this repo's history, and building-occt.md pointed readers at a nonexistent build-occt.yml build service. Kernel rebuilds are a manual local Scripts/build-occt.sh run; there is no CI job for them.

Pre-existing em-dashes in the large files touched here (docs/CHANGELOG.md, Scripts/patches/README.md) were left alone. Stripping 774 and 92 of them would bury this change in unrelated churn, and OKF-STANDARD.md does not require a backfill pass. Everything added here is em-dash free.

Refs #512, #484, #518, #374, #555, #501

Closes #522

🤖 Generated with Claude Code

@gsdali
gsdali force-pushed the chore/512-rebuild-kernel-0017 branch 4 times, most recently from 491ce9d to 2764aed Compare July 30, 2026 03:04
@gsdali gsdali changed the title chore(#512): rebuild OCCT.xcframework so carried patch 0017 reaches the shipped kernel chore(#512) + fix(#518): rebuild the kernel with patch 0017, and redesign 0016's Storage_Schema half after upstream review Jul 30, 2026
@gsdali
gsdali force-pushed the chore/512-rebuild-kernel-0017 branch 2 times, most recently from ed005d2 to 6bd7800 Compare July 30, 2026 22:35
@gsdali gsdali changed the title chore(#512) + fix(#518): rebuild the kernel with patch 0017, and redesign 0016's Storage_Schema half after upstream review chore(#512) + fix(#518, #555): rebuild the kernel, redesign 0016 after upstream review, add 0018 Jul 30, 2026
@gsdali
gsdali force-pushed the chore/512-rebuild-kernel-0017 branch from 94fdca8 to e777d79 Compare July 31, 2026 00:55
@gsdali gsdali changed the title chore(#512) + fix(#518, #555): rebuild the kernel, redesign 0016 after upstream review, add 0018 chore(#512) + fix(#518, #555, #522): rebuild the kernel, redesign 0016 after upstream review, add 0018 and 0019 Jul 31, 2026
@gsdali
gsdali force-pushed the chore/512-rebuild-kernel-0017 branch from 4e38a34 to 4373df6 Compare July 31, 2026 04:19
gsdali and others added 7 commits July 31, 2026 15:35
…he shipped kernel

Patch 0017 (the #484 null-context guard in ShapeFix_ComposeShell and
ShapeUpgrade_WireDivide) was carried but inert. build-occt.sh applies every patch in
Scripts/patches/ before each cmake build, so a patch only takes effect once the
xcframework is rebuilt from source. Rebuilt from V8_0_0_p1 + all 17 carried patches.

Verified against the rebuilt binary with no override-linked TUs, which matters because
all prior #484 evidence was override-linked: that proves the patch works, not that it
shipped.

- repro_484_crash.mm: both ctx=NO cases complete where they were KILLED BY SIGNAL 11.
- repro_484_equivalence.mm: all four ctx=yes fingerprints identical to the pre-rebuild
  values, now recorded in that reproducer's README as reference values for later rebuilds.
- swift test: 4633 tests in 1317 suites, clean.
- occt-src audited before building: modified in exactly the 47 files patches 0001-0017
  touch, no stray probes, all 17 reverse-apply cleanly.
- tsan-stress.sh deliberately not run: 0017 is a null-handle guard on a single-threaded
  path and touches no concurrency surface.

Package.swift's url:/checksum: pin is deliberately untouched. It must resolve to a release
asset that exists, and CI resolves it on every PR, so the bump belongs to the release
commit that ships this branch. Until then URL-resolving consumers stay on the previous
kernel while checkouts with a local Libraries/ get the new one.

Also here:
- docs/guides/building-occt.md gains a "Shipping a rebuild" section. That sequence only
  existed as hand-written checklists in issues, which is how the "bump both url: and
  checksum:" step gets dropped.
- Scripts/patches/README.md gains the 0016 (#374) entry it never got.
- Two stale workflow references corrected: ci.yml credited kernel rebuilds to a
  kernel-rebuild.yml that has never existed, and building-occt.md pointed readers at a
  nonexistent build-occt.yml build service.

Refs #512, #484

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

Patch 0016 (#374) fixed two kernel races. The Resource_Manager::Debug half (a
file-scope static bool made std::atomic<bool>) is unchanged. The Storage_Schema half
is redesigned: gkv311's review of our upstream PR OCCT#1399 pointed out that
ICurrentData(), a function-local static Handle(Storage_Data) shared by every
Storage_Schema in the process, does not need a lock at all if it becomes a
mutable Handle(Storage_Data) field on the class.

That holds up against the source. Every Storage_Schema in the tree is constructed
locally by its caller and used only there (PCDM_StorageDriver::Write, and
PCDM_ReadWriter_1 at three sites); none is cached or shared, and
Storage_CallBack::Add/Write/Read all take the driving schema as an argument, so every
callback re-entry lands back on the same instance. ICurrentData() and
ISetCurrentData() were both private with no reference outside the class, so removing
them breaks nothing. The remaining Storage_Schema users elsewhere in the tree
(BinLDrivers, XmlMDF, StdLDrivers) touch only its statics CheckTypeMigration() and
ICreationDate(), neither of which reads the current data.

The field is also strictly stronger on the failure #374 reported: under the mutex, a
throwaway Storage_Schema built by PCDM_ReadWriter_1 during an unrelated Open() still
nullified an in-flight Write()'s current data, it just did so without a data race.
Under the field it cannot reach another instance's data at all. It narrows exactly one
thing, two threads driving the same instance, which no caller does.

Also drops the static from AddPersistent()'s TCollection_AsciiString aTypeName scratch
variable: a smaller process-wide hazard in the same class that the removed mutex had
covered incidentally. It is assigned from the tName argument and read two lines later,
so static only ever saved an allocation.

Patch number stays 0016 (a corrected design, not a new fix), matching the 0011
precedent from #341 to #363; the file is renamed from ...-Storage_Schema-mutex-374 to
...-Storage_Schema-per-instance-374 since the old name no longer describes it.

Verification, all against the rebuilt kernel:
- Scripts/repro/374 harness (the unguarded variant): 0 races, 0 save/load/verify
  failures at 8x50, 8x30 and 10x60. Same result the mutex version gave; stock is
  13 races + SIGABRT.
- Full Scripts/tsan-stress.sh run gate, 10 scenarios, clean.
- clang-format clean on all three touched files.
- OCCT.xcframework repackaged from the same three slices (only TKernel changed).
  #484's two reproducers still behave as #512 recorded them, with all four ctx=yes
  fingerprints unchanged.
- swift test: 4666 tests in 1322 suites, clean.

Test counts in the #512 docs are refreshed from 4633/1317 to 4666/1322: #487, #488 and
#485 all landed in the base between that measurement and this one. One full-suite run
during this validation hit a pre-existing timing flake in #300's
stepRobustRepairCancellation (1 failure in 9 isolated runs of that suite, unrelated to
this change), filed as #525 rather than absorbed here.

Upstream OCCT#1399 still carries the mutex version. Updating it is the remaining half
of #518 and is not done here.

Refs #518, #374
Refs #525

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
New carried patch 0018, in the arc-length samplers behind quasiUniformParameters,
drawUniform, uniformAbscissa and the sampleUniform family. #501 closed the
OCCTSwift-reachable half of the first defect at the bridge layer; this is the kernel
side, which every other OCCT consumer still carried.

NbPoints() was not bounded by the requested count. GCPnts_UniformAbscissa sizes its
parameter array at theNbPoints + 5 and walks until it reaches the end parameter or runs
out of room, so a caller sizing its own buffer from the request could be handed more
points than it asked for; GCPnts_QuasiUniformAbscissa inherited this for every curve
that is neither Bezier nor BSpline.

The cause is a tolerance mismatch, not an off-by-one. Perform terminates on
abs(aUi - aUU2) <= theEPSILON, where theEPSILON comes from Resolution(), which converts
a 3D tolerance to a parametric one using the curve's largest derivative. On an ellipse
with major radius 1e6 and minor radius 1e-3 that is about 1e-13, while the local
derivative at the end is 1e-3, making the parametric tolerance that actually
corresponds to 1e-7 in 3D about 1e-4. The walk stops 1.557e-08 short, does not call
that done, takes one more step and appends a point measurably 1.175e-10 away in 3D from
its neighbour.

Perform now also accepts a point that coincides with the end in 3D within the caller's
tolerance. Clamping myNbPoints to the request, the other obvious fix, would drop the
exact end parameter and leave the distribution stopping short of the curve, which is
the defect #501 found in the one bridge function that already clamped.

A point count below 2 stored out of bounds. Both classes document theNbPoints >= 2 and
enforce it with Standard_ConstructionError_Raise_if, which compiles to nothing under
No_Exception, how the shipped Release kernel is built (#487).
GCPnts_QuasiUniformAbscissa's Bezier/BSpline branch then allocated
NCollection_HArray1<double>(1, theNbPoints), an empty range, and unconditionally stored
into index 1 of it: an uncatchable SIGSEGV, same class as #263, #310, #317 and #318.
Both classes now leave the object not done for such a count. The Raise_if stays, so a
build with exceptions enabled throws exactly as before.

Verification, against the rebuilt kernel with no override-linked TUs:
- 17 curve types x counts 2..200 x both classes, 6766 configurations: 232 lines change
  and they are exactly the 232 that were over-requesting. Every other line is
  byte-identical, and the last parameter is still exactly the end on the changed ones.
- Over-request goes from 22 counts to 0 on the pathological ellipse; every degenerate
  count on every curve returns IsDone() == false for both classes, replacing a SIGSEGV,
  a 5-point answer or a 1-point answer depending on curve and class.
- clang-format clean on both touched files.
- swift test: 4842 tests in 1346 suites, clean.

Ships in the same rebuild as #512 and #518. Not yet filed upstream.

One measurement trap is recorded in the reproducer README: building the override-linked
TUs without -DNo_Exception brings the Raise_if back, so the degenerate cases abort with
an uncaught Standard_ConstructionError instead of crashing or returning not-done, which
reads like a patch that broke something. It measures a kernel nobody ships.

Refs #555, #501

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OCCT#1417 carries 0018 against master b8f597c6. The two touched files are byte-identical
between upstream master and our V8_0_0_p1 pin, checked before branching, so the patch is
the same change on both. clang-format and the non-ASCII check pass locally against OCCT's
own config.

Filed as a fix PR with no companion reproducer issue, which is what maintainer dpasukhi
asked for on OCCT#1409: an issue is for when you are not working on a PR. The policy
amendment recording that goes to main separately, since it is not part of this release.

Refs #555

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

New carried patch 0019. A radius-10 sphere approximated at C0 and tolerance 1e-3 came
back as a degree-1, 2-pole-in-U B-spline: a straight line across the full 2*pi of its
longitude, deviating by the sphere's own diameter of 20, while isDone said the tolerance
was met and maxError said 1.07e-4. A bicubic Bezier at C0 collapsed to a 2x2 bilinear
patch reporting 4.08e-15 at every tolerance from 1e-1 down to 1e-7 -- tightening the
request changed nothing.

One line in AdvApp2Var_ApproxF2var::mma2ce1_ explains both. It partitions a single
scratch allocation into seven buffers, ipt4 for XMAXJU (the maxima of the U Jacobi
polynomials) and ipt5 for XMAXJV, then fills both from ipt5:

    mma2jmx_(ndjacu, iordru, &wrkar_off[ipt5]);   /* -> should be ipt4 */
    mma2jmx_(ndjacv, iordrv, &wrkar_off[ipt5]);

XMAXJU is never written. mma2ce2_ still reads it at ipt4, where the allocation left
whatever was there, in practice zeros, and hands it to mma2er1_/mma2er2_, whose entire
error model is |PATJAC(i,j)| * XMAXJU(i - 2*(IORDRU+1)) * XMAXJV(j - 2*(IORDRV+1)). A
zero XMAXJU zeroes every term.

Two silent consequences. The interior approximation error of a patch is reported as
exactly 0 whatever the discarded coefficients are, so mma2ce2_'s tolerance test can never
fire on it and MaxError() only ever reflects the boundary-iso errors AddErrors adds
afterwards. And mma2er2_, asked for the lowest degree whose truncation error still fits
the tolerance, always answers NDMINU, the floor derived from the constraint order and the
neighbouring isos, because every candidate scores 0.

Where that floor is low the fit collapses onto it. C0 gives IORDRU = 0, and a full
sphere's V-boundary isos degenerate to its two poles, one coefficient each, so NDMINU is
1. C1 and C2 hide the collapse, their floor already being 8, but not the misreported
error, which was never specific to C0.

Degree collapse per se was never the defect: a cylinder trimmed in V legitimately fits at
vDegree 1, it is linear there, and reported correctly before and after.

The write also overruns. mma2jmx_ writes ndjacu + 1 - 2*(IORDRU+1) doubles and the ipt5
slot is sized for the ndjacv equivalent, so a request with MaxDegU well above MaxDegV runs
past XMAXJV into the VECERR slot behind it. Benign in practice, VECERR being re-zeroed on
entry to mma2ce2_ and the run staying inside the single allocation, but out of bounds for
the buffer it was given.

Fix: target ipt4 from the U call. AdvApp2Var_Context's own two mma2jmx_ calls, the only
others in the tree, already write to separate per-direction arrays.

Measured across a 98-case sweep, 7 surface families x all 9 (uCont, vCont) combinations of
C0/C1/C2 plus C0/C0 at five tolerances: results whose real deviation exceeds the reported
maxError by more than 10x go from 12 to 0, and those exceeding it at all from 17 to 1, the
survivor a Bezier reproduced exactly and reporting 9.95221e-15 against a measured
9.96978e-15. Every reported error rises slightly, which is the interior contribution being
counted for the first time. Before and after transcripts committed alongside the
reproducers.

GeomConvert_ApproxSurface is not a leaf. BRepFill_Sweep, GeomFill_Sweep,
BRepOffset_Offset, GeomLib, ShapeCustom_BSplineRestriction, ShapeCustom_ConvertToBSpline,
ShapeConstruct, ShapeUpgrade_UnifySameDomain and GeomConvert_1 all call it, and
GeomPlate_MakeApprox drives AdvApp2Var_ApproxAFunc2Var directly. Most request C1 or C2, so
the collapse could not reach them, but the always-zero interior error could, and two
healing paths reach C0 on purpose: ShapeConstruct::ConvertSurfaceToBSpline and
ShapeCustom_BSplineRestriction both loop the requested continuity down to 0 on failure,
then accept the result on MaxError() <= tol.

The xcframework is rebuilt with 0019 in all three slices. Verified against it with no
override-linked TUs, matching the override-linked prediction line for line; full swift
test, 4847 tests in 1347 suites, clean. The Package.swift url/checksum bump stays the
release commit's job, as with 0017 and 0018.

Issue491SurfaceApproxParityTests.maxErrorDescribesTheSharedFit had to exclude .c0 when it
was written, because "sampled deviation <= reported maxError" failed there on OCCT's own
numbers; that exclusion is gone and it now checks every request. The four new tests in
Issue522ApproxC0CollapseTests were run against a deliberately reverted kernel and all four
fail there with exactly the numbers above; the fifth, the V-linear cylinder control,
passes either way.

okf's carried-patches table is taken from main, where it was brought up to date with rows
0013-0018 after this branch forked, so the new row lands on a current table rather than
one six rows short.

Closes #522

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
OCCT#1418 carries 0019 against master b8f597c6. The touched file is byte-identical
between upstream master and our V8_0_0_p1 pin, checked before branching, so the patch is
the same change on both.

Filed as a fix PR with no companion reproducer issue, the rule maintainer dpasukhi gave on
OCCT#1409 and the one 0018/OCCT#1417 already followed.

Refs #522

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…f its entries are comments

BRepFill_Sweep.cxx:1162 sits inside a /* */ block spanning :1064-:1179, and
BRepFill_Filling.cxx:712 is a //-commented line. Neither is compiled, so neither
constructs a GeomConvert_ApproxSurface, and BRepFill_Sweep should not have been in
0019's blast-radius list in the patch notes, the changelog, CLAUDE.md, the reproducer
README or either PR description. Corrected in all of them, with the trap named so the
next reader does not re-add them from the same grep.

The same pass found what the list was missing. ShapeCustom_ConvertToBSpline does not
construct an approximation itself: it calls ShapeConstruct::ConvertSurfaceToBSpline, and
forces cnt = GeomAbs_C0 for any offset surface (ShapeCustom_ConvertToBSpline.cxx:148, a
1999 workaround for a hang). So that path did not degrade into the collapsing continuity,
it started there. BRepOffset_Offset.cxx:1626 is a live PrecisCode 1 site absent from the
bridge's own census.

The reproducer README now carries the live sites as a table with the PrecisCode each
passes, and separates "constructs one" from "reaches one".

Blast-radius follow-ups filed: #570 the healing paths that decide on MaxError() <= tol,
#571 GeomPlate_MakeApprox driving AdvApp2Var_ApproxAFunc2Var directly, #572 whether the
C1/C2 consumers moved, #573 the bridge's PrecisCode census.

Refs #522, #570, #571, #572, #573

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gsdali
gsdali force-pushed the chore/512-rebuild-kernel-0017 branch from 4373df6 to 5fb6e0c Compare July 31, 2026 05:40
@gsdali
gsdali merged commit 01c9b3b into refactor/381-pass1b Jul 31, 2026
1 of 2 checks passed
@gsdali
gsdali deleted the chore/512-rebuild-kernel-0017 branch July 31, 2026 06:10
gsdali added a commit that referenced this pull request Jul 31, 2026
Merging with the macOS CI check red, same known gap as #519 — confirmed via the log this is the
identical inherited failure (22 issues, all Issue522ApproxC0CollapseTests/Issue491 C0 assertions),
not anything new introduced by this PR. refactor/381-pass1b's tip now permanently carries #519's
new regression tests, so every PR based on it will show this same red check until a release bumps
Package.swift's pin — this PR (kernel-integration.yml) doesn't fix that for itself (it only
triggers on Scripts/patches/** changes, which this PR doesn't touch), but it's exactly the tool
that gives a clean, unambiguous signal for the *next* PR that does.

This PR itself touches no Swift/bridge code, only CI config and docs.
gsdali added a commit that referenced this pull request Jul 31, 2026
…ness

Merging with the macOS CI check red, same known kernel-pin gap as #519/#576/#586 — refactor/381-pass1b's tip permanently carries #519's regression tests until a release bumps Package.swift's pin. Confirmed via local run against the actual patched kernel: exactly the same 23-issue Issue522/Issue491 signature, nothing new from this PR (which is comment/docs-only in OCCTBridge.h + a tooling script).

Two conflicts resolved during merge, both stale-branch artifacts (this PR's branch predates #507/#508's already-merged fixes to the same two OCCTBridge.h index entries) — kept the already-more-complete HEAD content in both spots, verified via `check-bridge-index.py` (0 stale, 5/5 self-test) after resolution. Third conflict in check-bridge-index.py itself: kept #510's continuation-line-aware parser (a strict superset of the current parenthetical-stripping one, already implied by the file's own already-merged docstring).
gsdali added a commit that referenced this pull request Jul 31, 2026
Merging with the macOS CI check red, same known kernel-pin gap as #519/#564/#576/#586. Confirmed locally against the actual patched kernel: exactly the same 23-issue Issue522/Issue491 signature, nothing new.

Clean cherry-pick, zero conflicts. Verified with the PR's own new check-null-handle-guards.py: "All bridge functions guard the geometry handle as well as the wrapper pointer."
gsdali added a commit that referenced this pull request Jul 31, 2026
…adius

Merging with the macOS CI check red, same known kernel-pin gap as #519/#564/#574/#576/#586.

Resolved the predicted trivial conflict with #574 in OCCTGeom2dGccCirc2dTanOnRad: both PRs insert an independent guard as the function's first statement (null-handle from #574, zero-radius from #575), kept both. Verified post-merge: all 22 of this PR's own tests pass (including the merged function's own case), and check-null-handle-guards.py still reports clean (#574's fix intact).
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