diff --git a/.github/workflows/aot-test.yml b/.github/workflows/aot-test.yml index b43e6b3..f5ec8dd 100644 --- a/.github/workflows/aot-test.yml +++ b/.github/workflows/aot-test.yml @@ -41,8 +41,9 @@ jobs: target/graalvm-native-image/jelly-cli version | grep "JVM reflection: supported" # Test RDF conversions - echo '_:b _:b .' | target/graalvm-native-image/jelly-cli \ - rdf to-jelly --in-format=nt > out.jelly && \ + echo '_:b _:b .' > in.nt + target/graalvm-native-image/jelly-cli \ + rdf to-jelly --in-format=nt in.nt > out.jelly && \ [ -s out.jelly ] target/graalvm-native-image/jelly-cli \ rdf from-jelly --out-format=jelly-text out.jelly > out.txt && \ @@ -56,6 +57,10 @@ jobs: echo '' | \ target/graalvm-native-image/jelly-cli rdf to-jelly --in-format "rdfxml" > rdfxml.jelly && \ [ -s rdfxml.jelly ] + + # Test rdf validate + target/graalvm-native-image/jelly-cli \ + rdf validate out.jelly --compare-to-rdf-file in.nt - name: Upload binary uses: actions/upload-artifact@v4 diff --git a/build.sbt b/build.sbt index f2ef2ca..1fed135 100644 --- a/build.sbt +++ b/build.sbt @@ -34,6 +34,8 @@ lazy val graalOptions = Seq( "--initialize-at-build-time=org.glassfish.json.UnicodeDetectingInputStream", "-H:+TrackPrimitiveValues", // SkipFlow optimization -- will be default in GraalVM 25 "-H:+UsePredicates", // SkipFlow optimization -- will be default in GraalVM 25 + // Make sure we don't include stuff that should be unreachable in the native image + "-H:AbortOnMethodReachable=*UUID.randomUUID*", ) lazy val TestSerial = config("test-serial") extend Test diff --git a/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java b/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java index 4b68ba8..130ef12 100644 --- a/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java +++ b/src/main/java/eu/neverblink/jelly/cli/graal/GraalSubstitutes.java @@ -9,8 +9,8 @@ import java.net.URI; import java.nio.charset.Charset; -import java.security.Provider; -import java.util.List; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; // Substitutions of classes and methods for GraalVM native image builds. // These try to remove things from the static analysis (and, in effect, from the final binary) @@ -53,14 +53,33 @@ final class HttpLibSubstitute { } final class HttpEnvSubstitute { } /** - * UUID generation is used by Jena for blank node IDs, but we can fall back to the default implementation. + * Use pseudo-random UUIDs instead of secure random ones for seeding the blank node allocator. + * The secure random number generation pulls in a lot of stuff we don't need. + *

+ * For conversion commands, this is not used at all, because we preserve blank node IDs. */ -@Substitute -@TargetClass(className = "sun.security.jca.ProviderList") -final class ProviderListSubstitute { +@TargetClass(org.apache.jena.riot.lang.BlankNodeAllocatorHash.class) +final class BlankNodeAllocatorHashSubstitute { + @Substitute + UUID freshSeed() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + return new UUID(r.nextLong(), r.nextLong()); + } +} + +/** + * Jena uses secure random number generation to create blank node IDs in its Model API. + *

+ * Replaced with pseudo-random UUIDs to avoid including secure random number generation in the native image. + * This should be good enough for our purposes, because this is only used in init code for vocabularies + * and not for user data. + */ +@TargetClass(org.apache.jena.graph.BlankNodeId.class) +final class BlankNodeIdSubstitute { @Substitute - public List providers() { - return List.of(); + public static String createFreshId() { + ThreadLocalRandom r = ThreadLocalRandom.current(); + return new UUID(r.nextLong(), r.nextLong()).toString(); } }