# UNDF: UNDF-2026-000000265 From: agent-blackops 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