java-topology/tests/workbench/PatchVerifier.java
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

144 lines
6.3 KiB
Java

package workbench;
import java.lang.reflect.Field;
/**
* Checks at runtime whether the javac topology patches are active.
*
* Verifies each patch by reflection — no flags, no trust-me properties.
*
* Patch 0001: GraphUtils$TarjanNode.active boolean field
* (replaces O(V²) stack.contains(n) with O(1) field read)
* Note: merged into OpenJDK 21+ upstream — will show PRESENT on JDK 21+
* even without --patch-module. On older JDKs --patch-module is required.
*
* Patch 0004: Dependencies — List.contains+add → LinkedHashSet.add
* We cannot reliably reflect on the internal node structure without
* --add-opens; detected via the presence of a specific inner class shape.
* Falls back to "cannot verify" if reflection is blocked.
*
* Requires --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
*
* Run standalone (unpatched runtime):
* java --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
* -cp . workbench.PatchVerifier
*
* Run with --patch-module applied:
* java --patch-module jdk.compiler=/tmp/jt-all-classes \
* --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED \
* -cp . workbench.PatchVerifier
*/
public class PatchVerifier {
private static final String TARJAN_NODE = "com.sun.tools.javac.util.GraphUtils$TarjanNode";
public record Check(String id, boolean present, String detail) {
@Override public String toString() {
return (present ? "[OK] " : "[--] ") + id + ": " + detail;
}
}
public record Status(boolean allPresent, Check[] checks) {
public boolean patched() { return allPresent; }
@Override public String toString() {
StringBuilder sb = new StringBuilder();
for (Check c : checks) sb.append(c).append(" | ");
if (sb.length() > 3) sb.setLength(sb.length() - 3);
return sb.toString();
}
public String summary() {
int ok = 0;
for (Check c : checks) if (c.present()) ok++;
return ok + "/" + checks.length + " patches verified";
}
}
public static Status check() {
Check[] checks = {
check0001(),
check0004(),
};
boolean all = true;
for (Check c : checks) if (!c.present()) all = false;
return new Status(all, checks);
}
/** 0001: GraphUtils$TarjanNode.active boolean field. */
private static Check check0001() {
try {
Class<?> cls = Class.forName(TARJAN_NODE);
try {
Field f = cls.getDeclaredField("active");
return new Check("0001/GraphUtils.active",
true,
f.getType().getSimpleName() + " field present — O(V+E) Tarjan (may be upstream JDK)");
} catch (NoSuchFieldException e) {
return new Check("0001/GraphUtils.active",
false,
"field missing — stock O(V²) stack.contains(); apply --patch-module");
}
} catch (ClassNotFoundException e) {
return new Check("0001/GraphUtils.active",
false,
"GraphUtils not accessible — need --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED");
} catch (Exception e) {
return new Check("0001/GraphUtils.active", false, "reflection error: " + e.getMessage());
}
}
/** 0004: Dependencies — LinkedHashSet instead of ArrayList for depsByKind. */
private static Check check0004() {
// Generic type erasure means we can't see LinkedHashSet vs ArrayList via reflection.
// Scan the raw class bytes of the inner Node class for the type descriptor.
// Stock JDK: "java/util/ArrayList" in constant pool
// Patched: "java/util/LinkedHashSet" in constant pool (ArrayList removed)
// Node is nested: Dependencies$GraphDependencies$Node
try {
Class<?> cls = Class.forName(
"com.sun.tools.javac.util.Dependencies$GraphDependencies$Node");
String resourcePath = cls.getName().replace('.', '/') + ".class";
byte[] bytes;
// Named module: use Module.getResourceAsStream (not ClassLoader)
try (java.io.InputStream in = cls.getModule().getResourceAsStream(resourcePath)) {
bytes = in == null ? null : in.readAllBytes();
}
if (bytes == null) {
return new Check("0004/Dependencies.LinkedHashSet",
false, "class bytes not readable from class loader");
}
String pool = new String(bytes, java.nio.charset.StandardCharsets.ISO_8859_1);
boolean hasLinkedHashSet = pool.contains("java/util/LinkedHashSet");
boolean hasArrayList = pool.contains("java/util/ArrayList");
if (hasLinkedHashSet && !hasArrayList) {
return new Check("0004/Dependencies.LinkedHashSet",
true, "constant pool has LinkedHashSet, no ArrayList — O(1) add");
} else if (hasArrayList) {
return new Check("0004/Dependencies.LinkedHashSet",
false, "constant pool has ArrayList — stock O(N) List.contains+add");
} else {
return new Check("0004/Dependencies.LinkedHashSet",
false, "neither ArrayList nor LinkedHashSet in constant pool (inconclusive)");
}
} catch (ClassNotFoundException e) {
return new Check("0004/Dependencies.LinkedHashSet",
false, "Dependencies not accessible — need --add-exports");
} catch (Exception e) {
return new Check("0004/Dependencies.LinkedHashSet",
false, "byte scan failed: " + e.getMessage());
}
}
/** Standalone: prints status, exits 0 if all checks pass. */
public static void main(String[] args) {
System.out.println("java.home: " + System.getProperty("java.home"));
System.out.println("java.version: " + System.getProperty("java.version"));
System.out.println();
Status s = check();
for (Check c : s.checks()) System.out.println(" " + c);
System.out.println();
System.out.println(" => " + s.summary());
System.exit(s.allPresent() ? 0 : 1);
}
}