blockchain-p2p: solang/tor/bitcoin/transmission/libtorrent/solc CWE-407 scan
solang-0001: add_external_functions emits_events Vec::contains O(F×E²) MEDIUM src/sema/external_functions.rs:93-103 — dedup accumulator Vec uses linear scan for each event per function; fix: IndexSet (already a dependency) for O(1) dedup tor/bitcoin/transmission/libtorrent/solc: CLEAN markers added after full scan Tor: nodes_have_common_family_id F=1-3 IDs, O(N×F²) ≈ O(9N), not scalable issue Bitcoin: TxGraph/sets throughout, no linear scan in hot paths Transmission: bitfields for piece tracking, sorted binary search for string table libtorrent: sorted vectors with lower_bound, DHT uses binary search on results solc: unordered_set/set throughout OverrideChecker, SMTEncoder, FunctionCallGraph
This commit is contained in:
parent
a17b91d5c4
commit
a7b08c7e05
6 changed files with 195 additions and 0 deletions
99
defects/solang/patch/solang-0001-emits-events-vec-hashset.md
Normal file
99
defects/solang/patch/solang-0001-emits-events-vec-hashset.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
# UNDF: (pending)
|
||||
# solang-0001: add_external_functions emits_events Vec::contains O(F×E²) → O(F×E)
|
||||
|
||||
## CWE-407 — Algorithmic Complexity
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| ID | solang-0001 |
|
||||
| Severity | MEDIUM |
|
||||
| Ecosystem | Solidity compiler (Solang) |
|
||||
| Package | sema |
|
||||
| File | `src/sema/external_functions.rs` |
|
||||
| Lines | 93–103 |
|
||||
| Complexity | O(F×E²) → O(F×E) with HashSet |
|
||||
| Hot path | Per contract compilation — called once per contract in `add_external_functions` |
|
||||
|
||||
## Defect
|
||||
|
||||
```rust
|
||||
// BEFORE (DEFECT) — Vec::contains is O(E) scan, called E times per function × F functions
|
||||
let mut emits_events = Vec::new();
|
||||
|
||||
for function_no in ns.contracts[contract_no].all_functions.keys() {
|
||||
let func = &ns.functions[*function_no];
|
||||
|
||||
for event_no in &func.emits_events {
|
||||
if !emits_events.contains(event_no) { // O(E) linear scan per event
|
||||
emits_events.push(*event_no);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The accumulator `emits_events` is a `Vec<usize>`. For each of F functions, for each of E
|
||||
events the function emits, `Vec::contains` performs a linear scan of the accumulator
|
||||
(already up to E entries). Total cost: O(F × E²).
|
||||
|
||||
For a contract with F=50 functions and each emitting E=20 events, the inner dedup
|
||||
scans up to 1000 elements per event check — 50 × 20 × 20 = 20,000 comparisons instead
|
||||
of 1,000.
|
||||
|
||||
## Fix
|
||||
|
||||
```rust
|
||||
// AFTER — IndexSet (insertion-ordered HashSet) dedup in O(1), preserving order
|
||||
use indexmap::IndexSet;
|
||||
|
||||
let mut emits_events_set: IndexSet<usize> = IndexSet::new();
|
||||
|
||||
for function_no in ns.contracts[contract_no].all_functions.keys() {
|
||||
let func = &ns.functions[*function_no];
|
||||
|
||||
for event_no in &func.emits_events {
|
||||
emits_events_set.insert(*event_no); // O(1) amortized
|
||||
}
|
||||
}
|
||||
|
||||
ns.contracts[contract_no].emits_events = emits_events_set.into_iter().collect();
|
||||
```
|
||||
|
||||
`IndexSet` is already used in this file (`CallList.solidity` on line 10), so no new
|
||||
dependency is required.
|
||||
|
||||
Alternatively, a plain `HashSet` followed by a `Vec` collect works if order does not
|
||||
matter (the original Vec ordering is not documented as significant):
|
||||
|
||||
```rust
|
||||
use std::collections::HashSet;
|
||||
|
||||
let mut seen: HashSet<usize> = HashSet::new();
|
||||
let mut emits_events = Vec::new();
|
||||
|
||||
for function_no in ns.contracts[contract_no].all_functions.keys() {
|
||||
let func = &ns.functions[*function_no];
|
||||
for event_no in &func.emits_events {
|
||||
if seen.insert(*event_no) {
|
||||
emits_events.push(*event_no);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Speedup
|
||||
|
||||
| F (functions) | E (events/fn) | Before (ops) | After (ops) | Speedup |
|
||||
|---------------|---------------|-------------|-------------|---------|
|
||||
| 10 | 5 | 250 | 50 | 5× |
|
||||
| 50 | 20 | 20,000 | 1,000 | 20× |
|
||||
| 200 | 50 | 500,000 | 10,000 | 50× |
|
||||
| 500 | 100 | 5,000,000 | 50,000 | 100× |
|
||||
|
||||
A contract with 500 functions (possible in generated Solidity or large proxy contracts)
|
||||
and 100 event types accumulates 5 million membership comparisons vs 50,000 with the fix.
|
||||
|
||||
## Notes
|
||||
|
||||
`IndexSet` is already a dependency of the `solang` crate (imported on line 5 of this
|
||||
file as `use indexmap::IndexSet`), making this a zero-dependency fix. The same
|
||||
`IndexSet` pattern is already used on line 10 for `CallList.solidity`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue