package unit; import java.util.Arrays; /** * RaylibGlyphIndexTest — CWE-407 raylib-0001 * * Models GetGlyphIndex(Font font, int codepoint): * slow() = O(n) linear scan of glyphs array (current defect) * fast() = O(log n) binary search on sorted glyphs array (patch) * * Assert: slowOps > fastOps * Nx at N=2048 glyphs. */ public class RaylibGlyphIndexTest { // Simulates font.glyphs[i].value — codepoint stored per glyph slot static int[] buildGlyphs(int n) { int[] glyphs = new int[n]; // Mimic a Unicode font: codepoints spread across BMP // Start at 0x20 (space), stride by 1 — typical Latin+extended range for (int i = 0; i < n; i++) { glyphs[i] = 0x20 + i; } return glyphs; } static long linearOps; static long binaryOps; /** * slow: O(n) linear scan — exact translation of raylib GetGlyphIndex * SUPPORT_UNORDERED_CHARSET branch. */ static int getGlyphIndexSlow(int[] glyphs, int codepoint) { int index = 0; int fallbackIndex = 0; for (int i = 0; i < glyphs.length; i++) { linearOps++; if (glyphs[i] == 63) fallbackIndex = i; // '?' fallback if (glyphs[i] == codepoint) { index = i; break; } } if (index == 0 && glyphs[0] != codepoint) index = fallbackIndex; return index; } /** * fast: O(log n) binary search — patch approach, requires sorted glyphs. */ static int getGlyphIndexFast(int[] glyphs, int codepoint) { int lo = 0, hi = glyphs.length - 1; while (lo <= hi) { binaryOps++; int mid = lo + (hi - lo) / 2; if (glyphs[mid] == codepoint) return mid; else if (glyphs[mid] < codepoint) lo = mid + 1; else hi = mid - 1; } // Fallback to '?' (codepoint 63) — also binary search return getGlyphIndexFast(glyphs, 63); } public static void main(String[] args) { final int N = 2048; // font.glyphCount final int NX = 10; // minimum required speedup factor final int TRIALS = 1000; // number of lookups to accumulate ops int[] glyphs = buildGlyphs(N); // Sorted by construction — binary search is valid // Worst-case codepoint: the last glyph (maximizes linear scan ops) int worstCaseCodepoint = glyphs[N - 1]; linearOps = 0; binaryOps = 0; for (int t = 0; t < TRIALS; t++) { getGlyphIndexSlow(glyphs, worstCaseCodepoint); } long slowOps = linearOps; for (int t = 0; t < TRIALS; t++) { getGlyphIndexFast(glyphs, worstCaseCodepoint); } long fastOps = binaryOps; // Correctness check int slowIdx = getGlyphIndexSlow(glyphs, worstCaseCodepoint); int fastIdx = getGlyphIndexFast(glyphs, worstCaseCodepoint); boolean correctnessOk = (slowIdx == fastIdx); boolean speedupOk = slowOps > fastOps * NX; System.out.printf("N=%d glyphs, worst-case codepoint=0x%X%n", N, worstCaseCodepoint); System.out.printf("slow (linear) ops over %d trials: %d%n", TRIALS, slowOps); System.out.printf("fast (binary) ops over %d trials: %d%n", TRIALS, fastOps); System.out.printf("speedup ratio: %.1fx (required >%dx)%n", (double) slowOps / fastOps, NX); System.out.printf("index match: slow=%d fast=%d%n", slowIdx, fastIdx); int passed = 0, total = 2; if (correctnessOk) { System.out.println("1/2 PASS correctness: slow and fast return same index"); passed++; } else { System.out.printf("1/2 FAIL correctness: slow=%d fast=%d%n", slowIdx, fastIdx); } if (speedupOk) { System.out.printf("2/2 PASS speedup: %d > %d * %d%n", slowOps, fastOps, NX); passed++; } else { System.out.printf("2/2 FAIL speedup: %d not > %d * %d%n", slowOps, fastOps, NX); } System.out.printf("%d/%d PASS%n", passed, total); if (passed < total) System.exit(1); } }