Skip to content

chore(#784): duplication rescan census, six issues filed - #797

Merged
gsdali merged 2 commits into
refactor/381-pass1bfrom
chore/784-duplication-rescan
Aug 8, 2026
Merged

gsdali merged 2 commits into
refactor/381-pass1bfrom
chore/784-duplication-rescan

Conversation

@gsdali

@gsdali gsdali commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

What & why

The duplication-rescan half of #784. Passes 1a (#380) and 1b (#381) are closed at 60 sub-issues,
both built as segmented subsystem reads. On 2026-08-07, six duplications were found in code both
passes had already audited, every one by accident while doing something else. #784 asks for a
derived, re-runnable census of the rest, following Scripts/derive-bridge-header-split.py's
pattern, per docs/v2.0.0-plan.md's census-once rule.

Scope: analysis and artifact, not fixes. A separate agent is doing #784's deprecation half on
chore/784-deprecation-adjudication, editing Sources/OCCTSwift heavily. This PR makes zero
changes to Sources/OCCTBridge or Sources/OCCTSwift: every finding is filed, not fixed, per the
explicit instruction not to conflict with that concurrent pass. None of the findings needed a
breaking change to fix, so this constraint cost nothing extra.

Closes #784 (the rescan half only; the deprecation-adjudication half is tracked and closed
separately on the other branch)

Updated after review (see "Review round" below): four correctness bugs in the detector itself
fixed, two internal duplications in the detector fixed, a committed report snapshot removed, a
resource-cleanup nit fixed. All findings and issue numbers below are current as of that round.

What shape the six share

Not one shape, three, two each (full detail with citations in
Scripts/repro/784-duplication-rescan/README.md):

The artifact

