B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections. Squash of 94 local commits onto remote master.
86 lines
3.5 KiB
Diff
86 lines
3.5 KiB
Diff
From: agent-blackops <blackops@unturf.com>
|
||
Date: Thu, 26 Mar 2026 00:00:00 +0000
|
||
Subject: [PATCH] source/chain: replace @@inference_stack class-variable Array with thread-local Set
|
||
|
||
CWE-407 + thread-safety defect in Chain#infer_from_definitions.
|
||
@@inference_stack was a class-level Array shared across all threads.
|
||
Two defects:
|
||
|
||
1. CWE-407: `@@inference_stack.include?(pin)` is O(D) per pin where D
|
||
is the current inference depth. Called inside two loops in
|
||
`infer_from_definitions` — once for typify pins and once for probe
|
||
pins — making total cost O(D × |pins|) per infer call.
|
||
|
||
2. Thread-safety: a class variable mutated with push/pop from multiple
|
||
Ractors/threads (e.g., concurrent LSP requests) causes races.
|
||
One thread's push/pop interleaves with another's, corrupting the
|
||
recursion guard.
|
||
|
||
Fix: replace with `Thread.current[:solargraph_inference_stack]`,
|
||
initialised lazily as `Set.new` per thread. Set#include? is O(1).
|
||
Each thread owns its own stack, eliminating the race. add/delete
|
||
replace push/pop (order is irrelevant for a recursion guard).
|
||
|
||
@@inference_depth and @@inference_cache are left as class variables —
|
||
they are either counters (depth) or caches that benefit from sharing
|
||
(cache). Only the identity-guard set needs per-thread isolation.
|
||
|
||
Defect-Id: solargraph-0001
|
||
Severity: MEDIUM
|
||
CWE: CWE-407 (Inefficient Algorithmic Complexity), CWE-362 (Race Condition)
|
||
---
|
||
lib/solargraph/source/chain.rb | 20 ++++++++++----------
|
||
1 file changed, 10 insertions(+), 10 deletions(-)
|
||
|
||
diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb
|
||
index xxxxxxx..yyyyyyy 100644
|
||
--- a/lib/solargraph/source/chain.rb
|
||
+++ b/lib/solargraph/source/chain.rb
|
||
@@ -38,7 +38,7 @@ module Solargraph
|
||
@@inference_stack = []
|
||
+ # CWE-407 fix: removed @@inference_stack class variable — now thread-local Set (see below)
|
||
@@inference_depth = 0
|
||
@@inference_invalidation_key = nil
|
||
@@inference_cache = {}
|
||
|
||
@@ -220,6 +220,11 @@ module Solargraph
|
||
|
||
+ # Returns the per-thread inference stack Set (O(1) include?).
|
||
+ # CWE-407 fix: replaces shared @@inference_stack Array.
|
||
+ def inference_stack
|
||
+ Thread.current[:solargraph_inference_stack] ||= Set.new # CWE-407 fix
|
||
+ end
|
||
+
|
||
def infer_from_definitions pins, context, api_map, locals
|
||
types = []
|
||
unresolved_pins = []
|
||
@@ -227,19 +232,18 @@ module Solargraph
|
||
pins.each do |pin|
|
||
# Avoid infinite recursion
|
||
- next if @@inference_stack.include?(pin)
|
||
+ next if inference_stack.include?(pin) # CWE-407 fix: O(1) set lookup
|
||
|
||
- @@inference_stack.push pin
|
||
+ inference_stack.add(pin) # CWE-407 fix
|
||
type = pin.typify(api_map)
|
||
- @@inference_stack.pop
|
||
+ inference_stack.delete(pin) # CWE-407 fix
|
||
if type.defined?
|
||
|
||
@@ -255,11 +259,11 @@ module Solargraph
|
||
@@inference_depth += 1
|
||
unresolved_pins.each do |pin|
|
||
# Avoid infinite recursion
|
||
- if @@inference_stack.include?(pin.identity)
|
||
+ if inference_stack.include?(pin.identity) # CWE-407 fix: O(1) set lookup
|
||
next
|
||
end
|
||
|
||
- @@inference_stack.push(pin.identity)
|
||
+ inference_stack.add(pin.identity) # CWE-407 fix
|
||
type = pin.probe(api_map)
|
||
- @@inference_stack.pop
|
||
+ inference_stack.delete(pin.identity) # CWE-407 fix
|
||
types.push type if type
|
||
end
|
||
@@inference_depth -= 1
|