all: Only load Opt in the system class loader

We can detect whether we are running on Android via system properties
and thus don't need to have this bit passed in via a flag.

As a result, the runtime no longer depends on Opt, which is thus no
longer loaded in the bootstrap class loader in addition to the system
class loader.
diff --git a/src/main/java/com/code_intelligence/jazzer/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/BUILD.bazel
index 779fab6..ea3eaa6 100644
--- a/src/main/java/com/code_intelligence/jazzer/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/BUILD.bazel
@@ -117,6 +117,7 @@
     ],
     deps = [
         "//src/main/java/com/code_intelligence/jazzer/driver",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "//src/main/java/com/code_intelligence/jazzer/utils:log",
         "@fmeum_rules_jni//jni/tools/native_loader",
     ],
diff --git a/src/main/java/com/code_intelligence/jazzer/Jazzer.java b/src/main/java/com/code_intelligence/jazzer/Jazzer.java
index 54664d7..e0f7bad 100644
--- a/src/main/java/com/code_intelligence/jazzer/Jazzer.java
+++ b/src/main/java/com/code_intelligence/jazzer/Jazzer.java
@@ -16,6 +16,7 @@
 
 package com.code_intelligence.jazzer;
 
+import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
 import static java.lang.System.exit;
 import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
@@ -208,7 +209,7 @@
     }
     char shellQuote = isPosixOrAndroid() ? '\'' : '"';
     String launcherTemplate;
