whitepaper: 352/169 — wave4 MEDIUM (hadoop/hbase/nova/neutron/openstack) + fix odl-0002 dup
This commit is contained in:
parent
9934133dcf
commit
835ae73b0f
82 changed files with 5931 additions and 6 deletions
251
defects/libgit2/unit/RefPathAvailableAlgorithm.java
Normal file
251
defects/libgit2/unit/RefPathAvailableAlgorithm.java
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
package unit;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Models libgit2's reference_path_available() — given a sorted list of packed
|
||||
* ref names, determine whether a candidate new ref name would collide with any
|
||||
* existing entry (i.e., the new ref name is a strict prefix component of some
|
||||
* existing ref: "refs/foo" conflicts with "refs/foo/bar").
|
||||
*
|
||||
* SLOW: O(R) linear scan of every packed ref.
|
||||
* FAST: O(log R) binary search to the first entry >= candidate + "/".
|
||||
*
|
||||
* CWE-407: libgit2 src/libgit2/refdb_fs.c:1185
|
||||
*/
|
||||
public class RefPathAvailableAlgorithm {
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Slow (defective) implementation — mirrors the current libgit2 C code.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class SlowChecker {
|
||||
final List<String> packedRefs; // sorted
|
||||
|
||||
SlowChecker(List<String> packedRefs) {
|
||||
this.packedRefs = packedRefs;
|
||||
}
|
||||
|
||||
/** Returns true if newRef would collide as a directory component. */
|
||||
boolean collides(String newRef) {
|
||||
int ops = 0;
|
||||
for (String existingRef : packedRefs) {
|
||||
ops++;
|
||||
int refLen = existingRef.length();
|
||||
int newLen = newRef.length();
|
||||
int cmpLen = Math.min(refLen, newLen);
|
||||
String lead = (refLen < newLen) ? newRef : existingRef;
|
||||
if (existingRef.regionMatches(0, newRef, 0, cmpLen)
|
||||
&& lead.charAt(cmpLen) == '/') {
|
||||
lastOps = ops;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
lastOps = ops;
|
||||
return false;
|
||||
}
|
||||
|
||||
int lastOps;
|
||||
int totalOps(int runs) { return lastOps; } // per single call
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Fast (fixed) implementation — O(log R) binary search.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static class FastChecker {
|
||||
final List<String> packedRefs; // sorted
|
||||
|
||||
FastChecker(List<String> packedRefs) {
|
||||
this.packedRefs = packedRefs;
|
||||
}
|
||||
|
||||
/** Returns true if newRef would collide as a directory component. */
|
||||
boolean collides(String newRef) {
|
||||
String prefix = newRef + "/";
|
||||
// Binary search for the first entry >= prefix
|
||||
int pos = Collections.binarySearch(packedRefs, prefix);
|
||||
if (pos < 0) pos = -(pos + 1); // insertion point
|
||||
ops = 1; // O(log R) — count as single logical search step
|
||||
if (pos >= packedRefs.size()) return false;
|
||||
return packedRefs.get(pos).startsWith(prefix);
|
||||
}
|
||||
|
||||
int ops;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static List<String> buildPackedRefs(int count) {
|
||||
List<String> refs = new ArrayList<>(count);
|
||||
for (int i = 0; i < count; i++) {
|
||||
refs.add(String.format("refs/remotes/origin/branch-%07d", i));
|
||||
}
|
||||
Collections.sort(refs);
|
||||
return refs;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Tests
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
static int passed = 0;
|
||||
static int total = 0;
|
||||
|
||||
static void check(String label, boolean condition) {
|
||||
total++;
|
||||
if (condition) {
|
||||
passed++;
|
||||
System.out.println(" PASS " + label);
|
||||
} else {
|
||||
System.out.println(" FAIL " + label);
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== RefPathAvailableAlgorithm ===");
|
||||
|
||||
// --- Correctness: no collision ---
|
||||
{
|
||||
List<String> refs = new ArrayList<>();
|
||||
refs.add("refs/heads/main");
|
||||
refs.add("refs/heads/next");
|
||||
refs.add("refs/tags/v1.0");
|
||||
Collections.sort(refs);
|
||||
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
String candidate = "refs/heads/feature";
|
||||
boolean slowResult = slow.collides(candidate);
|
||||
boolean fastResult = fast.collides(candidate);
|
||||
|
||||
check("no-collision slow returns false", !slowResult);
|
||||
check("no-collision fast returns false", !fastResult);
|
||||
check("no-collision results agree", slowResult == fastResult);
|
||||
}
|
||||
|
||||
// --- Correctness: collision (new ref is prefix of existing) ---
|
||||
{
|
||||
List<String> refs = new ArrayList<>();
|
||||
refs.add("refs/heads/foo/bar");
|
||||
refs.add("refs/heads/foo/baz");
|
||||
refs.add("refs/heads/zzz");
|
||||
Collections.sort(refs);
|
||||
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
// "refs/heads/foo" would be a directory component of existing refs
|
||||
String candidate = "refs/heads/foo";
|
||||
boolean slowResult = slow.collides(candidate);
|
||||
boolean fastResult = fast.collides(candidate);
|
||||
|
||||
check("collision slow returns true", slowResult);
|
||||
check("collision fast returns true", fastResult);
|
||||
check("collision results agree", slowResult == fastResult);
|
||||
}
|
||||
|
||||
// --- Correctness: near-miss (prefix but no slash) ---
|
||||
{
|
||||
List<String> refs = new ArrayList<>();
|
||||
refs.add("refs/heads/foobar");
|
||||
Collections.sort(refs);
|
||||
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
// "refs/heads/foo" is a STRING prefix of "refs/heads/foobar"
|
||||
// but NOT a directory prefix (no slash after "foo")
|
||||
String candidate = "refs/heads/foo";
|
||||
boolean slowResult = slow.collides(candidate);
|
||||
boolean fastResult = fast.collides(candidate);
|
||||
|
||||
check("near-miss slow returns false", !slowResult);
|
||||
check("near-miss fast returns false", !fastResult);
|
||||
check("near-miss results agree", slowResult == fastResult);
|
||||
}
|
||||
|
||||
// --- Correctness: empty cache ---
|
||||
{
|
||||
List<String> refs = new ArrayList<>();
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
boolean slowResult = slow.collides("refs/heads/anything");
|
||||
boolean fastResult = fast.collides("refs/heads/anything");
|
||||
|
||||
check("empty-cache slow returns false", !slowResult);
|
||||
check("empty-cache fast returns false", !fastResult);
|
||||
}
|
||||
|
||||
// --- Correctness: candidate is before all entries ---
|
||||
{
|
||||
List<String> refs = new ArrayList<>();
|
||||
refs.add("refs/heads/zzz/child");
|
||||
Collections.sort(refs);
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
boolean slowResult = slow.collides("refs/heads/aaa");
|
||||
boolean fastResult = fast.collides("refs/heads/aaa");
|
||||
|
||||
check("before-all slow returns false", !slowResult);
|
||||
check("before-all fast returns false", !fastResult);
|
||||
check("before-all results agree", slowResult == fastResult);
|
||||
}
|
||||
|
||||
// --- Performance: O(n²) vs O(n log n) ---
|
||||
{
|
||||
int R = 100_000;
|
||||
List<String> refs = buildPackedRefs(R);
|
||||
|
||||
// Candidate that collides with last entry to force full scan in slow:
|
||||
// We pick a ref name that IS a prefix of many entries.
|
||||
// Add a colliding ref:
|
||||
refs.add("refs/remotes/origin/branch-0000000");
|
||||
// new_ref that would collide: "refs/remotes/origin" collides if
|
||||
// "refs/remotes/origin/..." exist — but "refs/remotes/origin" itself
|
||||
// is not in the list. Let's test a true no-collision near the end
|
||||
// to force full scan.
|
||||
Collections.sort(refs);
|
||||
|
||||
// For slow, worst case: no collision but must scan all R entries.
|
||||
String noCollisionCandidate = "refs/zzz/new";
|
||||
|
||||
SlowChecker slow = new SlowChecker(refs);
|
||||
FastChecker fast = new FastChecker(refs);
|
||||
|
||||
long t0 = System.nanoTime();
|
||||
int slowRuns = 1000;
|
||||
for (int i = 0; i < slowRuns; i++) slow.collides(noCollisionCandidate);
|
||||
long slowNs = System.nanoTime() - t0;
|
||||
|
||||
long t1 = System.nanoTime();
|
||||
int fastRuns = 1000;
|
||||
for (int i = 0; i < fastRuns; i++) fast.collides(noCollisionCandidate);
|
||||
long fastNs = System.nanoTime() - t1;
|
||||
|
||||
// Slow must scan all R entries per call. Fast does O(log R).
|
||||
// We verify slow.lastOps = R, fast.ops = 1 (symbolic).
|
||||
boolean slowScansAll = (slow.lastOps == refs.size());
|
||||
boolean fastIsLogN = (fast.ops == 1);
|
||||
double ratio = (double) slowNs / fastNs;
|
||||
|
||||
System.out.printf(" INFO slow=%d ops/call fast=O(logN) ratio=%.1fx%n",
|
||||
slow.lastOps, ratio);
|
||||
|
||||
check("slow scans all R entries", slowScansAll);
|
||||
check("fast uses binary search", fastIsLogN);
|
||||
check("fast is meaningfully faster (>= 5x)", ratio >= 5.0);
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.printf("%d/%d PASS%n", passed, total);
|
||||
if (passed != total) System.exit(1);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue