Merge pull request #1494 from tangency-labs/fix/ub175-v101-guards

difftest: run the UB-175 inputs on v1.0.1; narrow the UB-241 stop to the GlueVertices path
This commit is contained in:
bherbruck
2026-10-29 06:13:14 -04:00
committed by GitHub
11 changed files with 178 additions and 128 deletions
+12 -11
View File
@@ -375,10 +375,10 @@ fn to_output(o: &[f64]) -> Result<Output, HarnessError> {
}
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
if ub_175(i)? {
if ub_242(i)? {
return Ok(Output::rejected(
"UB-175: ~SmProtoTopologyManager frees a proto edge that a proto vertex \
still lists, then ~SmProtoVertex reads it; not run",
"UB-242: IntersectInsertRelate ignores a failed imprint that edited a B-rep \
and goes on with the half-edited B-reps; not run",
));
}
match ffi::run(&to_call(i)) {
@@ -402,17 +402,18 @@ fn measured_output(o: &[f64]) -> Result<Output, HarnessError> {
Ok(to_output(head)?.values("measures", measures.to_vec()))
}
/// Whether the Boolean reaches UB-175 (`plan/upstream-issues.md`): the teardown of
/// `IntersectInsertRelate`'s proto topology manager reads a proto edge it already
/// freed, so the input is undefined in C++ (AGENTS.md section 4, step 5.6) and [`cpp`]
/// rejects it. Decided from the inputs and the Rust side only: the port replays the
/// teardown, goes on with its defined result and records `"UB-175"` on the kernel
/// ([`Kernel::upstream_ub_passed`]).
fn ub_175(i: &Inputs) -> Result<bool, HarnessError> {
/// Whether the Boolean reaches UB-242 (`plan/upstream-issues/`): an imprint in
/// `IntersectInsertRelate` fails after it may have edited a B-rep, and upstream goes on
/// with the half-edited B-reps, so the input is undefined in C++ (AGENTS.md section 4,
/// step 5.6) and [`cpp`] rejects it. Decided from the inputs and the Rust side only: the
/// port stops there (tangency-labs/smlib af8db37) and records `"UB-242"` on the kernel
/// ([`Kernel::upstream_ub_passed`]). (Until the v1.0.1 pin this guard was UB-175's, the
/// proto topology manager teardown, which v1.0.1 defines.)
fn ub_242(i: &Inputs) -> Result<bool, HarnessError> {
let mut k = Kernel::default();
match rust_call_with(&mut k, i, &|_| {}) {
Ok(_) | Err(Stop::Deferred(_) | Stop::Torus(_)) => {
Ok(k.upstream_ub_passed.contains(&"UB-175"))
Ok(k.upstream_ub_passed.contains(&"UB-242"))
}
Err(Stop::Harness(e)) => Err(e),
}
@@ -4313,15 +4313,12 @@ fn run_part2_op(
o.push(NULL_DEREF);
return Ok(());
}
// (where upstream reads a freed proto edge, UB-175, the shim's verbatim copy
// unlinks the stale entries and answers -2; the port goes on with its defined
// teardown and records the condition on the kernel. After a failed imprint,
// UB-242, both stop and the shim answers -2.)
// (after a failed imprint, UB-242, both stop and the shim answers -2. The
// v1.0.1 manager teardown is defined, so a run the port records UB-175 for,
// a v1.0.0 defect, is compared like any other.)
k.upstream_ub_passed.clear();
let r = ti.intersect_insert_relate(k, two, TI_HANDLE);
if k.upstream_ub_passed.contains(&"UB-242")
|| k.upstream_ub_passed.contains(&"UB-175")
{
if k.upstream_ub_passed.contains(&"UB-242") {
o.push(NULL_DEREF);
return Ok(());
}
@@ -3,9 +3,8 @@
//! Coordinates/dimensions are bounded by 1e100 through the shared reader, leaving
//! headroom for endpoint addition and squared-distance products in constructors.
//! This is the oracle's supported numeric domain, not a kernel input restriction.
//! UB-175 [PSIRT] in `IntersectInsertRelate` / `~SmProtoTopologyManager` is not
//! exercised by the current empty/vertex/open-line/plane operand families (no
//! closed edges). Adding circles or closed wires requires rechecking that guard.
//! UB-175 [PSIRT] (`~SmProtoTopologyManager`) is a v1.0.0 defect: v1.0.1's teardown
//! is defined, so closed edges need no guard here.
use crate::{call_with_buffer, FfiError};
mod sys {
@@ -45,9 +45,9 @@
//! are not vertices where upstream dereferences them, edges of a vertex without
//! curve, faces without surface, 46 without the maps reaching `Relate`'s insert, 52's
//! edges without curve, vertices or (the B-rep vertex's) primary edge use) and
//! answers `-2`; it also answers `-2` for a 47 whose manager teardown would read a
//! freed proto edge (UB-175: the shim runs a verbatim copy of
//! `IntersectInsertRelate` that unlinks the stale entries first).
//! answers `-2`; it also answers `-2` for a 47 whose failed imprint may have edited a
//! B-rep (UB-242: the shim runs a verbatim copy of `IntersectInsertRelate` that stops
//! there).
//!
//! 49 checks the index against both classifications only: upstream bounds its
//! `index + 1` writes by the B-rep classification's size, so with a shorter other
@@ -30,11 +30,10 @@
//! deleting a proto edge on its own (the difftest's script ops) stays upstream's.
//! Without it, the stale id names a deleted slot and the later passes fail with
//! `InvalidInput`.
//! - **Deleted vertex in `GlueCoincidentBrepVertices`** (SolidCraft fix,
//! tangency-labs/smlib da8d05e, UB-241). For two coincident vertices joined by
//! several common edges, upstream goes on to the next edge after squeezing or
//! gluing away the second vertex, and uses it after the free. The loop stops once
//! that vertex is recorded as deleted, in both B-reps' halves.
//! - **UB-241 in `GlueCoincidentBrepVertices`** (SolidCraft fix, tangency-labs/smlib
//! da8d05e): use-after-free in `GlueCoincidentBrepVertices` on the `GlueVertices`
//! path; the port stops before that call. The delete branch is defined and runs as
//! upstream.
//! - **B-rep access.** Topology objects are the caller's handles `O`, as in
//! [`p1`](super::p1). What these functions read from vertices and edges, and the
//! store new curves go to, is [`ProtoBrep`] (of `topology.proto_topology_p2`, which
@@ -1155,13 +1154,10 @@ impl<O: ClassObject> ProtoTopologyManager<O> {
}
} else {
for &edge in edges {
// SolidCraft change (tangency-labs/smlib da8d05e): squeezing
// or gluing for an earlier common edge deleted v2; every
// later step would use it (and the edges it ended) after the
// free, so stop.
if deleted_v.get(jj).copied().flatten() == Some(v2) {
break;
}
// UB-241 (SolidCraft fix, tangency-labs/smlib da8d05e): stop
// before the GlueVertices path; the delete branch runs as
// upstream.
let v2_deleted = deleted_v.get(jj).copied().flatten() == Some(v2);
let curve_handle =
brep.edge_curve(edge).ok_or(dangling("edge is unknown"))?;
let ivl = brep
@@ -1171,6 +1167,10 @@ impl<O: ClassObject> ProtoTopologyManager<O> {
.approximate_length(k, &ivl, 5, false)?
.0;
if len < xs {
if v2_deleted && brep.edge_other_vertex(edge, Some(v1))? != Some(v1)
{
break;
}
// If any to-be-deleted topology is mated, remove the
// relationship when done.
let edge_mate = mates
@@ -1230,6 +1230,10 @@ impl<O: ClassObject> ProtoTopologyManager<O> {
Err(e) => ret = Err(e),
}
} else {
// UB-241: stop before GlueVertices
if v2_deleted {
break;
}
// coincident Vertices connected by a long Edge: Glue.
if v1 != v2 {
ignore_soft(brep.glue_vertices(
@@ -1847,24 +1851,35 @@ mod tests {
/// Vertices 0 and 1 of B-rep 1 coincide and are joined by the long edges 0 and 1
/// (both on curve 0); vertices 2 and 3 of B-rep 2 are far apart. Records every
/// `GlueVertices` call and refuses one of a vertex already glued away.
/// `GlueVertices` call and refuses one of a vertex already glued away; records
/// every `SqueezeEdge` and `DeleteEdge` (after a squeeze, `edge_other_vertex` answers
/// vertex 0).
struct Pool {
curves: Vec<Curve>,
glued: Vec<(H, H)>,
squeezed: Vec<H>,
deleted: Vec<H>,
}
impl Pool {
fn new() -> Self {
Self::with_length(10.0)
}
/// As [`Pool::new`] with edges `length` long.
fn with_length(length: f64) -> Self {
let line = Line::create_line_segment(
3,
Vec3::new(0.0, 0.0, 0.0),
Vec3::new(10.0, 0.0, 0.0),
Vec3::new(length, 0.0, 0.0),
None,
)
.unwrap();
Self {
curves: vec![Curve::from(line)],
glued: Vec::new(),
squeezed: Vec::new(),
deleted: Vec::new(),
}
}
}
@@ -1905,7 +1920,11 @@ mod tests {
Ok(Some(vtx(0)))
}
fn edge_other_vertex(&self, _edge: H, _vertex: Option<H>) -> Result<Option<H>> {
Ok(Some(vtx(1)))
Ok(Some(if self.squeezed.is_empty() {
vtx(1)
} else {
vtx(0)
}))
}
fn vertex_point(&self, vertex: H) -> Result<Point3> {
Ok(match vertex.0 {
@@ -1983,10 +2002,11 @@ mod tests {
&mut self,
_k: &mut Kernel,
_brep: H,
_edge: H,
edge: H,
_survivor: H,
) -> Result<()> {
Err(SolidError::invalid_input("test", "unused"))
self.squeezed.push(edge);
Ok(())
}
fn brep_point_3d_classify(
&mut self,
@@ -2019,7 +2039,8 @@ mod tests {
}
impl BrepGlue<H> for Pool {
fn delete_edge(&mut self, _k: &mut Kernel, _brep: H, _edge: H) -> Result<()> {
fn delete_edge(&mut self, _k: &mut Kernel, _brep: H, edge: H) -> Result<()> {
self.deleted.push(edge);
Ok(())
}
fn glue_vertices(
@@ -2079,6 +2100,37 @@ mod tests {
assert_eq!(m.proto_vertex(pv).unwrap().vertex_definitions().len(), 1);
}
#[test]
fn glue_deletes_a_second_short_common_edge_after_the_squeeze() {
// the delete branch runs as upstream after a squeeze;
// boolean.api_brep crossing_creases_union_d1e-05
let mut k = Kernel::default();
let mut pool = Pool::with_length(1e-6);
let b = H(100, ClassObjectKind::Other);
let e = Brep::new();
let mut m = ProtoTopologyManager::new(&k, (b, &e), (b, &e), b, 1e-5, 1e-3, -1.0).unwrap();
let p = Point3::new(0.0, 0.0, 0.0);
let pv = m.new_proto_vertex(p, 1e-5).unwrap();
m.add_proto_vertex(pv);
let r = H(50, ClassObjectKind::Region);
for (v1, v2) in [(vtx(0), vtx(2)), (vtx(1), vtx(3))] {
let mut vd =
VertexDefinition::new(&k, &pool, &region_pc(r), &region_pc(r), p, 1e-5, 1e-6, None)
.unwrap();
vd.set_final_vertex(1, Some(v1));
vd.set_final_vertex(2, Some(v2));
let id = m.insert_vertex_definition(vd).unwrap();
m.add_vertex_definition(pv, id).unwrap();
}
m.glue_coincident_brep_vertices(&mut k, &mut pool, &mut NoMates)
.unwrap();
assert_eq!(pool.squeezed, vec![edg(0)]);
assert_eq!(pool.deleted, vec![edg(1)]);
assert!(pool.glued.is_empty());
assert_eq!(m.proto_vertex(pv).unwrap().vertex_definitions().len(), 1);
}
/// A vertex definition first placed by a grazing crossing is placed again by a
/// steeper one, whose misplacement bound along the edge (gap over sine) is smaller,
/// and not by a later, more grazing one (WR-410, #1059).
@@ -8,8 +8,9 @@
// sm_FindFirstLowestDimensionSol, sm_IntersectShellVertices, sm_IntersectWireLaminaEdges,
// sm_SetEdgeClass, sm_CheckInteriorEdge, sReasonType, sm_TestAndSplitTwoEdges and
// sm_TestAndImprintFace are VERBATIM copies of upstream (commit c9c5979, lines 1272-1572,
// 2676-2829 and 6564-6861) inside namespace sc_ti_p1 (SM_DEBUG_CODE and CBI_CACHE are not
// defined in the reference build).
// 2676-2829 and 6564-6861; byte-identical in v1.0.1 3fd5b95, lines 1272-1572, 2676-2829 and
// 6578-6875) inside namespace sc_ti_p1 (SM_DEBUG_CODE and CBI_CACHE are not defined in the
// reference build).
//
// One entry point, sc_boolean_topology_intersector_p1_eval(op, path, in, n_in, out, cap,
// out_len). Layouts are documented in
@@ -820,62 +821,6 @@ struct TiMgr
};
template struct TiRob<TiMgr, &SmTopologyIntersector::m_pProtoTopoMgr>;
// UB-175: ~SmProtoTopologyManager deletes the listed proto edges before the listed proto
// vertices; each ~SmProtoEdge unlists itself once from its start vertex and once from a
// different end vertex, then each ~SmProtoVertex calls RemoveProtoVertex on every edge it
// still lists. An edge whose ends coincide is listed twice there (AddProtoEdge, DD1) and a
// combined vertex keeps entries of edges that no longer end at it: those entries are freed
// edges. Replays the unlisting on copies and returns whether any listed vertex keeps a
// listed edge; with `unlink`, removes those entries first so the destructor stays defined
// (the script's own manager: its teardown is ours, lesson 23). Scope: only the edge-phase
// unlisting is modelled; a proto vertex freed before the manager's destructor would be a
// different use-after-free and is not flagged (no such path in source for these ops; the
// pre-ready valgrind sample of the op-47 family covers it).
static bool mgr_teardown_stale(SmProtoTopologyManager* mgr, bool unlink)
{
SmTArray<SmProtoVertex*>& pvs = mgr->GetProtoVertices();
SmTArray<SmProtoEdge*>& pes = mgr->GetProtoEdges();
std::map<SmProtoVertex*, std::vector<SmProtoEdge*>> lists;
for (ULONG i = 0; i < pvs.GetSize(); i++)
{
if (pvs[i] == nullptr || lists.count(pvs[i]))
continue;
std::vector<SmProtoEdge*>& l = lists[pvs[i]];
SmTArray<SmProtoEdge*>& src = pvs[i]->GetProtoEdges();
for (ULONG j = 0; j < src.GetSize(); j++)
l.push_back(src[j]);
}
auto unlist = [&](SmProtoVertex* v, SmProtoEdge* e) {
auto it = lists.find(v);
if (it == lists.end())
return;
auto f = std::find(it->second.begin(), it->second.end(), e);
if (f != it->second.end())
it->second.erase(f);
};
std::vector<SmProtoEdge*> freed;
for (ULONG i = 0; i < pes.GetSize(); i++)
{
SmProtoEdge* e = pes[i];
if (e == nullptr || std::find(freed.begin(), freed.end(), e) != freed.end())
continue;
freed.push_back(e);
unlist(e->GetStartProtoVertex(), e);
if (e->GetEndProtoVertex() != e->GetStartProtoVertex())
unlist(e->GetEndProtoVertex(), e);
}
bool stale = false;
for (auto& kv : lists)
for (SmProtoEdge* e : kv.second)
if (std::find(freed.begin(), freed.end(), e) != freed.end())
{
stale = true;
if (unlink)
kv.first->RemoveProtoEdge(e);
}
return stale;
}
enum TiOp : int32_t
{
TI_EXPORT = 0,
@@ -1751,11 +1696,7 @@ void run_script(Rd& rd, Two& t, std::vector<double>& o)
}
}
delete ti; // the destructor frees the table (and its curves), maps and subset
if (mgr)
{
mgr_teardown_stale(mgr, true);
delete mgr;
}
delete mgr;
}
// UB-166: the B-spline surface constructor's sm_FixupKnotVector can raise a v knot
@@ -221,8 +221,9 @@ bool set_rels_maps_ok(SmProtoTopologyManager* mgr, int64_t ctor_kind)
return true;
}
// ImprintIntersections (SmTopologyIntersector.cpp:2302-2413, upstream commit c9c5979)
// VERBATIM, apart from the SM_DEBUG_CODE blocks and the members reached through accessors,
// ImprintIntersections (SmTopologyIntersector.cpp:2302-2413, upstream commit c9c5979;
// byte-identical in v1.0.1 3fd5b95 at the same lines) VERBATIM, apart from the SM_DEBUG_CODE
// blocks and the members reached through accessors,
// plus one addition: `edited` answers whether a failing imprint may have edited a B-rep,
// as the port's ImprintOutcome::failure_edited does. A vertex definition's imprint and the
// glue edit first; an edge definition's imprint edits nothing when it stops before its
@@ -312,16 +313,17 @@ SmStatus ii_verbatim(SmTopologyIntersector* ti, bool& edited)
return eRetStat;
}
// IntersectInsertRelate (SmTopologyIntersector.cpp:1092-1270, upstream commit c9c5979)
// VERBATIM, apart from the
// IntersectInsertRelate (SmTopologyIntersector.cpp:1092-1270, upstream commit c9c5979;
// byte-identical in v1.0.1 3fd5b95 at the same lines) VERBATIM, apart from the
// SM_VALIDATE_TOPOLOGY, SM_DEBUG_CODE and SM_USE_TBB blocks (not defined in the reference
// build) and the members reached through accessors, plus one addition: before the delete,
// whether ~SmProtoTopologyManager would read a freed proto edge (UB-175); if so the stale
// entries are unlinked first, so the run stays defined, and `ub` is set (the port answers
// InvalidInput there). A second addition stops after a failed ImprintIntersections (UB-242)
// whose failure may have edited a B-rep (ii_verbatim above), where upstream goes on with
// half-imprinted B-reps: `ub` is set there too. The real IntersectInsertRelate is no longer exercised by this
// difftest, so re-diff this copy against upstream whenever the pinned commit moves.
// build) and the members reached through accessors, plus one addition: it stops after a
// failed ImprintIntersections (UB-242) whose failure may have edited a B-rep (ii_verbatim
// above), where upstream goes on with half-imprinted B-reps, and sets `ub` (the port
// answers InvalidInput there). v1.0.1's ~SmProtoTopologyManager cuts the proto vertex /
// proto edge links before deleting them, so the teardown is defined (UB-175 is a v1.0.0
// defect) and the copy deletes the manager as upstream does. The real
// IntersectInsertRelate is no longer exercised by this difftest, so re-diff this copy
// against upstream whenever the pinned commit moves.
SmStatus iir_verbatim(SmTopologyIntersector* ti, bool& ub)
{
SmProtoTopologyManager*& m_pProtoTopoMgr = ti->*ti_get(TiMgr());
@@ -357,7 +359,7 @@ SmStatus iir_verbatim(SmTopologyIntersector* ti, bool& ub)
// B-reps; stop here as the port does, deleting the manager as below, and answer -2)
if (eStat != SM_SUCCESS && bEdited)
{
if(m_pProtoTopoMgr) { mgr_teardown_stale(m_pProtoTopoMgr, true); delete m_pProtoTopoMgr ; m_pProtoTopoMgr = NULL; }
if(m_pProtoTopoMgr) { delete m_pProtoTopoMgr ; m_pProtoTopoMgr = NULL; }
ub = true;
return eStat;
}
@@ -366,7 +368,7 @@ SmStatus iir_verbatim(SmTopologyIntersector* ti, bool& ub)
eStat = ti->SetRelationships();
// all done - clean up and exit
if(m_pProtoTopoMgr) { ub = mgr_teardown_stale(m_pProtoTopoMgr, true); delete m_pProtoTopoMgr ; m_pProtoTopoMgr = NULL; }
if(m_pProtoTopoMgr) { delete m_pProtoTopoMgr ; m_pProtoTopoMgr = NULL; }
return eStat;
}
} // namespace
+10 -10
View File
@@ -112,12 +112,12 @@ SmExtent2d domain(const SmSurface& s, const double* f) {
double u=n.GetUMin(),v=n.GetVMin(),du=n.GetUMax()-u,dv=n.GetVMax()-v;
return SmExtent2d(u+du*f[0],v+dv*f[1],u+du*f[2],v+dv*f[3]);
}
// UB-154 (plan/upstream-issues.md): the file-static sm_GetEllipseTrimIntervals
// (SmCone.cpp:5282) ignores the statuses of the smgu_LinePlaneIntersect calls on the
// trimmed cone's two seam lines and then compares and uses the uninitialised dParam[0..1]
// (:5399-5405, :5463-5477). It is reached only from inside IntersectWithCone and
// IntersectWithPlane, with an ellipse they compute, so the shim replays the same calls in
// the same order up to that point (seam_lines_defined: the head of
// UB-154 (plan/upstream-issues/; SmCone.cpp line numbers here are v1.0.1's, 3fd5b95): the
// file-static sm_GetEllipseTrimIntervals (SmCone.cpp:5315) ignores the statuses of the
// smgu_LinePlaneIntersect calls on the trimmed cone's two seam lines and then compares and
// uses the uninitialised dParam[0..1] (:5434-5439, :5505-5521). It is reached only from
// inside IntersectWithCone and IntersectWithPlane, with an ellipse they compute, so the shim
// replays the same calls in the same order up to that point (seam_lines_defined: the head of
// sm_GetEllipseTrimIntervals; the *_ellipse_seams_undefined functions: the paths of the two
// callers that reach it). Status-checked steps that make upstream return before the call
// return false here too.
@@ -149,8 +149,8 @@ bool seam_lines_defined(const SmCone& crCone, const SmExtent2d& crUVDomain,
dParam[1]) == SM_SUCCESS;
}
// SmCone::IntersectWithCone (SmCone.cpp:2604) up to its elliptical branch for two
// cylinders of the same radius whose axes intersect (:3010-3137): four
// SmCone::IntersectWithCone (SmCone.cpp:2605) up to its elliptical branch for two
// cylinders of the same radius whose axes intersect (:3011-3138): four
// sm_AddEllipseIntervalsTrimmedToTwoCones calls, each trimming one ellipse to this cone
// and then to the other one. True when one of those eight trims reads dParam.
bool cone_ellipse_seams_undefined(const SmCone& crThis, const SmExtent2d& crUVDomain,
@@ -232,9 +232,9 @@ bool cone_ellipse_seams_undefined(const SmCone& crThis, const SmExtent2d& crUVDo
return trims_undefined();
}
// SmCone::IntersectWithPlane (SmCone.cpp:3219) up to its elliptical branch (:3664-3834),
// SmCone::IntersectWithPlane (SmCone.cpp:3220) up to its elliptical branch (:3665-3835),
// which trims the ellipse to the cone's natural domain. True when that trim reads dParam.
// No known input reaches it: the generator lines tested at :3716 and :3735 are as long
// No known input reaches it: the generator lines tested at :3717 and :3736 are as long
// as the seams and one of them is the least steep to the plane, so (up to rounding) they
// fail first and return. Kept so the guard follows every path into
// sm_GetEllipseTrimIntervals.
@@ -0,0 +1,17 @@
# topology.proto_topology_p3: the UB-241 stop in GlueCoincidentBrepVertices was broader than the defect (2026-10-29)
**Unit:** `topology.proto_topology_p3` (`ProtoTopologyManager::glue_coincident_brep_vertices`),
found through `boolean.api_brep` case `crossing_creases_union_d1e-05`. Until the v1.0.1 pin the
case was kept from the C++ side by the `boolean.api_brep` UB-175 guard.
**Values.** Both sides succeed; C++ returns 12 faces, Rust 13 (`values.measures.len`: cpp=15
rust=16).
**Cause.** The UB-241 stop (SolidCraft fix, tangency-labs/smlib da8d05e) was broader than the
defect: the defect is on the `GlueVertices` path; the delete branch is safe, so the port now
stops only before `GlueVertices`. With the broader stop the port skipped edge deletes that C++
makes, and one extra edge was related and kept, splitting a face. An ASan run of this case on
v1.0.1 is clean.
**Fix.** Unit test `glue_deletes_a_second_short_common_edge_after_the_squeeze`;
`glue_stops_once_the_second_vertex_is_glued_away` is unchanged.
@@ -0,0 +1,19 @@
Updates: UB-154
#### UB-154 (addendum). Still present in v1.0.1 (3fd5b95)
- **Severity:** Crash / undefined behaviour
- **Area:** Surfaces and volumes
- **Location:** `source/SMLib/src/SmCone.cpp:5434`-`5439` and `:5505`-`5521` (v1.0.1),
`sm_GetEllipseTrimIntervals` (def. `:5315`, file static).
- **Class:** uninitialised read: the statuses of the two seam-line `smgu_LinePlaneIntersect`
calls are still ignored, and `dParam[0]`, `dParam[1]` are still compared and used when a call
fails.
- **Change in v1.0.1:** the function now takes its trim circles from the trim domain's v limits
and accepts seam-line crossings within a tolerance scaled by each seam's length
(`dStartParamTol`, `dEndParamTol`) instead of `dScaledZero`; `GlobalSurfaceIntersect` also
sends cylinders to `IntersectWithCone`. None of this checks the two statuses.
- **How checked:** read from the v1.0.1 source. The `surface.cone_p3` shim's replay of the
function's head (STEP domain, corners, seam vectors, the two intersection statuses) was compared
line by line with v1.0.1 and is unchanged, so its guard still refuses exactly the inputs that
reach the read.
- **Status:** present in v1.0.1; the difftest guard stays.
@@ -0,0 +1,22 @@
Updates: UB-175
#### UB-175 (addendum). Not reproduced on v1.0.1 under AddressSanitizer
- **Severity:** Crash / undefined behaviour
- **Area:** Intersection
- **Location:** `source/SMLib/src/SmProtoTopology.cpp`, `SmProtoTopologyManager::~SmProtoTopologyManager`
(v1.0.1 clears every proto edge's end vertices and every proto vertex's edge list before the
teardown deletes them).
- **Change:** with the reference pinned to v1.0.1 (3fd5b95), the `boolean.api_brep` runner no longer
keeps the inputs that reached this use-after-free on v1.0.0 from the C++ side; they now run the
real SmApi Boolean and its proto topology teardown, and are compared like any other case. Its
guard now rejects only the inputs that reach UB-242 (a failed imprint that may have edited a
B-rep, where the port stops and upstream goes on); on v1.0.0 those also reached this teardown. The
`boolean.topology_intersector_p1`/`_p2` shim no longer unlinks stale proto edge entries before
deleting a proto topology manager, and its runner no longer answers `-2` for an
`IntersectInsertRelate` the port records UB-175 for (the UB-242 stop is unchanged).
- **How checked:** SMLib, SM_API and the difftest shims were rebuilt at 3fd5b95 with AddressSanitizer
(the flags and defines of `ffi.env` otherwise unchanged). A default `boolean.api_brep` run and a
4000-seed run, which include every input the earlier addenda found reaching the teardown, ran
with no sanitizer report.
- **Status:** not reproduced on v1.0.1; the difftest guard for it is removed. The record stays: it describes
a defect of v1.0.0.