java-topology/tools/tickets/defects/solc-0001.md
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

82 lines
2.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: solc-0001
repo: ethereum/solidity
file: libyul/optimiser/CallGraphGenerator.cpp
line: 49
status: unpatched
severity: HIGH
complexity: O(F × D²) — F functions, D max call depth
pattern: std::find on std::vector<FunctionHandle> currentPath in DFS cycle detector
---
## Description
`CallGraphCycleFinder::visit()` maintains `currentPath` as a `std::vector<FunctionHandle>`
representing the current DFS stack. On every node visited, it calls:
```cpp
auto it = find(currentPath.begin(), currentPath.end(), _function); // O(|path|)
```
This is an O(|path|) linear scan to check whether `_function` is already on the current
DFS path (i.e., whether we've found a cycle back to it). For a call graph with F functions
and maximum call depth D, the total cost is O(F × D²).
The developer left a comment at line 36: `// TODO: This algorithm is non-optimal.`
## Context
`CallGraph::recursiveFunctions()` is called during Yul optimizer passes to identify which
functions are recursive (so they can be handled differently during inlining and optimization).
It is invoked on every contract compiled through the Yul IR pipeline, which includes all
contracts compiled with `--via-ir` or `--optimize` on modern solc.
## Fix
Maintain a parallel `std::set<FunctionHandle>` (or `std::unordered_set` if hash is available)
alongside `currentPath`:
```cpp
struct CallGraphCycleFinder {
CallGraph const& callGraph;
std::set<FunctionHandle> containedInCycle{};
std::set<FunctionHandle> visited{};
std::vector<FunctionHandle> currentPath{};
std::set<FunctionHandle> currentPathSet{}; // CWE-407 fix
void visit(FunctionHandle const& _function) {
if (visited.count(_function))
return;
if (currentPathSet.count(_function)) // O(log n) instead of O(|path|)
{
// Cycle confirmed — find position for range insert (rare, O(|path|) ok)
auto it = find(currentPath.begin(), currentPath.end(), _function);
containedInCycle.insert(it, currentPath.end());
}
else {
currentPathSet.insert(_function);
currentPath.emplace_back(_function);
if (callGraph.functionCalls.count(_function))
for (auto const& child: callGraph.functionCalls.at(_function))
visit(child);
currentPath.pop_back();
currentPathSet.erase(_function);
visited.insert(_function);
}
}
};
```
The fallback `find` inside the cycle-detected branch runs only when a cycle is actually
found — rare in practice. The hot path (no cycle) is now O(log D) per node instead of O(D).
## Complexity after fix
O(F × D × log D) instead of O(F × D²).
## Work items
- [ ] patch
- [ ] unit test (operation count: comparisons before and after at F=100, D=50)
- [ ] integration test (contracts with recursive Yul functions)
- [ ] benchmark