96 lines
3.6 KiB
Java
96 lines
3.6 KiB
Java
/**
|
||
* CWE-407 simulation: OBS Studio push_audio_tree da_find O(N²) per audio frame.
|
||
*
|
||
* Simulates the defect in libobs/obs-audio.c where push_audio_tree()
|
||
* and push_audio_tree2() use da_find(audio->render_order, &source, 0)
|
||
* — a linear scan — to check if a source is already in the render order.
|
||
* Called once per source during audio tree traversal, this is O(N²) per
|
||
* audio frame (~47 Hz). With complex scenes (many sources), this causes
|
||
* measurable audio processing overhead.
|
||
*
|
||
* Fix: use a HashSet alongside the render_order list for O(1) membership.
|
||
*/
|
||
import java.util.*;
|
||
|
||
public class ObsStudioTest {
|
||
|
||
/* --- Defective path: linear scan per source (da_find) --- */
|
||
static int buildRenderOrderDefective(Object[] sources) {
|
||
int ops = 0;
|
||
List<Object> renderOrder = new ArrayList<>();
|
||
for (Object source : sources) {
|
||
// da_find: linear scan of render_order
|
||
boolean found = false;
|
||
for (int i = 0; i < renderOrder.size(); i++) {
|
||
ops++;
|
||
if (renderOrder.get(i) == source) {
|
||
found = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!found) {
|
||
renderOrder.add(source);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
/* --- Fixed path: hash set for O(1) membership --- */
|
||
static int buildRenderOrderFixed(Object[] sources) {
|
||
int ops = 0;
|
||
List<Object> renderOrder = new ArrayList<>();
|
||
Set<Object> seen = new HashSet<>();
|
||
for (Object source : sources) {
|
||
ops++; // HashSet.contains is O(1)
|
||
if (seen.add(source)) {
|
||
renderOrder.add(source);
|
||
}
|
||
}
|
||
return ops;
|
||
}
|
||
|
||
public static void main(String[] args) {
|
||
int[] sizes = {100, 500, 1000};
|
||
boolean allPass = true;
|
||
|
||
System.out.println("=== OBS Studio push_audio_tree CWE-407 Test ===");
|
||
System.out.printf("%-8s %-12s %-12s %-8s %-6s%n",
|
||
"N", "Defective", "Fixed", "Ratio", "Pass");
|
||
|
||
for (int N : sizes) {
|
||
// Simulate N unique sources being added to render order
|
||
Object[] sources = new Object[N];
|
||
for (int i = 0; i < N; i++) sources[i] = new Object();
|
||
|
||
int opsDefective = buildRenderOrderDefective(sources);
|
||
int opsFixed = buildRenderOrderFixed(sources);
|
||
|
||
double ratio = (double) opsDefective / opsFixed;
|
||
boolean pass = ratio > 2.0;
|
||
if (!pass) allPass = false;
|
||
|
||
System.out.printf("%-8d %-12d %-12d %-8.1fx %-6s%n",
|
||
N, opsDefective, opsFixed, ratio, pass ? "PASS" : "FAIL");
|
||
}
|
||
|
||
// Simulate repeated frames (the defect runs every audio frame)
|
||
System.out.println("\n--- Per-second overhead at 47 frames/sec ---");
|
||
int N = 200; // realistic complex scene
|
||
Object[] sources = new Object[N];
|
||
for (int i = 0; i < N; i++) sources[i] = new Object();
|
||
int perFrameDefective = buildRenderOrderDefective(sources);
|
||
int perFrameFixed = buildRenderOrderFixed(sources);
|
||
System.out.printf("N=%d: defective=%d ops/frame × 47 = %d ops/sec%n",
|
||
N, perFrameDefective, perFrameDefective * 47);
|
||
System.out.printf("N=%d: fixed=%d ops/frame × 47 = %d ops/sec%n",
|
||
N, perFrameFixed, perFrameFixed * 47);
|
||
|
||
System.out.println();
|
||
if (allPass) {
|
||
System.out.println("ALL PASS — defective path is quadratic, fixed path is linear");
|
||
} else {
|
||
System.out.println("FAIL — ratio not demonstrated");
|
||
System.exit(1);
|
||
}
|
||
}
|
||
}
|