2 follow-up flagships: symfony-0001 + pyright-0001 (Wave 17 backlog cleared)

Both were flagged in Wave 17 survey as borderline real defects deferred
for follow-up because the fix needed careful design beyond a single-line
set hoist. Both shipped now with full bench + ticket + intel.

UNDF-1310 symfony-0001 (HIGH) - PropertyAccessor::writeCollection.
  Doctrine entity collection diff: in_array($item, $collection, true)
  per item in $previousValue, then in_array($item, $previousValue, true)
  per item in $collection. O(P*C). Fix: dual lookup
  (SplObjectStorage for objects + serialize-keyed array for scalars,
  in_array fallback for resources). Bench: 5.2x at P=C=100,
  88x at P=C=2000.

UNDF-1311 pyright-0001 (HIGH) - CallHierarchyProvider outgoing/incoming
  call dedup. _outgoingCalls.find / _incomingCalls.find with composite
  key (uri, range) walks the list per call expression. O(C^2). Fix:
  parallel Map<string, entry> keyed by composite serialized form
  (uri|start.line|start.char|end.line|end.char). Bench: 2.7x at C=100,
  22x at C=2000.

Total session flagships: 11 (was 9) — 7 CWE-407 + 3 MOAD-0003 + 1 MOAD-0004.
Wave 17 borderline backlog now empty.
This commit is contained in:
russell@unturf.com 2026-04-26 12:36:07 -04:00
parent 2eea7d128c
commit 24052ec668
No known key found for this signature in database
11 changed files with 758 additions and 0 deletions

View file

@ -0,0 +1,103 @@
# UNDF: UNDF-2026-000001311
# CWE-407: Algorithmic Complexity — O(C^2) -> O(C) in
# CallHierarchyProvider {outgoing,incoming} call dedup
#
# Defect: packages/pyright-internal/src/languageService/callHierarchyProvider.ts
# has TWO parallel patterns that linear-scan the recorded calls list to
# dedup by (uri, range):
#
# L394-396 (outgoing):
# let outgoingCall = this._outgoingCalls.find(
# (outgoing) => outgoing.to.uri === callDest.uri &&
# rangesAreEqual(outgoing.to.range, callDest.range)
# );
#
# L608-610 (incoming):
# let incomingCall = this._incomingCalls.find(
# (incoming) => incoming.from.uri === callSource.uri &&
# rangesAreEqual(incoming.from.range, callSource.range)
# );
#
# Per discovered call expression, the find() walks the list from index 0.
# For C call expressions: O(C^2). For glue functions with 1000+ call sites
# (utility/dispatcher/middleware patterns common in large Python codebases),
# per-IDE-request cost is 1M+ comparisons.
#
# Fix: maintain a parallel Map<string, entry> keyed by composite
# (uri | start.line | start.character | end.line | end.character). Lookup
# via map.get(key) is O(1); list still preserves discovery order and is
# what getOutgoingCalls()/getIncomingCalls() returns.
#
# Complexity gate (defects/pyright/bench/bench-pyright-0001.py):
# C=2000: defective ~46ms, fixed <3ms (>=20x speedup)
# k-scaling 5x: time ratio must be <17.5x
--- a/packages/pyright-internal/src/languageService/callHierarchyProvider.ts
+++ b/packages/pyright-internal/src/languageService/callHierarchyProvider.ts
@@ -287,7 +287,16 @@ class CallFinder extends ParseTreeWalker {
private _evaluator: TypeEvaluator;
private _fs: ReadOnlyFileSystem;
private _outgoingCalls: CallHierarchyOutgoingCall[] = [];
+ // Parallel map for O(1) dedup lookup; key is composite (uri|range).
+ // _outgoingCalls remains the discovery-ordered list returned to callers.
+ private _outgoingCallsByKey: Map<string, CallHierarchyOutgoingCall> = new Map();
+ private static _callKey(uri: string, range: Range): string {
+ return `${uri}|${range.start.line}|${range.start.character}|`
+ + `${range.end.line}|${range.end.character}`;
+ }
+
constructor(parseResults: ParseResults, evaluator: TypeEvaluator, fs: ReadOnlyFileSystem) {
super();
this._parseResults = parseResults;
@@ -391,11 +400,12 @@ class CallFinder extends ParseTreeWalker {
// Is there already a call recorded for this destination? If so,
// we'll simply add a new range. Otherwise, we'll create a new entry.
- let outgoingCall: CallHierarchyOutgoingCall | undefined = this._outgoingCalls.find(
- (outgoing) => outgoing.to.uri === callDest.uri && rangesAreEqual(outgoing.to.range, callDest.range)
- );
+ const dedupKey = CallFinder._callKey(callDest.uri, callDest.range);
+ let outgoingCall: CallHierarchyOutgoingCall | undefined =
+ this._outgoingCallsByKey.get(dedupKey);
if (!outgoingCall) {
outgoingCall = {
to: callDest,
fromRanges: [],
};
this._outgoingCalls.push(outgoingCall);
+ this._outgoingCallsByKey.set(dedupKey, outgoingCall);
}
@@ -420,7 +430,9 @@ class CallVisitor extends ParseTreeWalker {
private _evaluator: TypeEvaluator;
private _functionNode: FunctionNode;
private _fileUri: Uri;
- private readonly _incomingCalls: CallHierarchyIncomingCall[] = [];
+ private _incomingCalls: CallHierarchyIncomingCall[] = [];
+ // Parallel map for O(1) dedup lookup; key is composite (uri|range).
+ private _incomingCallsByKey: Map<string, CallHierarchyIncomingCall> = new Map();
constructor(
parseResults: ParseResults,
@@ -605,11 +617,15 @@ class CallVisitor extends ParseTreeWalker {
// Is there already a call recorded for this caller? If so,
// we'll simply add a new range. Otherwise, we'll create a new entry.
- let incomingCall: CallHierarchyIncomingCall | undefined = this._incomingCalls.find(
- (incoming) => incoming.from.uri === callSource.uri && rangesAreEqual(incoming.from.range, callSource.range)
- );
+ const dedupKey = `${callSource.uri}|${callSource.range.start.line}|`
+ + `${callSource.range.start.character}|`
+ + `${callSource.range.end.line}|`
+ + `${callSource.range.end.character}`;
+ let incomingCall: CallHierarchyIncomingCall | undefined =
+ this._incomingCallsByKey.get(dedupKey);
if (!incomingCall) {
incomingCall = {
from: callSource,
fromRanges: [],
};
this._incomingCalls.push(incomingCall);
+ this._incomingCallsByKey.set(dedupKey, incomingCall);
}