graalvm

roklenarcic 2024-06-06T09:31:55.197589Z

Using graalvm-ce-java11-22.3.3 I am trying to include BouncyCastle into my generated binary. I have been looking high and low for a how-to. In the end I copied stuff from pod-babashka-buddy : I added a Feature class:

public class BCFeature implements Feature {

    @Override
    public void afterRegistration(AfterRegistrationAccess access) {
        Security.addProvider(new BouncyCastleProvider());
    }

}
and I added these switches to native-image run:
--report-unsupported-elements-at-runtime
--features=clj_easy.graal_build_time.InitClojureClasses,devtools.BCFeature
--rerun-class-initialization-at-runtime=org.bouncycastle.jcajce.provider.drbg.DRBG\$Default,org.bouncycastle.jcajce.provider.drbg.DRBG\$NonceAndIV
--initialize-at-build-time=org.bouncycastle
to reflect config.json I added:
"java.security.KeyFactory"
    "clojure.lang.RT"
    "org.bouncycastle.crypto.util.PrivateKeyFactory"
    "org.bouncycastle.crypto.util.PublicKeyFactory"
    "org.bouncycastle.jcajce.provider.asymmetric.rsa.KeyFactorySpi"
    "org.bouncycastle.jcajce.provider.asymmetric.rsa.DigestSignatureSpi$SHA256"
    "org.bouncycastle.jcajce.provider.asymmetric.RSA$Mappings"
    "org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPrivateKey"
    "org.bouncycastle.jcajce.provider.asymmetric.edec.BCEdDSAPublicKey"
I get this error from native-image:
Fatal error: com.oracle.graal.pointsto.util.AnalysisError$ParsingError: Error encountered while parsing org.bouncycastle.jcajce.provider.drbg.DRBG$Default.engineNextBytes(byte[]) 
Parsing context:
   at org.bouncycastle.jcajce.provider.drbg.DRBG$Default.engineNextBytes(Unknown Source)
   at java.security.SecureRandom.nextBytes(SecureRandom.java:751)
   at org.bouncycastle.util.BigIntegers.createRandom(BigIntegers.java:362)
   at org.bouncycastle.util.BigIntegers.createRandomBigInteger(BigIntegers.java:292)
   at org.bouncycastle.util.BigIntegers.createRandomInRange(BigIntegers.java:139)
   at org.bouncycastle.math.Primes.enhancedMRProbablePrimeTest(Primes.java:181)
   at org.bouncycastle.crypto.params.RSAKeyParameters.validate(Unknown Source)
   at org.bouncycastle.crypto.params.RSAKeyParameters.<init>(Unknown Source)
   at org.bouncycastle.crypto.params.RSAKeyParameters.<init>(Unknown Source)

