wave16: dart-0001/2/3 + octave-0002 + php-0003/4 + cassandra-0005 + erlang-0003 + chef-0001 — 542/240
This commit is contained in:
parent
31850d5ef6
commit
d816d3c74c
18 changed files with 1902 additions and 6 deletions
119
defects/octave/patch/octave-0002-loadpath-dir-hashset.md
Normal file
119
defects/octave/patch/octave-0002-loadpath-dir-hashset.md
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# octave-0002: load_path::add — O(D²) directory-presence scan on path initialization
|
||||
|
||||
## Summary
|
||||
|
||||
| Field | Value |
|
||||
|-------------|-------|
|
||||
| ID | octave-0002 |
|
||||
| Severity | MEDIUM |
|
||||
| CWE | CWE-407 (Algorithmic Complexity) |
|
||||
| File | `libinterp/corefcn/load-path.cc` |
|
||||
| Functions | `load_path::find_dir_info`, `load_path::add` |
|
||||
| Lines | 1021–1055, 1105–1155 |
|
||||
| Speedup | ~500x op-count at D=1000 dirs |
|
||||
|
||||
## Defect
|
||||
|
||||
Every call to `load_path::add(dir, ...)` calls `find_dir_info(dir)` (lines 1119, 1151),
|
||||
which is an O(D) linear scan over the `m_dir_info_list`:
|
||||
|
||||
```cpp
|
||||
// load-path.cc:1029
|
||||
while (retval != m_dir_info_list.cend ())
|
||||
{
|
||||
if (retval->m_dir_name == dir) // O(D) string compare per call
|
||||
break;
|
||||
retval++;
|
||||
}
|
||||
```
|
||||
|
||||
`load_path::set()` (called at startup from `initialize()`) loops over all N
|
||||
path elements and calls `append()` → `add()` for each one:
|
||||
|
||||
```cpp
|
||||
// load-path.cc:346
|
||||
for (const auto& elt : elts)
|
||||
append (elt, warn); // each append → add → find_dir_info (O(D))
|
||||
```
|
||||
|
||||
Since `m_dir_info_list` grows by 1 per iteration, the total cost is:
|
||||
`1 + 2 + ... + N = O(N²)`.
|
||||
|
||||
Additionally, each `add()` call issues a **second** `find_dir_info(".")` call at
|
||||
line 1151 to keep "." at the front — doubling the quadratic work.
|
||||
|
||||
A standard Octave installation ships with ~200+ path directories via
|
||||
`OCTAVE_PATH` and `restoredefaultpath`. Toolbox-heavy environments (Octave Forge,
|
||||
biomedical packages) can exceed 500+ directories.
|
||||
|
||||
## Root Cause
|
||||
|
||||
`m_dir_info_list` is a `std::list<dir_info>` with no parallel index.
|
||||
Membership is checked by linear scan every time a directory is added.
|
||||
This is O(N²) overall for N-directory path initialization.
|
||||
|
||||
## Fix
|
||||
|
||||
Maintain a `std::unordered_set<std::string> m_dir_name_set` in `load_path`
|
||||
alongside `m_dir_info_list`. Replace the `find_dir_info` loop with O(1)
|
||||
set membership, and maintain the set in sync with the list on add/remove.
|
||||
|
||||
```diff
|
||||
--- a/libinterp/corefcn/load-path.h
|
||||
+++ b/libinterp/corefcn/load-path.h
|
||||
@@ -... @@
|
||||
+ std::unordered_set<std::string> m_dir_name_set;
|
||||
|
||||
--- a/libinterp/corefcn/load-path.cc
|
||||
+++ b/libinterp/corefcn/load-path.cc
|
||||
@@ -1021,10 +1021,12 @@
|
||||
load_path::find_dir_info (const std::string& dir_arg) const
|
||||
{
|
||||
std::string dir = ...;
|
||||
- auto retval = m_dir_info_list.cbegin ();
|
||||
- while (retval != m_dir_info_list.cend ())
|
||||
- {
|
||||
- if (retval->m_dir_name == dir)
|
||||
- break;
|
||||
- retval++;
|
||||
- }
|
||||
- return retval;
|
||||
+ // O(1) fast path: check set membership before walking the list
|
||||
+ if (m_dir_name_set.find (dir) == m_dir_name_set.end ())
|
||||
+ return m_dir_info_list.cend ();
|
||||
+ // Only walk list to get iterator when dir is actually present (rare)
|
||||
+ auto retval = m_dir_info_list.cbegin ();
|
||||
+ while (retval != m_dir_info_list.cend ())
|
||||
+ {
|
||||
+ if (retval->m_dir_name == dir) break;
|
||||
+ retval++;
|
||||
+ }
|
||||
+ return retval;
|
||||
}
|
||||
@@ -1119 +1121 @@
|
||||
+ m_dir_name_set.insert (di.m_dir_name);
|
||||
if (at_end)
|
||||
m_dir_info_list.push_back (di);
|
||||
@@ -remove @@
|
||||
+ m_dir_name_set.erase (di.m_dir_name);
|
||||
m_dir_info_list.erase (...);
|
||||
```
|
||||
|
||||
The fix eliminates the O(D) scan for the common case (dir not yet present).
|
||||
Total path initialization cost drops from O(D²) to O(D).
|
||||
|
||||
## Measurement
|
||||
|
||||
Simulated with `OctaveLoadPathAlgorithm.java`:
|
||||
|
||||
| D | slow ops | fast ops | ratio |
|
||||
|------|----------|----------|-------|
|
||||
| 100 | 10,200 | 200 | 51× |
|
||||
| 500 | 251,000 | 1,000 | 251× |
|
||||
| 1000 | 1,002,000| 2,000 | 501× |
|
||||
|
||||
## References
|
||||
|
||||
- `libinterp/corefcn/load-path.cc` lines 1021–1055 (`find_dir_info`)
|
||||
- `libinterp/corefcn/load-path.cc` lines 1105–1155 (`add`)
|
||||
- `libinterp/corefcn/load-path.cc` lines 346–347 (`set` loop)
|
||||
Loading…
Add table
Add a link
Reference in a new issue