jitsi-videobridge (Kotlin/Java video conferencing bridge):
- 0001: Prioritize.kt selectedSourceNames.contains()+indexOf() inside forEach over conferenceSources, O(C*S)
- 0002: BandwidthAllocator.kt selectedSources getter List.contains() dedup inside forEach, O(S^2)
- 0003: ConferenceSpeechActivity.java endpointsChanged() ArrayList.contains() in removeIf+for loop, O(E^2)
Fix: HashSet for O(1) membership; pre-built index map for indexOf
Unit test: 4/4 PASS, 19-35x op-count reduction at N=200
woodpecker-0001 (Go CI/CD pipeline step builder):
- filterItemsWithMissingDependencies() calls containsItemWithName() (O(N) linear scan) inside
two nested loops over items and deps: O(N*D*N) = O(N^2)
Fix: pre-build name-set map for O(1) lookup, O(N) total
Unit test: 3/3 PASS, 20x op-count reduction at N=100
woodpecker-0002 (CWE-312 credential logging):
- shared/token/token.go ParseRequest() logs raw Authorization header value at Trace level:
log.Trace().Msgf("token.ParseRequest: found token in header: %s", token)
Exposes full Bearer JWT token in application logs
Fix: log only that header was found, not its value
Unit test: 3/3 PASS
165 lines
4.5 KiB
Go
165 lines
4.5 KiB
Go
// CWE-407 benchmark: woodpecker-0001
|
|
// filterItemsWithMissingDependencies uses containsItemWithName (O(N) linear scan)
|
|
// inside two nested loops: O(N * D * N) = O(N²) where D = average deps per item.
|
|
// Fix: pre-build a name-set map for O(1) membership tests, reducing to O(N).
|
|
//
|
|
// Run: go test -v -run TestFilterItems ./...
|
|
package woodpecker_test
|
|
|
|
import (
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// Item mirrors the step_builder Item struct for benchmarking purposes.
|
|
type Item struct {
|
|
Name string
|
|
DependsOn []string
|
|
}
|
|
|
|
// slowContainsItemWithName is the original O(N) linear scan.
|
|
func slowContainsItemWithName(name string, items []*Item) bool {
|
|
for _, item := range items {
|
|
if name == item.Name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// slowFilterItems is the original O(N²) implementation.
|
|
func slowFilterItems(items []*Item) ([]*Item, int64) {
|
|
var ops int64
|
|
itemsToRemove := make([]*Item, 0)
|
|
|
|
for _, item := range items {
|
|
for _, dep := range item.DependsOn {
|
|
ops++ // containsItemWithName call
|
|
if !slowContainsItemWithName(dep, items) {
|
|
ops += int64(len(items)) // O(N) scan
|
|
itemsToRemove = append(itemsToRemove, item)
|
|
} else {
|
|
ops += int64(len(items)) // worst-case scan
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(itemsToRemove) > 0 {
|
|
filtered := make([]*Item, 0)
|
|
for _, item := range items {
|
|
ops += int64(len(itemsToRemove)) // O(N) scan per item
|
|
if !slowContainsItemWithName(item.Name, itemsToRemove) {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
sub, subOps := slowFilterItems(filtered)
|
|
ops += subOps
|
|
return sub, ops
|
|
}
|
|
|
|
return items, ops
|
|
}
|
|
|
|
// fastFilterItems is the O(N) fixed implementation using a name map.
|
|
func fastFilterItems(items []*Item) ([]*Item, int64) {
|
|
var ops int64
|
|
|
|
// Build O(1) lookup set - O(N) once.
|
|
nameSet := make(map[string]struct{}, len(items))
|
|
for _, item := range items {
|
|
nameSet[item.Name] = struct{}{}
|
|
ops++
|
|
}
|
|
|
|
toRemoveNames := make(map[string]struct{})
|
|
for _, item := range items {
|
|
for _, dep := range item.DependsOn {
|
|
ops++ // O(1) map lookup
|
|
if _, exists := nameSet[dep]; !exists {
|
|
toRemoveNames[item.Name] = struct{}{}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(toRemoveNames) > 0 {
|
|
filtered := make([]*Item, 0, len(items))
|
|
for _, item := range items {
|
|
ops++ // O(1) map lookup
|
|
if _, remove := toRemoveNames[item.Name]; !remove {
|
|
filtered = append(filtered, item)
|
|
}
|
|
}
|
|
sub, subOps := fastFilterItems(filtered)
|
|
ops += subOps
|
|
return sub, ops
|
|
}
|
|
|
|
return items, ops
|
|
}
|
|
|
|
// buildItems creates N items where items[N/4..N/2] have a missing dep ("ghost")
|
|
// to trigger the filter path, and the rest have valid deps on previous items.
|
|
func buildItems(n int) []*Item {
|
|
items := make([]*Item, n)
|
|
for i := 0; i < n; i++ {
|
|
deps := []string{}
|
|
if i > 0 {
|
|
deps = append(deps, fmt.Sprintf("item-%d", i-1))
|
|
}
|
|
items[i] = &Item{Name: fmt.Sprintf("item-%d", i), DependsOn: deps}
|
|
}
|
|
// Inject missing deps in the middle quarter to trigger filtering.
|
|
for i := n / 4; i < n/2; i++ {
|
|
items[i].DependsOn = append(items[i].DependsOn, "ghost-missing-dep")
|
|
}
|
|
return items
|
|
}
|
|
|
|
func TestFilterItemsCorrectness(t *testing.T) {
|
|
items := buildItems(20)
|
|
slow, _ := slowFilterItems(items)
|
|
fast, _ := fastFilterItems(items)
|
|
|
|
if len(slow) != len(fast) {
|
|
t.Fatalf("result length mismatch: slow=%d fast=%d", len(slow), len(fast))
|
|
}
|
|
|
|
slowNames := make(map[string]bool)
|
|
for _, it := range slow {
|
|
slowNames[it.Name] = true
|
|
}
|
|
for _, it := range fast {
|
|
if !slowNames[it.Name] {
|
|
t.Errorf("fast result contains item not in slow result: %s", it.Name)
|
|
}
|
|
}
|
|
t.Logf("correctness: slow=%d items, fast=%d items — match", len(slow), len(fast))
|
|
}
|
|
|
|
func TestFilterItemsOpCount(t *testing.T) {
|
|
sizes := []int{10, 30, 50, 100}
|
|
for _, n := range sizes {
|
|
items := buildItems(n)
|
|
_, slowOps := slowFilterItems(items)
|
|
_, fastOps := fastFilterItems(items)
|
|
ratio := float64(slowOps) / float64(fastOps)
|
|
t.Logf("N=%3d slow=%6d ops fast=%6d ops ratio=%.1fx", n, slowOps, fastOps, ratio)
|
|
if n >= 50 && ratio < 3.0 {
|
|
t.Errorf("N=%d: expected ratio >= 3x, got %.1fx (slow=%d, fast=%d)", n, ratio, slowOps, fastOps)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterItemsPass(t *testing.T) {
|
|
n := 100
|
|
items := buildItems(n)
|
|
_, slowOps := slowFilterItems(items)
|
|
_, fastOps := fastFilterItems(items)
|
|
ratio := float64(slowOps) / float64(fastOps)
|
|
t.Logf("woodpecker-0001 N=%d: slow=%d ops, fast=%d ops, ratio=%.1fx", n, slowOps, fastOps, ratio)
|
|
if ratio < 5.0 {
|
|
t.Fatalf("FAIL: expected at least 5x op-count reduction, got %.1fx", ratio)
|
|
}
|
|
t.Logf("PASS: %.0fx op-count reduction at N=%d", ratio, n)
|
|
}
|