Caused by: org.graalvm.compiler.java.BytecodeParser$BytecodeParserError: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected.  Object has been initialized by the org.bouncycastle.jcajce.provider.drbg.DRBG$Default class initializer with a trace: 
 	at org.bouncycastle.crypto.prng.SP800SecureRandom.<init>(Unknown Source)
	at org.bouncycastle.crypto.prng.SP800SecureRandomBuilder.buildHash(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG.createBaseRandom(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG.access$100(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG$Default.<clinit>(Unknown Source)
. Try avoiding to initialize the class that caused initialization of the object. The object was probably created by a class initializer and is reachable from a static field. You can request class initialization at image runtime by using the option --initialize-at-run-time=<class-name>. Or you can write your own initialization methods and call them explicitly from your main entry point.

Caused by: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Detected an instance of Random/SplittableRandom class in the image heap. Instances created during image generation have cached seed values and don't behave as expected.  Object has been initialized by the org.bouncycastle.jcajce.provider.drbg.DRBG$Default class initializer with a trace: 
 	at org.bouncycastle.crypto.prng.SP800SecureRandom.<init>(Unknown Source)
	at org.bouncycastle.crypto.prng.SP800SecureRandomBuilder.buildHash(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG.createBaseRandom(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG.access$100(Unknown Source)
	at org.bouncycastle.jcajce.provider.drbg.DRBG$Default.<clinit>(Unknown Source)
There’s 4 more such errors. I am at a loss of how to proceed

borkdude 2024-06-06T09:32:48.984659Z

FWIW, graalvm-ce-java11-22.3.3 is pretty old

roklenarcic 2024-06-06T09:33:44.440069Z

yeah I know, but its a larger project and this was supposed to be a simple task and now it has ballooned into multiple day troubleshooting

borkdude 2024-06-06T09:33:45.399059Z

Also: unbalanced monitor stuff indicates you or something else is using an older clojure version than 1.11

roklenarcic 2024-06-06T09:34:16.834099Z

unbalanced monitor?

borkdude 2024-06-06T09:34:30.853369Z

Sorry, I scrolled up too much

borkdude 2024-06-06T09:34:35.759959Z

that was a different post ;)

borkdude 2024-06-06T09:35:26.044259Z

> Detected an instance of Random/SplittableRandom class in the image heap. This indicates that .. what is says and this usually happens when something calls a random expression on the top level, rather than at runtime in a function body

roklenarcic 2024-06-06T09:35:56.191829Z

how would I detect where this happens?

borkdude 2024-06-06T09:36:36.307479Z

bisecting and following graal's output

borkdude 2024-06-06T09:36:51.249399Z

run the most minimal example you can come up with

borkdude 2024-06-06T09:37:51.790769Z

start with an an empty -main function. don't load any libraries. Then start adding stuff until you hit the error and then investigate

roklenarcic 2024-06-06T09:39:35.396669Z

I thought that the whole point of rerun-class-initialization-at-runtime option was to reinitialize random generators at runtime

roklenarcic 2024-06-06T09:39:42.048759Z

and that would fix that

borkdude 2024-06-06T09:40:48.169679Z

yes, that would in fact be nice if it worked :)

borkdude 2024-06-06T09:40:55.262559Z

that's a new feature though?

borkdude 2024-06-06T09:42:10.816789Z

hmm, no, it seems to be mentioned way back, but I do remember something was announced around this in recent release notes

borkdude 2024-06-06T09:42:29.629399Z

my "dumb" solution to this is just to avoid any top level randomness

roklenarcic 2024-06-06T09:59:15.605419Z

Might be hard to do. For instance in taoensso.encore thereis:

#?(:clj
   (compile-if (fn [] (java.security.SecureRandom/getInstanceStrong)) ; Java 8+, blocking
     (def ^:private srng* (thread-local-proxy (java.security.SecureRandom/getInstanceStrong)))
     (def ^:private srng* (thread-local-proxy (java.security.SecureRandom/getInstance "SHA1SRNG")))))

borkdude 2024-06-06T10:03:13.486809Z

This dependency is transitively used in bb as well. I haven't had any problems with this specific case

borkdude 2024-06-06T10:04:49.318519Z

Perhaps I haven't upgraded it

roklenarcic 2024-06-06T10:05:54.056729Z

which version?

borkdude 2024-06-06T10:06:17.803589Z

but I know for a fact that @ptaoussanis is pretty open to making stuff work with graalvm native-image

borkdude 2024-06-06T10:06:39.616669Z

which version, let me check

roklenarcic 2024-06-06T10:07:33.945209Z

My main namespace doesn’t include any of these classes

roklenarcic 2024-06-06T10:07:56.409399Z

so I am guessing that graalvm also looks at other namespaces that are not directly included

borkdude 2024-06-06T10:08:20.122029Z

no, it just follows whatever is reachable from your main function

roklenarcic 2024-06-06T10:09:55.370209Z

I mean I get this warning:

Warning: Using a deprecated option --rerun-class-initialization-at-runtime from command line. Currently there is no replacement for this option. Try using --initialize-at-run-time or use the non-API option -H:ClassInitialization directly.

roklenarcic 2024-06-06T10:10:14.606079Z

I guess I have to use an even older graalvm?

borkdude 2024-06-06T10:10:53.900609Z

yes, I would definitely upgrade and consult the most recent docs

borkdude 2024-06-06T10:11:09.130529Z

FWIW this is what is used in bb: [com.taoensso/timbre "6.5.0"] [com.taoensso/encore "3.85.0"] [com.taoensso/truss "1.11.0"] [org.clj-commons/pretty "2.2.1"]

roklenarcic 2024-06-06T10:11:19.876249Z

My main is just:

(ns cli.release
  (:gen-class)
  (:import (java.security Signature)))

(defn -main [& args]
  (Signature/getInstance "EDDSA" "BC"))

borkdude 2024-06-06T10:12:03.288879Z

ok, but because of your bountycastle feature stuff is still loaded. I'm not sure. have you tried compiling the buddy pod?

borkdude 2024-06-06T10:12:19.254809Z

you could perhaps just start with that and then morph it into your project

roklenarcic 2024-06-06T10:12:19.984199Z

But I do have a bunch of classes in --initialize-at-build-time option

roklenarcic 2024-06-06T10:12:26.494359Z

Hm good idea

borkdude 2024-06-06T10:12:39.548139Z

oh yes, you should try to avoid built-time for Java classes whenever you can

borkdude 2024-06-06T10:12:46.041039Z

only use this fro clojure namespaces

borkdude 2024-06-06T10:13:07.138619Z

This plugin will take care of that for you: https://github.com/clj-easy/graal-build-time

roklenarcic 2024-06-06T10:13:29.661549Z

but your pod also has --initialize-at-build-time=org.bouncycastle

borkdude 2024-06-06T10:14:39.281969Z

hmm

borkdude 2024-06-06T10:14:49.277619Z

I don't remember why I did that, it's been a while

borkdude 2024-06-06T10:16:47.097479Z

I'll trigger CI to see if it still builds

roklenarcic 2024-06-06T10:17:24.624779Z

just ran it and it builds

borkdude 2024-06-06T10:18:24.817289Z

ok

borkdude 2024-06-06T10:23:08.823009Z

but I guess only the classes that are actually used by the main function will get initialized at build time

borkdude 2024-06-06T10:23:31.553539Z

and perhaps org.bouncycastle.jcajce.provider.drbg isn't reached in this case?

roklenarcic 2024-06-06T10:29:27.466429Z

If I delete that option from the pod build it fails

roklenarcic 2024-06-06T10:29:44.775179Z

rg.bouncycastle.jcajce.provider.asymmetric.edec.KeyFactorySpi was unintentionally initialized at build time. and so on and on about all the bouncy castle classes

roklenarcic 2024-06-06T10:34:03.495909Z

now I have changed the entry point to:

(ns cli.release
  (:gen-class))

(defn -main [& args]
  1)
and it still fails

roklenarcic 2024-06-06T10:35:41.788449Z

Would clj_easy.graal_build_time.InitClojureClasses do more class initializations at build time?

roklenarcic 2024-06-06T10:36:04.543009Z

How would that class know which classes that are clojure namespaces to initialize?

roklenarcic 2024-06-06T10:38:57.054449Z

Ah:

List var2 = var1.getApplicationClassPath();
        String[] var3 = packages.list(var2);
        String var4 = packages.listStr(var3);
        System.out.println("[clj-easy/graal-build-time] Registering packages for build time initialization: " + var4);
        RuntimeClassInitialization.initializeAtBuildTime(var3);
So basically, regardless of how many namespaces I include from the entry point, all clojure namespaces are initialized

roklenarcic 2024-06-06T10:50:19.170999Z

Ok managed to do it

roklenarcic 2024-06-06T10:53:25.492889Z

So the thing is that the option rerun-class-initialization-at-runtime is crucial. And at my version of graalvm it is deprecated and ignored. I had to add code to that Feature:

@Override
    public void afterRegistration(AfterRegistrationAccess access) {
        RuntimeClassInitialization.initializeAtBuildTime("org.bouncycastle");
        RuntimeClassInitializationSupport rci = ImageSingletons.lookup(RuntimeClassInitializationSupport.class);
        rci.rerunInitialization("org.bouncycastle.jcajce.provider.drbg.DRBG$Default", "");
        rci.rerunInitialization("org.bouncycastle.jcajce.provider.drbg.DRBG$NonceAndIV", "");
        Security.addProvider(new BouncyCastleProvider());
    }

}
But that doesn’t play with the module system as the RuntimeClassInitializationSupport is not exported. So I had to also add option --add-exports=org.graalvm.sdk/org.graalvm.nativeimage.impl=ALL-UNNAMED . If the Feature class was in a module then I would have to name that module

borkdude 2024-06-06T11:48:35.021179Z

I think I read something in recent release notes that there is a new thing in addition to initialize-at-runtime and build-time which re-runs the class

borkdude 2024-06-06T11:48:41.365719Z

but I can't actually find it now

Ingy döt Net 2024-06-06T10:13:18.527609Z

@borkdude out of curiosity about how long do your linux native-image builds of bb take?

borkdude 2024-06-06T10:13:33.361619Z

it depends on your computer ;)

borkdude 2024-06-06T10:13:39.662069Z

on my m1 mac it takes 2-3 minutes

borkdude 2024-06-06T10:14:05.221769Z

on circleci it takes longer, on github actions it takes even longer since that tends to not be as fast

Ingy döt Net 2024-06-06T10:14:35.257679Z

ok I feel in the right ballpark then... thanks