java-topology/defects/tinkerpop/patch/tinkerpop-0001-mutablepath-issimple-hashset.md

81 lines
2.4 KiB
Markdown
Raw 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.

# UNDF: UNDF-2026-000000314
# UNDF: (pending)
# tinkerpop-0001: MutablePath.isSimple — missing O(P) override, falls back to O(P²) default
## CWE-407 — Algorithmic Complexity
| Field | Value |
|-------|-------|
| ID | tinkerpop-0001 |
| Severity | MEDIUM |
| Ecosystem | tinkerpop |
| Package | gremlin-core |
| File | `gremlin-core/src/main/java/org/apache/tinkerpop/gremlin/process/traversal/step/util/MutablePath.java` |
| Lines | 36165 (missing override) |
| Complexity | O(P²) |
| Hot path | Called per traverser in `PathFilterStep.filter()` when `.simplePath().by(...)` or `.cyclicPath().by(...)` is used |
## Defect
`MutablePath` does not override `isSimple()`. The default implementation in `Path.java` uses a
nested double-loop over `objects()`:
```java
// Path.java default — O(P²)
public default boolean isSimple() {
final List<Object> objects = this.objects();
for (int i = 0; i < objects.size() - 1; i++) {
for (int j = i + 1; j < objects.size(); j++) {
if (Objects.equals(objects.get(i), objects.get(j)))
return false;
}
}
return true;
}
```
`ImmutablePath` already has the correct O(P) override using a `HashSet`:
```java
// ImmutablePath — O(P) ✓
public boolean isSimple() {
final Set<Object> objects = new HashSet<>();
ImmutablePath currentPath = this;
while (true) {
if (currentPath.isTail()) return true;
else if (objects.contains(currentPath.currentObject)) return false;
else { objects.add(currentPath.currentObject); currentPath = currentPath.previousPath; }
}
}
```
`MutablePath` is used in `PathFilterStep.filter()` (line 65) and `PathStep` (line 119) when a
`by()` modulator is present. `byPath.isSimple()` then calls the O(P²) default, giving quadratic
behavior for traversals like `g.V().simplePath().by(...)` with long paths.
## Fix
Add the O(P) override to `MutablePath`:
```java
// MutablePath.java — add this override
@Override
public boolean isSimple() {
final Set<Object> seenObjects = new HashSet<>();
for (final Object object : this.objects) {
if (!seenObjects.add(object)) {
return false;
}
}
return true;
}
```
## Speedup
| P (path length) | Before (comparisons) | After (comparisons) | Speedup |
|-----------------|---------------------|---------------------|---------|
| 10 | 45 | 10 | 4.5× |
| 50 | 1,225 | 50 | 24.5× |
| 100 | 4,950 | 100 | 49.5× |
| 500 | 124,750 | 500 | 249.5× |