-    if (isAndroid()) {
+    if (IS_ANDROID) {
       launcherTemplate = "#!/system/bin/env sh\n%s $@\n";
     } else if (isPosix()) {
       launcherTemplate = "#!/usr/bin/env sh\n%s $@\n";
@@ -237,7 +238,7 @@
     // argv0 is printed by libFuzzer during reproduction, so have the launcher basename contain
     // "jazzer".
     Path launcher;
-    if (isAndroid()) {
+    if (IS_ANDROID) {
       launcher = Files.createTempFile(
           Paths.get("/data/local/tmp/"), "jazzer-", launcherExtension, launcherScriptAttributes);
     } else {
@@ -251,7 +252,7 @@
 
   private static Path javaBinary() {
     String javaBinaryName;
-    if (isAndroid()) {
+    if (IS_ANDROID) {
       javaBinaryName = "dalvikvm";
     } else if (isPosix()) {
       javaBinaryName = "java";
@@ -274,7 +275,7 @@
         // entitlements required for library insertion.
         "-Djdk.attach.allowAttachSelf=true", Jazzer.class.getName());
 
-    if (isAndroid()) {
+    if (IS_ANDROID) {
       // ManagementFactory wont work with Android
       return stream;
     }
@@ -300,7 +301,7 @@
   }
 
   private static Path findLibrary(List<String> candidateNames) {
-    if (!isAndroid()) {
+    if (!IS_ANDROID) {
       return findHostClangLibrary(candidateNames);
     }
 
@@ -387,7 +388,7 @@
   }
 
   private static List<String> hwasanLibNames() {
-    if (!isAndroid()) {
+    if (!IS_ANDROID) {
       Log.error("HWAsan is only supported for Android. Please try --asan");
       exit(1);
     }
@@ -397,7 +398,7 @@
 
   private static List<String> asanLibNames() {
     if (isLinux()) {
-      if (isAndroid()) {
+      if (IS_ANDROID) {
         Log.error("ASan is not supported for Android at this time. Use --hwasan for Address "
             + "Sanitization on Android");
         exit(1);
@@ -412,7 +413,7 @@
 
   private static List<String> ubsanLibNames() {
     if (isLinux()) {
-      if (isAndroid()) {
+      if (IS_ANDROID) {
         // return asList("libclang_rt.ubsan_standalone-aarch64-android.so");
         Log.error("ERROR: UBSan is not supported for Android at this time.");
         exit(1);
@@ -437,15 +438,14 @@
   }
 
   private static boolean isPosix() {
-    return !isAndroid() && FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
-  }
-
-  private static boolean isAndroid() {
-    return Boolean.parseBoolean(System.getProperty("jazzer.android", "false"));
+    return !IS_ANDROID && FileSystems.getDefault().supportedFileAttributeViews().contains("posix");
   }
 
   private static boolean isPosixOrAndroid() {
-    return isPosix() || isAndroid();
+    if (isPosix()) {
+      return true;
+    }
+    return IS_ANDROID;
   }
 
   private static byte[] readAllBytes(InputStream in) throws IOException {
diff --git a/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java b/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java
index 5006c24..5dd041a 100644
--- a/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java
+++ b/src/main/java/com/code_intelligence/jazzer/agent/AgentInstaller.java
@@ -15,8 +15,8 @@
 package com.code_intelligence.jazzer.agent;
 
 import static com.code_intelligence.jazzer.agent.AgentUtils.extractBootstrapJar;
+import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
 
-import com.code_intelligence.jazzer.driver.Opt;
 import java.lang.instrument.Instrumentation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -37,7 +37,7 @@
       return;
     }
 
-    if (Opt.isAndroid) {
+    if (IS_ANDROID) {
       return;
     }
 
diff --git a/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel
index 89dc0bc..3106acb 100644
--- a/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/agent/BUILD.bazel
@@ -11,6 +11,7 @@
     deps = [
         ":agent_lib",
         "//src/main/java/com/code_intelligence/jazzer/driver:opt",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "@net_bytebuddy_byte_buddy_agent//jar",
     ],
 )
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel
index 468d4d7..48e4af4 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/driver/BUILD.bazel
@@ -22,6 +22,7 @@
         ":opt",
         "//src/main/java/com/code_intelligence/jazzer/agent:agent_installer",
         "//src/main/java/com/code_intelligence/jazzer/driver/junit:junit_runner",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "//src/main/java/com/code_intelligence/jazzer/utils:log",
     ],
 )
@@ -47,6 +48,7 @@
     deps = [
         ":opt",
         "//src/main/java/com/code_intelligence/jazzer/api:hooks",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "//src/main/java/com/code_intelligence/jazzer/utils:log",
     ],
 )
@@ -59,6 +61,7 @@
         ":fuzz_target_holder",
         ":opt",
         "//src/main/java/com/code_intelligence/jazzer/api",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "//src/main/java/com/code_intelligence/jazzer/utils:log",
         "//src/main/java/com/code_intelligence/jazzer/utils:manifest_utils",
     ],
@@ -106,6 +109,7 @@
         "//src/main/java/com/code_intelligence/jazzer/autofuzz",
         "//src/main/java/com/code_intelligence/jazzer/instrumentor",
         "//src/main/java/com/code_intelligence/jazzer/mutation",
+        "//src/main/java/com/code_intelligence/jazzer/runtime:constants",
         "//src/main/java/com/code_intelligence/jazzer/runtime:jazzer_bootstrap_compile_only",
         "//src/main/java/com/code_intelligence/jazzer/utils:log",
         "//src/main/java/com/code_intelligence/jazzer/utils:manifest_utils",
@@ -143,9 +147,10 @@
         "OptParser.java",
     ],
     visibility = [
-        "//src/main/java/com/code_intelligence/jazzer:__subpackages__",
+        "//src/main/java/com/code_intelligence/jazzer/agent:__pkg__",
+        "//src/main/java/com/code_intelligence/jazzer/driver:__subpackages__",
         "//src/main/java/com/code_intelligence/jazzer/junit:__pkg__",
-        "//src/test/java/com/code_intelligence/jazzer/driver:__pkg__",
+        "//src/test/java/com/code_intelligence/jazzer/driver:__subpackages__",
     ],
     deps = [
         "//src/main/java/com/code_intelligence/jazzer:constants",
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/Driver.java b/src/main/java/com/code_intelligence/jazzer/driver/Driver.java
index a99a155..8640e6c 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/Driver.java
+++ b/src/main/java/com/code_intelligence/jazzer/driver/Driver.java
@@ -16,6 +16,7 @@
 
 package com.code_intelligence.jazzer.driver;
 
+import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
 import static java.lang.System.exit;
 
 import com.code_intelligence.jazzer.agent.AgentInstaller;
@@ -32,8 +33,7 @@
 
 public class Driver {
   public static int start(List<String> args, boolean spawnsSubprocesses) throws IOException {
-    boolean isAndroid = Boolean.parseBoolean(System.getProperty("jazzer.android", "false"));
-    if (isAndroid) {
+    if (IS_ANDROID) {
       if (!System.getProperty("jazzer.autofuzz", "").isEmpty()) {
         Log.error("--autofuzz is not supported for Android");
         return 1;
@@ -65,7 +65,7 @@
         // pass its path to the agent in every child process. This requires adding
         // the argument to argv for it to be picked up by libFuzzer, which then
         // forwards it to child processes.
-        if (!isAndroid) {
+        if (!IS_ANDROID) {
           idSyncFile = Files.createTempFile("jazzer-", "");
         } else {
           File f = File.createTempFile("jazzer-", "", new File("/data/local/tmp/"));
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt
index 3bec53d..ed4b056 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt
+++ b/src/main/java/com/code_intelligence/jazzer/driver/ExceptionUtils.kt
@@ -17,6 +17,7 @@
 package com.code_intelligence.jazzer.driver
 
 import com.code_intelligence.jazzer.api.FuzzerSecurityIssueLow
+import com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID
 import com.code_intelligence.jazzer.utils.Log
 import java.lang.management.ManagementFactory
 import java.nio.ByteBuffer
@@ -97,7 +98,7 @@
             (frame !in observedFrames).also { observedFrames.add(frame) }
         }
         var securityIssueMessage = "Stack overflow"
-        if (!Opt.isAndroid) {
+        if (!IS_ANDROID) {
             securityIssueMessage = "$securityIssueMessage (use '${getReproducingXssArg()}' to reproduce)"
         }
         FuzzerSecurityIssueLow(securityIssueMessage, throwable).apply {
@@ -106,7 +107,7 @@
     }
     is OutOfMemoryError -> {
         var securityIssueMessage = "Out of memory"
-        if (!Opt.isAndroid) {
+        if (!IS_ANDROID) {
             securityIssueMessage = "$securityIssueMessage (use '${getReproducingXmxArg()}' to reproduce)"
         }
         stripOwnStackTrace(FuzzerSecurityIssueLow(securityIssueMessage, throwable))
@@ -200,7 +201,7 @@
         Log.println("")
     }
 
-    if (Opt.isAndroid) {
+    if (IS_ANDROID) {
         // ManagementFactory is not supported on Android
         return
     }
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java
index ef3c0bc..c2c4177 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java
+++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetFinder.java
@@ -16,6 +16,7 @@
 
 package com.code_intelligence.jazzer.driver;
 
+import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
 import static java.lang.System.exit;
 
 import com.code_intelligence.jazzer.api.FuzzedDataProvider;
@@ -40,7 +41,7 @@
     if (!Opt.targetClass.isEmpty()) {
       return Opt.targetClass;
     }
-    if (Opt.isAndroid) {
+    if (IS_ANDROID) {
       // Fuzz target detection tools aren't supported on android
       return null;
     }
@@ -55,12 +56,8 @@
   static FuzzTarget findFuzzTarget(String targetClassName) {
     Class<?> fuzzTargetClass;
     try {
-      if (Opt.isAndroid) {
-        fuzzTargetClass =
-            Class.forName(targetClassName, false, FuzzTargetFinder.class.getClassLoader());
-      } else {
-        fuzzTargetClass = Class.forName(targetClassName);
-      }
+      fuzzTargetClass =
+          Class.forName(targetClassName, false, FuzzTargetFinder.class.getClassLoader());
     } catch (ClassNotFoundException e) {
       Log.error(String.format(
           "'%s' not found on classpath:%n%n%s%n%nAll required classes must be on the classpath specified via --cp.",
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java
index 7a4992b..a5e189e 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java
+++ b/src/main/java/com/code_intelligence/jazzer/driver/FuzzTargetRunner.java
@@ -17,6 +17,7 @@
 package com.code_intelligence.jazzer.driver;
 
 import static com.code_intelligence.jazzer.driver.Constants.JAZZER_FINDING_EXIT_CODE;
+import static com.code_intelligence.jazzer.runtime.Constants.IS_ANDROID;
 import static java.lang.System.exit;
 import static java.util.stream.Collectors.joining;
 
@@ -95,7 +96,7 @@
       throw new IllegalStateException(e);
     }
     useFuzzedDataProvider = fuzzTarget.usesFuzzedDataProvider();
-    if (!useFuzzedDataProvider && Opt.isAndroid) {
+    if (!useFuzzedDataProvider && IS_ANDROID) {
       Log.error("Android fuzz targets must use " + FuzzedDataProvider.class.getName());
       exit(1);
       throw new IllegalStateException("Not reached");
@@ -367,7 +368,9 @@
       // https://github.com/llvm/llvm-project/blob/da3623de2411dd931913eb510e94fe846c929c24/compiler-rt/lib/fuzzer/FuzzerFlags.def#L19
       args.add("-len_control=100");
     }
-    SignalHandler.initialize();
+    if (!IS_ANDROID) {
+      SignalHandler.initialize();
+    }
     return startLibFuzzer(
         args.stream().map(str -> str.getBytes(StandardCharsets.UTF_8)).toArray(byte[][] ::new));
   }
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java
index f06f81e..78accfd 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/Opt.java
+++ b/src/main/java/com/code_intelligence/jazzer/driver/Opt.java
@@ -42,15 +42,15 @@
  * <p>Each option corresponds to a command-line argument of the driver of the same name.
  *
  * <p>Every public field should be deeply immutable.
- *
- * <p>This class is loaded twice: As it is used in {@link FuzzTargetRunner}, it is loaded in the
- * class loader that loads {@link Driver}. It is also used in
- * {@link com.code_intelligence.jazzer.agent.Agent} after the agent JAR has been added to the
- * bootstrap classpath and thus is loaded again in the bootstrap loader. This is not a problem since
- * it only provides immutable fields and has no non-fatal side effects.
  */
 public final class Opt {
   static {
+    if (Opt.class.getClassLoader() == null) {
+      throw new IllegalStateException("Opt should not be loaded in the bootstrap class loader");
+    }
+  }
+
+  static {
     // We additionally list system properties supported by the Jazzer JUnit engine that do not
     // directly map to arguments. These are not shown in help texts.
     ignoreSetting("instrument");
@@ -153,10 +153,6 @@
   public static final boolean dedup =
       boolSetting("dedup", hooks, "Compute and print a deduplication token for every finding");
 
-  // Default to false. Sets if fuzzing is taking place on Android device (virtual or physical)
-  public static final boolean isAndroid =
-      boolSetting("android", false, "Jazzer is running on Android");
-
   // Whether hook instrumentation should add a check for JazzerInternal#hooksEnabled before
   // executing hooks. Used to disable hooks during non-fuzz JUnit tests.
   public static final boolean conditionalHooks =
diff --git a/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java b/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java
index 0ad913c..215a047 100644
--- a/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java
+++ b/src/main/java/com/code_intelligence/jazzer/driver/SignalHandler.java
@@ -19,10 +19,8 @@
 
 public final class SignalHandler {
   static {
-    if (!Opt.isAndroid) {
-      RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class);
-      Signal.handle(new Signal("INT"), sig -> handleInterrupt());
-    }
+    RulesJni.loadLibrary("jazzer_signal_handler", SignalHandler.class);
+    Signal.handle(new Signal("INT"), sig -> handleInterrupt());
   }
 
   public static void initialize() {
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
index 59064a8..6198f08 100644
--- a/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/BUILD.bazel
@@ -104,7 +104,6 @@
         "//src/test:__subpackages__",
     ],
     deps = [
-        "//src/main/java/com/code_intelligence/jazzer/driver:opt",
         "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider",
     ],
 )
@@ -123,7 +122,7 @@
     srcs = ["FuzzTargetRunnerNatives.java"],
     visibility = ["//src/main/native/com/code_intelligence/jazzer/driver:__pkg__"],
     deps = [
-        "//src/main/java/com/code_intelligence/jazzer/driver:opt",
+        ":constants",
     ],
 )
 
@@ -159,9 +158,17 @@
         "//src/main/java/com/code_intelligence/jazzer/utils:unsafe_provider",
     ],
     deps = [
+        ":constants",
         ":coverage_map",
         ":trace_data_flow_native_callbacks",
         "//src/main/java/com/code_intelligence/jazzer/api:hooks",
-        "//src/main/java/com/code_intelligence/jazzer/driver:opt",
     ],
 )
+
+# This target exposes a class that can safely be loaded in both the system and the bootstrap class
+# loader as it provides true constants that do not change over the lifetime of the JVM.
+java_library(
+    name = "constants",
+    srcs = ["Constants.java"],
+    visibility = ["//visibility:public"],
+)
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java b/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java
new file mode 100644
index 0000000..92f4a3c
--- /dev/null
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/Constants.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 Code Intelligence GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.code_intelligence.jazzer.runtime;
+
+public final class Constants {
+  public static final boolean IS_ANDROID = System.getProperty("java.vm.vendor").contains("Android");
+}
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java
index 2db68ad..bbf74fd 100644
--- a/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/FuzzTargetRunnerNatives.java
@@ -14,7 +14,6 @@
 
 package com.code_intelligence.jazzer.runtime;
 
-import com.code_intelligence.jazzer.driver.Opt;
 import com.github.fmeum.rules_jni.RulesJni;
 
 /**
@@ -26,7 +25,7 @@
  */
 public class FuzzTargetRunnerNatives {
   static {
-    if (!Opt.isAndroid && FuzzTargetRunnerNatives.class.getClassLoader() != null) {
+    if (!Constants.IS_ANDROID && FuzzTargetRunnerNatives.class.getClassLoader() != null) {
       throw new IllegalStateException(
           "FuzzTargetRunnerNatives must be loaded in the bootstrap loader");
     }
diff --git a/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
index 01573f4..8572f05 100644
--- a/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
+++ b/src/main/java/com/code_intelligence/jazzer/runtime/NativeLibHooks.java
@@ -16,7 +16,6 @@
 
 import com.code_intelligence.jazzer.api.HookType;
 import com.code_intelligence.jazzer.api.MethodHook;
-import com.code_intelligence.jazzer.driver.Opt;
 import java.lang.invoke.MethodHandle;
 
 @SuppressWarnings("unused")
@@ -31,7 +30,7 @@
       targetMethodDescriptor = "(Ljava/lang/String;)V")
   public static void
   loadLibraryHook(MethodHandle method, Object thisObject, Object[] arguments, int hookId) {
-    if (Opt.isAndroid) {
+    if (Constants.IS_ANDROID) {
       return;
     }