Scripts/repro/784-duplication-rescan/detect-duplicate-logic.py: a MOSS-style k-token-shingle
clone detector over every bridge C/Objective-C++ function (Sources/OCCTBridge/src/*.mm) and every
Swift func (Sources/OCCTSwift/*.swift), scored by containment (not Jaccard, so a small
duplicated function embedded inside a much bigger caller, the PR#778 shape, still registers), with
a document-frequency boilerplate exclusion (the bridge's own necessarily-repetitive C-wrapper
preamble is expected, not a finding) and a delegation exclusion (A calling B by name is
composition, not duplication, including the accidentally-discovered case of a Swift function
nested inside another, where containment is approximately 1.0 by pure syntax).

Tuning was measured, not guessed: the first pass at loose settings reported 3219 of the
bridge's 4152 units as candidate pairs, almost entirely OCCT's own deliberately parallel 3D/2D
class hierarchies (Geom_Curve/Geom2d_Curve) and already-deduplicated forwarding shims. Six
successive tightenings, each validated by reading a sample, converge on 38 bridge candidates and 21
Swift candidates. Full trace in the README.

Review round: 8 findings addressed, none in Sources/

Code review found 8 issues, all in the census script itself; nothing it touched required a
Sources/ change.

Four confirmed correctness bugs, all under-reporting (the failure mode that matters most for a
script whose whole claim is "the tree is clean of shape A", since a clean run cannot be told apart
from a blind one):

  1. swift_functions()'s parameter-list regex ([^)]*) was not nesting-aware, so a closure-typed
    default parameter (handler: () -> Void = { }) hijacked the body scan and extracted the
    closure's own empty { } instead of the real body. Fixed with paren-depth tracking.
  2. strip_comments() had no case for single-quoted char literals, so '"' was misread as opening
    a real string and the scan swallowed real comments looking for a closing ".
  3. The same gap let a {/} inside a char literal corrupt c_functions()'s brace-depth walk;
    measured case: one phantom brace was enough to swallow the next function's entire definition.
  4. The delegation exclusion matched a name anywhere in the other body, including inside a string
    literal, so a sibling's name quoted in a log message was mistaken for a real call.

Bugs 2-4 share one fix: strip_comments() now blanks the content of string AND char literals
(previously it copied double-quoted string content through unchanged and had no char-literal
handling at all). Each of the four has a dedicated --self-test fixture, confirmed to fail against
a hybrid build carrying the pre-fix extraction code and pass against the fix, per
okf/policies/prove-the-test-fails.md.

Did the fix move the candidate counts? Checked, not assumed. Bridge: unchanged (38 pairs, 4152
units, byte-identical report). Swift: unit count rose from 3374 to 3471 (+97 previously-invisible
functions, mostly tuple-typed parameter lists hitting bug 1's mechanism), and the pair list changed
by exactly two: strokeWidth (PDFExporter/SVGExporter) dropped below the size floor once its
string-literal case labels stopped inflating its token count (already filed in #795, noted there
as a comment so a fresh run isn't confusing); TransformUtils.displacement/transformation
appeared as a genuine new pair (the old parser found zero functions in that file at all, confirmed
directly), added to #796 as a sixth pair.

Two internal duplications, found in the duplication detector itself: c_functions() and
swift_functions() carried an identical brace-matching loop; bridge_units()/swift_units()
duplicated the same iterate/strip/extract/tokenize/construct sequence. Both factored into one
shared helper each (_brace_match(), _units_from_sources()), no behavior change.

One committed artifact dropped. An earlier revision committed full-report.txt, a captured
run's output. Review cited this repo's own Documentation Standards ("no duplicate content", "code
reviews and handoff docs are ephemeral, don't commit them") against it, correctly: the file had
already drifted from a fresh run by the time of the review. Dropped rather than kept; the six filed
issues each carry their own complete evidence independent of it (see README's "On not committing a
captured report").

One resource-cleanup nit: bridge_sources()/swift_sources() now close each file handle
explicitly (with open(...)) instead of relying on refcounting.

--self-test is now 15/15 (11 bridge, 4 Swift, up from 11/11), with a removal-matrix row added
for the four parser fixes (revert to pre-fix extraction code, confirm exactly those four fail and
nothing else changes). Full table in the README.

Findings: 6 issues filed, 0 fixed here

All labelled type:chore, cluster:kernel, off the v2.0.0 milestone (matches #761's own precedent:
internal consolidation changes no public behavior, so it can ship in any release).

What the census does NOT find (read before trusting a small number)

Documented in the README's own section: shape B (#777, no shared text), shape C (intra-function
branch duplication, dev-tooling population), any unit below min_distinctive (two real one-line
duplicates in #792 were found by reading the file directly, not by the script, noted rather than
hidden), and generic/computed-property Swift units swift_functions() doesn't extract standalone.

Gates and tests

  • All 9 gate scripts, plain run and --self-test: clean. (check-bridge-index,
    check-null-handle-guards, check-docs-defaults, derive-bridge-header-split --verify,
    count-operations, census-unmeasured-values, check-changelog-transcription,
    derive-shape-domain-split, derive-swift-file-split.)
  • Full swift test: 5510 tests, 1440 suites, 0 failures (unchanged from before the review
    round, since nothing under Sources/ changed).
  • swift build: clean (pre-existing unrelated deprecation warnings only).
  • Scripts/tsan-stress.sh not run: this PR touches no concurrency-relevant code (a standalone
    Python script; zero changes under Sources/).

CHANGELOG entry

#784 duplication rescan: a committed census artifact, six issues filed, no source changes

Added Scripts/repro/784-duplication-rescan/detect-duplicate-logic.py, a re-runnable k-token-shingle
clone detector over the bridge's C functions and the Swift API's funcs, built to answer whether
Pass 1a/1b (#380/#381) missed more of the duplication shape six accidentally-found instances
revealed on 2026-08-07. It found 38 bridge and 21 Swift candidate pairs at its tuned thresholds; 6
issues (#791-#796) were filed from what measured as genuine, none fixed in this PR. No public API
changed.

SemVer impact

NONE. Zero changes under Sources/OCCTBridge or Sources/OCCTSwift; this PR adds a script and its
README under Scripts/repro/, outside the public Swift API surface count-operations.py tracks,
and files GitHub issues. No consumer-visible effect, no migration.

Checklist

  • New or changed behavior is covered by a unit test in the same PR: the detector's own
    --self-test (15/15), which is the "behavior" a census script has.
  • Every new test/self-test case was run once with its subject broken: the full removal matrix
    (15 cases across 7 mechanism rows plus the parser-fix reversion row) is in the README,
    including one row's flip explained structurally rather than left as an unexplained "adds
    nothing."
  • The CHANGELOG entry above is complete, and docs/CHANGELOG.md is not in this diff.
  • The SemVer impact above is stated, and docs/SEMVER.md is not in this diff.

Notes for the reviewer

Passes 1a/1b (#380/#381) are closed but six duplications surfaced in already-audited code
by accident on 2026-08-07. This adds the derived, re-runnable census #784 asks for
(Scripts/repro/784-duplication-rescan/detect-duplicate-logic.py, a k-token-shingle clone
detector over bridge .mm functions and Swift API funcs) and files six issues (#791-#796)
from what it found. No source changes: analysis and artifact only, per this branch's scope
constraint against the concurrent deprecation-adjudication pass.

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

gsdali commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

Reviewed. Nothing blocking, and the headline finding corrects my own framing of #784.

The six do not share one shape, and that matters

#784 asserted the dominant shape was "two entry points onto the same OCCT call that drifted", and
told you to confirm it from the six rather than assume. You confirmed it is half, and the split
is the useful result:

shape instances findable by a clone detector?
A: two entry points, near-identical bodies #761, PR #768, PR #778 yes, and this is what the artifact targets
B: same question, two algorithms, no shared text #777 no, a real scope boundary
C: duplication inside one function's branches, or between dev-tooling files PR #774, PR #773 no, a different detection problem

That is worth more than the candidate list. A token-based detector can never find shape B, and
#777 is the instance: PocketFeature.isOpen's hand-rolled incidence and BRepGraph's indexed one
answer the same question with no text in common. Any future claim that the tree is duplication-clean
has to say "clean of shape A", and now it can.

Verified independently

I checked #795's headline pair against the source rather than the score.
PDFExporter.primitiveOps() and SVGExporter.primitiveOps() are byte-for-byte identical across
all nine lines
. Score 1.00 with 170 shared shingles is accurate, not an artifact of the metric.

The tuning is the part I would have got wrong

Loose settings reported 3219 of 4152 bridge units as pairs, almost all of them OCCT's own
deliberate 3D/2D mirrors. Shipping that number would have been worse than shipping nothing, because
a report that flags 78% of the corpus trains people to ignore it, which is the same argument
default_since() makes in the changelog checker.

Six tightenings, each validated by reading samples, converging on 38 plus 21. That is a measured
convergence rather than a threshold picked to produce a comfortable number.

The removal matrix

11/11, and the row I care about is min_distinctive, which you found structurally redundant with
min_shared and said so with the reason
, rather than leaving it as an unexplained "adds nothing".
That is exactly what the policy asks for: a green row is ambiguous, and the disambiguation is the
deliverable.

The nested-Swift-function discovery is a good one. A local function's body is trivially contained in
its enclosing function by pure syntax, so it scores near 1.0 with no duplication present at all,
21 times on the real corpus. Rescued by the existing delegation exclusion and captured as its own
fixture, so a future change that breaks the exclusion shows up as 21 false positives rather than
silently.

On filing rather than fixing

Correct per the scoping, and the reason still holds: the deprecation agent is editing
Sources/OCCTSwift heavily. Zero changes under Sources/ in this PR, confirmed.

Six issues, #791 to #796. #795 is the substantial one and should not sit in the backlog long: three
exporters independently reimplementing a pipeline that DrawingDispatch.swift exists to share is
the shape that produces a bug in one and not the others.

@secondmouseAU-bot secondmouseAU-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated high-effort code review (8 findings).

yield name, start, i


SWIFT_FUNC = re.compile(r"func\s+(\w+)\s*\([^)]*\)(?:\s*(?:async|throws|rethrows))*\s*(?:->\s*[^\{]+?)?\{")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correctness (confirmed): SWIFT_FUNC's parameter-list ([^)]*) and return-type ([^\{]+?) regexes aren't nesting-aware, so a Swift func whose signature has a closure-literal default parameter value hijacks the function's body-opening brace. E.g. for func onComplete(handler: () -> Void = { }) { doWork(); doWorkAgain() }, swift_functions() extracts { } (the closure's own empty body) instead of the real two-statement body — verified by running the exact code against this input. Any genuine duplicate logic inside the real body becomes invisible to the whole detector: a silent false negative, exactly the failure mode ("a census that says nothing wrong when it should") this script exists to avoid.

j = n if j < 0 else j + 2
out.append("".join(c if c == "\n" else " " for c in text[i:j]))
i = j
elif text[i] == '"':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

correctness (confirmed): strip_comments() has no special-casing for single-quoted C/C++ char literals, so a char literal containing a double quote (e.g. '"') is misread as opening a string literal, and the scan then swallows everything — including real ////* comment markers — up to the next literal " it finds. For source containing if (c == '"') { return 1; } followed on the next line by // real comment with "quote inside, running this exact function leaves the comment line completely un-blanked (output byte-identical to input). The un-stripped comment prose then gets tokenized as code by tokenize(), either manufacturing a spurious shared-token duplicate pair or diluting a real duplicate's shingle set below the 0.85 containment threshold — with no indication in the report that this happened.

j = n if j < 0 else j + 2
out.append("".join(c if c == "\n" else " " for c in text[i:j]))
i = j
elif text[i] == '"':

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (confirmed): Separately from the comment-swallowing issue on this same line — strip_comments() excludes double-quoted string contents from brace-scanning but does nothing for single-quoted char literals, so a {/} inside a char literal is left in the stripped text and counted as a real brace. Bridge .mm source with a character literal like '{'/'}' (common in delimiter/format-char comparisons, e.g. the OBJ/PLY exporter code this script itself analyzes) throws off c_functions()'s depth counter, silently truncating or extending the extracted function body into the next function — corrupting the very unit boundaries the duplicate-detection scoring depends on, with no error raised.

NOT_A_FUNCTION = {"if", "for", "while", "switch", "catch", "return"}


def c_functions(text):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (confirmed): c_functions() (199-215) and swift_functions() (221-235) copy-paste an identical brace-depth-matching while-loop verbatim, differing only in which regex feeds it. A bug fix to the brace-matching walk (e.g. handling the char-literal gap flagged elsewhere in this review) has to be applied twice by hand in the same file; missing one copy leaves the two extractors silently inconsistent — exactly the copy-paste-with-drift pattern this script exists to catch, now present inside the detector itself.

Unit = collections.namedtuple("Unit", "name kind file line tokens related")


def bridge_units(sources):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (confirmed): bridge_units() (273-284) and swift_units() (287-297) duplicate the same source-iteration / strip / extract / tokenize / Unit-construction sequence, differing only in the extractor function called and how kind is computed. Any change to how a Unit is built from a source file (e.g. adding a field, or fixing the strip_comments bugs flagged elsewhere in this review) must be edited in two near-identical 12-line functions instead of one, and the two can silently diverge if only one copy is updated.

results = []
for (i, j), shared in shared_counts.items():
if shared < min_shared:
continue

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (confirmed): The delegation exclusion (units[i].name in token_sets[j]) matches the function's name anywhere it appears as a token in the other body — including inside a string literal — not just an actual call. A genuine near-duplicate pair where one function's name happens to be quoted in the other's body (e.g. an NSLog/assert message referencing the sibling function by name) is silently reclassified as "delegation" and dropped from the candidate list — the same under-counting failure mode the script's own docstring says every hand-built census in this repo's history has had (#558/#571/#583/#595/#640), now possibly reproduced inside the tool meant to fix it.

@@ -0,0 +1,209 @@
bridge (Sources/OCCTBridge/src): 38 candidate pair(s) out of 4152 unit(s)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (plausible): Committing this captured run snapshot duplicates content the script itself derives and can drift from the tree, in tension with this repo's own Documentation Standards ("No duplicate content — one canonical location per topic. Link, don't copy"; "Code reviews and handoff docs are ephemeral — don't commit them"). A future reader who opens full-report.txt instead of re-running the script (which the README itself warns may be necessary since it "may drift from a fresh run") can act on stale candidate pairs/line numbers that no longer match the current source tree.


def bridge_sources():
paths = sorted(glob.glob(os.path.join(BRIDGE_SRC_DIR, "*.mm")))
return [(p, open(p, errors="ignore").read()) for p in paths]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cleanup (plausible): bridge_sources()/swift_sources() open every source file without a context manager or explicit close, relying on CPython refcounting to reclaim the file handle. On an interpreter or run where the file object's refcount doesn't drop immediately (e.g. a different Python implementation, or if sources is retained by a caller), file descriptors accumulate across the hundreds of bridge/Swift source files scanned, risking a "too many open files" error on a large enough corpus.

… drop snapshot

PR #797 review found eight issues in the duplication-rescan census script, all fixed here,
none touching Sources/:

Four correctness bugs, all under-reporting:
- swift_functions()'s non-nesting parameter-list regex let a closure-typed default parameter
  hijack the body scan, hiding the real function body entirely.
- strip_comments() had no case for single-quoted char literals, so '"' was misread as opening
  a real string and swallowed real comments looking for a closing quote.
- The same gap let a brace inside a char literal corrupt the brace-depth walk, in one measured
  case swallowing the next function's entire definition.
- The delegation exclusion matched a name anywhere in a body, including inside a string literal,
  so a sibling named in a log message was mistaken for a real call.

Bugs 2-4 share one fix: strip_comments() now blanks string/char literal content instead of
copying it through. Each bug has a self-test fixture proven to fail against a hybrid build
carrying the pre-fix code and pass against the fix (11 bridge, 4 Swift fixtures, 15/15 total).

Re-running the fixed detector found the parser fix moved the Swift unit count (3374 to 3471,
+97 previously-invisible functions) and the pair list by exactly two: strokeWidth dropped below
the size floor (already filed in #795, noted there) and TransformUtils.displacement/transformation
appeared as a genuine new pair (added to #796 as a sixth pair). Bridge side unchanged.

Also: factored c_functions()/swift_functions()'s duplicated brace-matching loop and
bridge_units()/swift_units()'s duplicated construction sequence into shared helpers (the
detector contained the duplication it detects); dropped the committed full-report.txt snapshot
per this repo's own "no duplicate content" standard, since every filed issue already carries
its own complete evidence; closed file handles explicitly instead of relying on refcounting.

All six filed issues (#791-#796) and two follow-up comments updated for the TransformUtils/
strokeWidth findings and for writing-style compliance (em-dashes, "--" as a sentence dash).

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

gsdali commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator Author

All eight actioned. One thing needed repairing that the report claimed was done, and the answer
to the question I actually asked is a good one.

The counts did move, and the ratio is the result

I asked whether fixing the parser surfaces units it was dropping. It does: the Swift corpus goes
3,334 units to 3,431, ninety-seven functions that were invisible. Verified by running both
parsers side by side rather than taking the number.

Ninety-seven newly visible functions produced exactly one new duplicate pair,
TransformUtils.displacement/transformation at 0.92. That ratio is the useful output. The parser
bug was real and worth fixing, and it was not concealing a mass of duplication, which strengthens
the rescan's "clean of shape A" conclusion rather than undermining it. A fix that had surfaced
thirty new pairs would have meant the original run proved nothing.

The bridge report is byte-identical, so the C parser was already sound.

Shingle counts drop where string literals are now blanked, formatTolerance going 256 to 205, which
is the fix working rather than a regression.

What I had to repair

Two follow-up comments, on #795 and #796, were posted as the literal text @/tmp/issue796_comment.md.
gh issue comment --body does not expand @file; only --body-file does. Both comments conveyed
nothing
, and the temp files were gone by the time I checked, so I reconstructed them from the
detector output and patched them in place.

Worth naming because the report said the new pair was "added to #796" and it was not. A shell
quoting slip is invisible to every check in this repo: the command exits 0, the comment exists, and
only reading it shows it is a file path. Read back what you post.

The rest

The four correctness bugs are fixed at one root, strip_comments() now blanking literal content, and
each carries a fixture proven against a hybrid build with the pre-fix code. 15/15, up from 11/11.

Findings 5 and 6, the detector's own duplicated brace walk and unit construction, are factored. That
mattered more than it looks: finding 3 was a bug in the brace walk, so the copy-paste would have
required fixing it twice.

Dropping full-report.txt is the right call and the README says why: every filed issue already
carries its evidence independently, so the snapshot was evidence of nothing that was not already
durable.

Also cleared every em-dash from the script, the README, all six issue bodies and the PR body. The
original artifact violated the writing standard throughout, which I should have caught when I
reviewed it the first time.

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.

2 participants