chore(#512) + fix(#518, #555, #522): rebuild the kernel, redesign 0016 after upstream review, add 0018 and 0019 - #519
Merged
Conversation
Closed
12 tasks
gsdali
force-pushed
the
chore/512-rebuild-kernel-0017
branch
4 times, most recently
from
July 30, 2026 03:04
491ce9d to
2764aed
Compare
gsdali
force-pushed
the
chore/512-rebuild-kernel-0017
branch
2 times, most recently
from
July 30, 2026 22:35
ed005d2 to
6bd7800
Compare
gsdali
force-pushed
the
chore/512-rebuild-kernel-0017
branch
from
July 31, 2026 00:55
94fdca8 to
e777d79
Compare
gsdali
force-pushed
the
chore/512-rebuild-kernel-0017
branch
from
July 31, 2026 04:19
4e38a34 to
4373df6
Compare
…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
force-pushed
the
chore/512-rebuild-kernel-0017
branch
from
July 31, 2026 05:40
4373df6 to
5fb6e0c
Compare
This was referenced Jul 31, 2026
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).
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.
Four changes that have to ship in one kernel binary: the #512 rebuild, #518's redesign of patch
0016after upstream review, #555's new patch0018, and #522's new patch0019. 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 forShapeFix_ComposeShell::Perform(),ShapeFix_ComposeShell::SplitEdges()andShapeUpgrade_WireDivide::Perform()) was carried but inert.build-occt.shapplies every carried patch before each cmake build, so a patch only reaches callers onceLibraries/OCCT.xcframeworkis rebuilt from source. It is now rebuilt fromV8_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
0017arrived onrefactor/381-pass1bwhen #511 merged partway through the rebuild, so there is nostacking left. Rebased three times as #516 (#487), #517 (#488) and #509 (#485) landed in the base;
each conflicted on the same
docs/CHANGELOG.mdinsertion 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 binarythat 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+ patches0001-0016:After, same source file, same compile command:
The other six cases in
repro_484_crash.mmare unchanged.repro_484_equivalence.mm: all fourctx=yesfingerprints byte-identical to the pre-rebuild values, and all fourctx=NOfingerprints now complete and match theirctx=yescounterpart.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:
Other gates:
swift test: 4842 tests in 1346 suites passed, clean, withOCCTSWIFT_BRIDGE_PREBUILTunset so the bridge compiled from source (it was ambiently set in the shell, which would have masked fix(#484): two upstream null-context SIGSEGVs patched, Face.fixed given a context, connectedFaces covered and de-first-of-N'd #511's own bridge changes).Scripts/tsan-stress.shdeliberately not run, per the checklist:0017is a null-handle guard on a single-threaded path.Build hygiene
The checklist's two build traps were both checked rather than assumed:
occt-srccontents. Modified in exactly the 47 files patches0001-0017touch, 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 intoocct-src. All 17 logged asalready 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.#518:
Storage_Schema's scratch handle becomes a member, not a guarded globalPatch
0016(#374) fixed two kernel races. ItsResource_Manager::Debughalf (a file-scopestatic boolmadestd::atomic<bool>) is untouched. Reviewing our upstream PR OCCT#1399, maintainer gkv311 pointed out theStorage_Schemahalf needs no lock at all ifICurrentData()becomes amutable Handle(Storage_Data)field on the class.That checks out against the source, and I verified the premise before writing any code:
Storage_Schemainstance is ever shared. All four constructions in the tree are function-local (PCDM_StorageDriver::Write,PCDM_ReadWriter_1at 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.Storage_CallBack::Add/Write/Readall take the driving schema as an argument, so theBindType/AddPersistent/PersistentToAddcalls made from callbacks go through*this.ICurrentData()andISetCurrentData()were bothprivatewith no reference anywhere outside the class. OtherStorage_Schemausers in the tree (BinLDrivers,XmlMDF,StdLDrivers) touch only its staticsCheckTypeMigration()andICreationDate(), neither of which reads the current data.mutableis required, becauseWrite,Clear,HasTypeBinding,BindType,TypeBinding,AddPersistentandPersistentToAddare allconst.The field is also strictly stronger on the failure #374 reported. Under the mutex, a throwaway
Storage_Schemabuilt byPCDM_ReadWriter_1during an unrelatedOpen()still nullified an in-flightWrite()'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()'sstatic TCollection_AsciiString aTypeName, a second process-wide mutable object in the same class (the 374 reproducer's own README had noted it). Thestaticis dropped: the value is assigned from thetNameargument and read two lines later, so it only ever saved an allocation. Currently unreachable in-tree, since both concretePCDM_StorageDriversubclasses overrideWrite, butAddPersistentis public and a third-party subclass can reach it.Patch number stays
0016, a corrected design rather than a new fix, matching the0011precedent from #341 to #363. The file is renamed to...-Storage_Schema-per-instance-374.patchsince the old name saysmutex.Verification, all against the rebuilt kernel:
Scripts/repro/374harness, the unguarded variant: 0 races, 0 save/load/verify failures at 8×50, 8×30 and 10×60. Same result the mutex version gave; stock is 13 races plus SIGABRT.Scripts/tsan-stress.sh rungate, 10/10 scenarios clean. Run twice, the second time against the exact final patch after theaTypeNamechange.clang-formatclean on all three touched files (OCCT's own.clang-format).ctx=yesfingerprints identical.One full-suite run during this validation hit a pre-existing timing flake in #300's
stepRobustRepairCancellation, which reports.importFailedwhere it expects.cancelledabout 1 run in 9 in isolation. Unrelated to either change here (Storage_Schemais 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 - Summaryconvention asFoundation Classes - ...), rewrote the PR description, and replied to gkv311's review, including an offer to split theaTypeNamechange into its own commit and to splitResource_ManagerfromStorage_Schemainto two PRs if preferred.clang-formatand the non-ASCII check are clean locally against OCCT's own.clang-format; CI is running.The upstream branch's diff and our carried
0016are the same change, verified by diffing them (identical apart from one hunk-header context label, which differs because our tree also carries0012inResource_Manager.cxx). ThemyCurrentDatadoc comment is one line rather than three, perokf/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
GCPntspoint-count defects, patched as0018GCPnts_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 sizedtheNbPoints + 5and 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:Performterminates on a parametric epsilon thatResolution()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 < aDeltagate so it runs on the final step rather than every step.A point count below 2 stored out of bounds. Both classes document
theNbPoints >= 2and enforce it withStandard_ConstructionError_Raise_if, which compiles to nothing underNo_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; theRaise_ifstays, so an exceptions-enabled build throws exactly as before.Verification, against the rebuilt kernel with no override-linked TUs:
IsDone() == falsefor 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-formatclean 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_Exceptionbrings theRaise_ifback, so the degenerate cases abort with an uncaughtStandard_ConstructionErrorrather 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*piof its longitude, deviating by the sphere's own diameter of 20, whileisDonesaid the tolerance was met andmaxErrorsaid 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,ipt4forXMAXJU(the maxima of the U Jacobi polynomials) andipt5forXMAXJV, then fills both fromipt5:XMAXJUis never written;mma2ce2_reads whatever the allocation left atipt4, 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 zeroXMAXJUzeroes every term. The interior error of every patch then evaluates to exactly 0, which meansmma2ce2_'s tolerance test can never fire on it andMaxError()only ever describes the boundary iso-curves; andmma2er2_, asked for the lowest degree whose truncation error still fits, always answersNDMINU, 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, soNDMINUis 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 atvDegree = 1, and does so before and after.The write also overruns:
mma2jmx_writesndjacu + 1 - 2*(IORDRU+1)doubles into a slot sized for thendjacvequivalent, running intoVECERRwhenMaxDegU >> MaxDegV. Benign, sinceVECERRis re-zeroed on entry tomma2ce2_, but out of bounds.Fix: target
ipt4from the U call. One character.AdvApp2Var_Context's own twomma2jmx_calls, the only others in the tree, already write to separate per-direction arrays.Verification, against the rebuilt kernel with no override-linked TUs:
maxErrormaxErrorat all98 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 assweep-before.txt/sweep-after.txt. The mechanism was confirmed directly by dumping the buffer through an-O0override-link:xmaxju[8] = 0 0 0 0 0 0 0 0stock,0.9682 0.986 1.078 ...fixed.This one does change OCCTSwift behaviour, unlike
0017and0018.Surface.approximated(tolerance:continuity:)andSurface.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 amaxErrorthat bounds the real deviation.Issue491SurfaceApproxParityTests.maxErrorDescribesTheSharedFithad to exclude.c0when it was written for exactly this reason; that exclusion is gone and it now checks every request. Four new tests inIssue522ApproxC0CollapseTestswere 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,ShapeConstructandGeomConvert_1construct aGeomConvert_ApproxSurface,ShapeCustom_ConvertToBSplineandShapeUpgrade_UnifySameDomainreach one, andGeomPlate_MakeApproxdrivesAdvApp2Var_ApproxAFunc2Vardirectly. Most request C1 or C2, so the collapse could not reach them, but the always-zero interior error could — andShapeConstruct::ConvertSurfaceToBSplineandShapeCustom_BSplineRestrictionboth loop the requested continuity down to 0 on failure, then accept the result onMaxError() <= tol.ShapeCustom_ConvertToBSplinestarts 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:1162sits inside a/* */block andBRepFill_Filling.cxx:712is//-commented — a filename-level grep counts both, which is how an earlier revision of this description listedBRepFill_Sweep. Follow-ups filed per consumer.Full
swift testafter: 4847 tests in 1347 suites, clean. Filed upstream as OCCT#1418, fix-only, based onb8f597c6; the touched file is byte-identical between upstreammasterand ourV8_0_0_p1pin. Reproducers, root-cause walkthrough and transcripts:Scripts/repro/522-approx-c0-collapse/.Release-time steps, deliberately not in this PR
Package.swift'surl:/checksum:pin is untouched on purpose. It has to resolve to a release asset that exists, andci.ymlrunsswift buildagainst it on every PR torefactor/**, so pointing it at an unpublished tag would 404 the PR. The bump belongs to the release commit that ships this branch:OCCT.xcframework.zipto the new tag's release. Built artifact: 149,078,261 bytes, sha2562ccb194e11a675ba6c2769f7a13821da1d75787aa7d8a37e07905ff433915d53. 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 atLibraries/OCCT.xcframework.zip.Package.swift: bump the OCCTurl:to that tag andchecksum:to the sha256 above, and extend the carried-patch comment block (it currently ends at0016) to name0017.docs/CHANGELOG.md: add#484to 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.xcframeworkalready have the new kernel, while anything resolving the remoteurl:stays onv1.15.18.Also in here
Three things the rebuild surfaced:
docs/guides/building-occt.mdgains a "Shipping a rebuild" section. The rebuild-to-release sequence existed only as hand-written checklists inside issues, which is how the "bump bothurl:andchecksum:" step gets dropped. It now covers verifying the patch set, verifying object freshness, resuming an interrupted build withoutrm -rfing the progress, proving the fix reached the binary, and packaging plus pinning.Scripts/patches/README.mdgains the0016(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 from0015to0017.ci.ymlcredited kernel rebuilds to akernel-rebuild.ymlthat has never existed in this repo's history, andbuilding-occt.mdpointed readers at a nonexistentbuild-occt.ymlbuild service. Kernel rebuilds are a manual localScripts/build-occt.shrun; 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, andOKF-STANDARD.mddoes 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