This is an automated email from the ASF dual-hosted git repository.

ifesdjeen pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/cassandra-simulator.git

commit c694efb9ba66674c63ac6193e6766b7da41573bd
Author: Alex Petrov <[email protected]>
AuthorDate: Mon Jul 13 14:26:14 2026 +0000

    Reject Simulator outside system classloader
    
    buildSchedule() assumes simulator-shared classes come from the system 
classloader. Fail fast when Simulator is loaded from a different host loader so 
unsupported classloader setups do not silently split shared static state.
---
 .../org/apache/cassandra/simulator/Simulator.java  |   5 +-
 .../SimulatorHostClassLoaderReproTest.java         | 144 +++++++++++++++++++++
 2 files changed, 148 insertions(+), 1 deletion(-)

diff --git 
a/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java 
b/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
index feb5d6a..6fb96b1 100644
--- a/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
+++ b/simulator-core/src/main/java/org/apache/cassandra/simulator/Simulator.java
@@ -162,7 +162,10 @@ public class Simulator implements AutoCloseable
     {
         this.monitorDelayChance = monitorDelayChance;
         ClassLoader cl = getClass().getClassLoader();
-        this.classLoader = cl != null ? cl : 
ClassLoader.getSystemClassLoader();
+        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+        if (cl != null && cl != systemClassLoader)
+            throw new IllegalStateException("Simulator must be loaded by the 
system classloader; found " + cl);
+        this.classLoader = cl != null ? cl : systemClassLoader;
 
         RandomSource.Default rnd = new RandomSource.Default();
         rnd.reset(seed);
diff --git 
a/simulator-core/src/test/java/org/apache/cassandra/simulator_test/SimulatorHostClassLoaderReproTest.java
 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/SimulatorHostClassLoaderReproTest.java
new file mode 100644
index 0000000..a51cbc5
--- /dev/null
+++ 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/SimulatorHostClassLoaderReproTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 org.apache.cassandra.simulator_test;
+
+import java.io.File;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Arrays;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotSame;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that Simulator rejects construction from a non-system host 
classloader.
+ *
+ * Expected behavior: constructing Simulator through a non-system 
URLClassLoader fails
+ * immediately with an IllegalStateException.
+ *
+ * Failure criterion: invoking Probe.constructSimulator() via a non-system 
URLClassLoader
+ * throws IllegalStateException with a message that mentions the system 
classloader
+ * requirement.
+ */
+public class SimulatorHostClassLoaderReproTest
+{
+    @Test
+    void simulatorConstructionFromNonSystemLoaderIsRejected() throws Exception
+    {
+        try (URLClassLoader loader = new 
ChildFirstURLClassLoader(runtimeClasspath(), 
ClassLoader.getPlatformClassLoader()))
+        {
+            Class<?> probeClass = Class.forName(Probe.class.getName(), true, 
loader);
+            Method probeMethod = probeClass.getMethod("constructSimulator");
+
+            // TRIGGER: run Simulator from a non-system host classloader.
+            assertNotSame(Probe.class, probeClass, "probe must be loaded 
through the custom host loader");
+            assertSame(loader, probeClass.getClassLoader(), "probe must come 
from the custom host loader");
+
+            // HARNESS: reflectively invoke a child-loaded helper that 
constructs Simulator.
+            // ORACLE: this must fail fast with IllegalStateException instead 
of silently accepting
+            // an unsupported classloader configuration.
+            Throwable failure = invokeAndCapture(probeMethod);
+            assertEquals(IllegalStateException.class, failure.getClass());
+            assertTrue(failure.getMessage().contains("Simulator must be loaded 
by the system classloader"),
+                       "unexpected message: " + failure.getMessage());
+        }
+    }
+
+    public static final class Probe
+    {
+        public static void constructSimulator()
+        {
+            new org.apache.cassandra.simulator.Simulator(42L).close();
+        }
+    }
+
+    private static Throwable invokeAndCapture(Method probeMethod) throws 
Exception
+    {
+        try
+        {
+            probeMethod.invoke(null);
+            throw new AssertionError("expected simulator construction to 
fail");
+        }
+        catch (InvocationTargetException e)
+        {
+            return e.getCause();
+        }
+    }
+
+    private static final class ChildFirstURLClassLoader extends URLClassLoader
+    {
+        private ChildFirstURLClassLoader(URL[] urls, ClassLoader parent)
+        {
+            super(urls, parent);
+        }
+
+        @Override
+        protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException
+        {
+            synchronized (getClassLoadingLock(name))
+            {
+                Class<?> loaded = findLoadedClass(name);
+                if (loaded == null && shouldLoadChildFirst(name))
+                {
+                    try
+                    {
+                        loaded = findClass(name);
+                    }
+                    catch (ClassNotFoundException ignored)
+                    {
+                    }
+                }
+                if (loaded == null)
+                    loaded = super.loadClass(name, false);
+                if (resolve)
+                    resolveClass(loaded);
+                return loaded;
+            }
+        }
+
+        private boolean shouldLoadChildFirst(String name)
+        {
+            return name.startsWith("org.apache.cassandra.simulator.")
+                   || name.startsWith("org.apache.cassandra.simulator_test.");
+        }
+    }
+
+    private static URL[] runtimeClasspath()
+    {
+        return 
Arrays.stream(System.getProperty("java.class.path").split(File.pathSeparator))
+                     .map(path -> {
+                         try
+                         {
+                             return new File(path).toURI().toURL();
+                         }
+                         catch (MalformedURLException e)
+                         {
+                             throw new RuntimeException(e);
+                         }
+                     })
+                     .toArray(URL[]::new);
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to