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 4546e356d88bcd62fd16bdef304a33f2b8eea7ff
Author: Alex Petrov <[email protected]>
AuthorDate: Thu Jul 23 19:40:22 2026 +0200

    Add static intercept rules; make sure intercept rules only receive one 
method name
---
 .../cassandra/simulator/asm/ClassTransformer.java  |  12 +-
 .../simulator/asm/GlobalMethodTransformer.java     |  35 +++--
 .../cassandra/simulator/asm/InterceptClasses.java  |   4 +-
 .../cassandra/simulator/asm/InterceptRule.java     | 100 ++++++++++--
 .../cassandra/simulator/asm/InterceptRules.java    | 171 +++++++++++++++++++++
 .../asm/InterceptRuleConstructorTest.java          |  61 ++++++++
 .../asm/InterceptRuleFactoryMethodTest.java        |  64 ++++++++
 .../simulator/asm/InterceptRuleMethodCallTest.java |  67 ++++++++
 .../asm/InterceptRuleStaticMethodCallTest.java     |  59 +++++++
 .../simulator/asm/InterceptRuleTestSupport.java    |  90 +++++++++++
 .../simulator/asm/InterceptRulesTest.java          |  66 ++++++++
 .../org/apache/cassandra/simulator/Simulator.java  |   1 -
 ...leMethodCallOutsideConfiguredPackagesTest.java} |   2 +-
 ...eTest.java => InterceptRuleMethodCallTest.java} |   2 +-
 14 files changed, 696 insertions(+), 38 deletions(-)

diff --git 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/ClassTransformer.java
 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/ClassTransformer.java
index e2f5209..1c543ec 100644
--- 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/ClassTransformer.java
+++ 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/ClassTransformer.java
@@ -18,10 +18,8 @@
 
 package org.apache.cassandra.simulator.asm;
 
-import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
-import java.util.Set;
 import java.util.function.Consumer;
 
 import org.objectweb.asm.AnnotationVisitor;
@@ -107,7 +105,7 @@ class ClassTransformer extends ClassVisitor implements 
MethodWriterSink
     private final NemesisFieldKind.Selector nemesisFieldSelector;
     private final Hashcode insertHashcode;
     private final MethodLogger methodLogger;
-    private final Set<InterceptRule> customRules;
+    private final InterceptRules customRules;
     private boolean isTransformed;
     private boolean isCacheablyTransformed = true;
     private final EnumSet<Flag> flags;
@@ -117,20 +115,20 @@ class ClassTransformer extends ClassVisitor implements 
MethodWriterSink
 
     ClassTransformer(int api, String className, EnumSet<Flag> flags, 
Consumer<String> dependentTypes)
     {
-        this(api, new ClassWriter(ClassWriter.COMPUTE_MAXS), className, flags, 
null, null, null, null, Collections.emptySet(), dependentTypes);
+        this(api, new ClassWriter(ClassWriter.COMPUTE_MAXS), className, flags, 
null, null, null, null, InterceptRules.none(), dependentTypes);
     }
 
     ClassTransformer(int api, String className, EnumSet<Flag> flags, 
ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, 
NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode, 
Consumer<String> dependentTypes)
     {
-        this(api, new ClassWriter(ClassWriter.COMPUTE_MAXS), className, flags, 
monitorDelayChance, nemesis, nemesisFieldSelector, insertHashcode, 
Collections.emptySet(), dependentTypes);
+        this(api, new ClassWriter(ClassWriter.COMPUTE_MAXS), className, flags, 
monitorDelayChance, nemesis, nemesisFieldSelector, insertHashcode, 
InterceptRules.none(), dependentTypes);
     }
 
-    ClassTransformer(int api, String className, EnumSet<Flag> flags, 
ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, 
NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode, 
Set<InterceptRule> customRules, Consumer<String> dependentTypes)
+    ClassTransformer(int api, String className, EnumSet<Flag> flags, 
ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, 
NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode, 
InterceptRules customRules, Consumer<String> dependentTypes)
     {
         this(api, new ClassWriter(ClassWriter.COMPUTE_MAXS), className, flags, 
monitorDelayChance, nemesis, nemesisFieldSelector, insertHashcode, customRules, 
dependentTypes);
     }
 
-    private ClassTransformer(int api, ClassWriter classWriter, String 
className, EnumSet<Flag> flags, ChanceSupplier monitorDelayChance, 
NemesisGenerator nemesis, NemesisFieldKind.Selector nemesisFieldSelector, 
Hashcode insertHashcode, Set<InterceptRule> customRules, Consumer<String> 
dependentTypes)
+    private ClassTransformer(int api, ClassWriter classWriter, String 
className, EnumSet<Flag> flags, ChanceSupplier monitorDelayChance, 
NemesisGenerator nemesis, NemesisFieldKind.Selector nemesisFieldSelector, 
Hashcode insertHashcode, InterceptRules customRules, Consumer<String> 
dependentTypes)
     {
         super(api, classWriter);
         if (flags.contains(NEMESIS) && (nemesis == null || 
nemesisFieldSelector == null))
diff --git 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/GlobalMethodTransformer.java
 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/GlobalMethodTransformer.java
index 0153473..541a3f9 100644
--- 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/GlobalMethodTransformer.java
+++ 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/GlobalMethodTransformer.java
@@ -18,9 +18,7 @@
 
 package org.apache.cassandra.simulator.asm;
 
-import java.util.Collections;
 import java.util.EnumSet;
-import java.util.Set;
 
 import org.objectweb.asm.AnnotationVisitor;
 import org.objectweb.asm.MethodVisitor;
@@ -48,7 +46,7 @@ class GlobalMethodTransformer extends MethodVisitor
 {
     private final ClassTransformer transformer;
     private final String methodName;
-    private final Set<InterceptRule> customRules;
+    private final InterceptRules customRules;
     /** Enables the bulk of built-in redirects: Thread.start, Executors, UUID, 
Unsafe,
      *  Uninterruptibles, TimeUnit.sleep, and Future.get. */
     private boolean globalMethods;
@@ -65,10 +63,10 @@ class GlobalMethodTransformer extends MethodVisitor
 
     public GlobalMethodTransformer(EnumSet<Flag> flags, ClassTransformer 
transformer, int api, String methodName, MethodVisitor parent)
     {
-        this(flags, transformer, api, methodName, parent, 
Collections.emptySet());
+        this(flags, transformer, api, methodName, parent, 
InterceptRules.none());
     }
 
-    public GlobalMethodTransformer(EnumSet<Flag> flags, ClassTransformer 
transformer, int api, String methodName, MethodVisitor parent, 
Set<InterceptRule> customRules)
+    public GlobalMethodTransformer(EnumSet<Flag> flags, ClassTransformer 
transformer, int api, String methodName, MethodVisitor parent, InterceptRules 
customRules)
     {
         super(api, parent);
         this.globalMethods = flags.contains(GLOBAL_METHODS);
@@ -174,7 +172,7 @@ class GlobalMethodTransformer extends MethodVisitor
             else
                 super.visitMethodInsn(opcode, owner, name, descriptor, 
isInterface);
         }
-        else if (!customRules.isEmpty() && applyMethodRule(opcode, owner, 
name, descriptor, isFirstMethodInsn))
+        else if (applyMethodRule(opcode, owner, name, descriptor, 
isFirstMethodInsn))
         {
             // emitted by the matching rule
         }
@@ -189,8 +187,13 @@ class GlobalMethodTransformer extends MethodVisitor
      */
     private boolean applyMethodRule(int opcode, String owner, String name, 
String descriptor, boolean isFirstMethodInsn)
     {
-        for (InterceptRule rule : customRules)
+        InterceptRuleBucket candidates = customRules.forSourceOwner(owner);
+        if (candidates == null)
+            return false;
+
+        for (int i = 0, size = candidates.size(); i < size; i++)
         {
+            InterceptRule rule = candidates.get(i);
             if (!rule.match(opcode, owner, name, descriptor))
                 continue;
 
@@ -219,11 +222,21 @@ class GlobalMethodTransformer extends MethodVisitor
                     // The target descriptor consumes it as its first 
parameter.
                     super.visitMethodInsn(Opcodes.INVOKESTATIC,
                                           methodCall.toClass,
-                                          methodCall.toMethod,
+                                          methodCall.fromMethod,
                                           methodCall.toMethodDescriptor,
                                           false);
                     return true;
 
+                case STATIC_METHOD_CALL:
+                    InterceptRule.StaticMethodCall staticMethodCall = 
(InterceptRule.StaticMethodCall) rule;
+                    transformer.witness(GLOBAL_METHOD);
+                    super.visitMethodInsn(Opcodes.INVOKESTATIC,
+                                          staticMethodCall.destClass,
+                                          staticMethodCall.sourceMethod,
+                                          staticMethodCall.descriptor,
+                                          false);
+                    return true;
+
                 default:
                     throw new AssertionError(rule.kind());
             }
@@ -284,10 +297,12 @@ class GlobalMethodTransformer extends MethodVisitor
     @Override
     public void visitTypeInsn(int opcode, String type)
     {
-        if (!customRules.isEmpty() && opcode == Opcodes.NEW)
+        InterceptRuleBucket candidates;
+        if (opcode == Opcodes.NEW && (candidates = 
customRules.forSourceOwner(type)) != null)
         {
-            for (InterceptRule rule : customRules)
+            for (int i = 0, size = candidates.size(); i < size; i++)
             {
+                InterceptRule rule = candidates.get(i);
                 if (!rule.match(opcode, type, null, null))
                     continue;
 
diff --git 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptClasses.java
 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptClasses.java
index f3bc092..33be3d2 100644
--- 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptClasses.java
+++ 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptClasses.java
@@ -125,7 +125,7 @@ public class InterceptClasses implements BiFunction<String, 
byte[], byte[]>
     private final NemesisFieldKind.Selector nemesisFieldSelector;
     private final ClassLoader prewarmClassLoader;
     private final Predicate<String> prewarm;
-    private final Set<InterceptRule> customRules;
+    private final InterceptRules customRules;
     private final byte[] bufIn = new byte[4096];
     private final ByteArrayOutputStream bufOut = new ByteArrayOutputStream();
 
@@ -153,7 +153,7 @@ public class InterceptClasses implements BiFunction<String, 
byte[], byte[]>
         this.nemesisFieldSelector = nemesisFieldSelector;
         this.prewarmClassLoader = prewarmClassLoader;
         this.prewarm = prewarm;
-        this.customRules = customRules;
+        this.customRules = InterceptRules.index(customRules);
     }
 
     @Override
diff --git 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRule.java
 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRule.java
index d41262d..582a291 100644
--- 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRule.java
+++ 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRule.java
@@ -20,26 +20,30 @@ package org.apache.cassandra.simulator.asm;
 
 import java.util.Objects;
 
+import org.apache.cassandra.simulator.asm.InterceptRules.RuleOrRules;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
 /**
  * A bytecode interception rule applied by {@link GlobalMethodTransformer}.
  */
-public abstract class InterceptRule
+public abstract class InterceptRule extends RuleOrRules
 {
     public enum Kind
     {
         FACTORY_METHOD,
         CONSTRUCTOR,
-        METHOD_CALL
+        METHOD_CALL,
+        STATIC_METHOD_CALL
     }
 
     private final Kind kind;
+    public final String fromClass;
 
-    private InterceptRule(Kind kind)
+    private InterceptRule(Kind kind, String fromClass)
     {
         this.kind = kind;
+        this.fromClass = fromClass;
     }
 
     public final Kind kind()
@@ -47,6 +51,25 @@ public abstract class InterceptRule
         return kind;
     }
 
+    public final String sourceOwner()
+    {
+        return fromClass;
+    }
+
+    @Override
+    final int size()
+    {
+        return 1;
+    }
+
+    @Override
+    final InterceptRule get(int index)
+    {
+        if (index != 0)
+            throw new IndexOutOfBoundsException(index);
+        return this;
+    }
+
     /**
      * Tests an ASM instruction. For type instructions, {@code name} and 
{@code descriptor}
      * are {@code null} and {@code owner} contains the instruction's type.
@@ -59,14 +82,12 @@ public abstract class InterceptRule
      */
     public static final class FactoryMethod extends InterceptRule
     {
-        public final String fromClass;
         public final String methodName;
         public final String descriptor;
 
         public FactoryMethod(String fromClass, String methodName, String 
descriptor)
         {
-            super(Kind.FACTORY_METHOD);
-            this.fromClass = fromClass;
+            super(Kind.FACTORY_METHOD, fromClass);
             this.methodName = methodName;
             this.descriptor = descriptor;
         }
@@ -114,13 +135,11 @@ public abstract class InterceptRule
      */
     public static final class Constructor extends InterceptRule
     {
-        public final String fromClass;
         public final String toClass;
 
         public Constructor(String fromClass, String toClass)
         {
-            super(Kind.CONSTRUCTOR);
-            this.fromClass = fromClass;
+            super(Kind.CONSTRUCTOR, fromClass);
             this.toClass = toClass;
         }
 
@@ -154,6 +173,60 @@ public abstract class InterceptRule
         }
     }
 
+    /**
+     * Redirects a static invocation to the same method on another class.
+     *
+     * Keeping the method name and descriptor fixed makes this a class 
substitution rather than
+     * a general bytecode rewrite.
+     */
+    public static final class StaticMethodCall extends InterceptRule
+    {
+        public final String sourceMethod;
+        public final String descriptor;
+        public final String destClass;
+
+        public StaticMethodCall(String sourceClass, String sourceMethod, 
String descriptor, String destClass)
+        {
+            super(Kind.STATIC_METHOD_CALL, sourceClass);
+            this.sourceMethod = sourceMethod;
+            this.descriptor = descriptor;
+            this.destClass = destClass;
+        }
+
+        @Override
+        public boolean match(int opcode, String owner, String name, String 
descriptor)
+        {
+            return opcode == Opcodes.INVOKESTATIC
+                   && fromClass.equals(owner)
+                   && sourceMethod.equals(name)
+                   && this.descriptor.equals(descriptor);
+        }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            if (!(o instanceof StaticMethodCall))
+                return false;
+            StaticMethodCall that = (StaticMethodCall) o;
+            return fromClass.equals(that.fromClass)
+                   && sourceMethod.equals(that.sourceMethod)
+                   && descriptor.equals(that.descriptor);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return Objects.hash(fromClass, sourceMethod, descriptor);
+        }
+
+        @Override
+        public String toString()
+        {
+            return "StaticMethodCall(" + fromClass + '.' + sourceMethod + 
descriptor
+                   + " -> " + destClass + '.' + sourceMethod + descriptor + 
')';
+        }
+    }
+
     /**
      * Redirects an instance invocation to a static method.
      *
@@ -163,27 +236,22 @@ public abstract class InterceptRule
      */
     public static final class MethodCall extends InterceptRule
     {
-        public final String fromClass;
         public final String fromMethod;
         public final String fromMethodDescriptor;
         public final String toClass;
-        public final String toMethod;
         public final String toMethodDescriptor;
 
         public MethodCall(String fromClass,
                           String fromMethod,
                           String fromMethodDescriptor,
                           String toClass,
-                          String toMethod,
                           String toMethodDescriptor)
         {
-            super(Kind.METHOD_CALL);
-            this.fromClass = fromClass;
+            super(Kind.METHOD_CALL, fromClass);
             this.fromMethod = fromMethod;
             this.fromMethodDescriptor = fromMethodDescriptor;
             validateDescriptors(fromMethodDescriptor, toMethodDescriptor);
             this.toClass = toClass;
-            this.toMethod = toMethod;
             this.toMethodDescriptor = toMethodDescriptor;
         }
 
@@ -237,7 +305,7 @@ public abstract class InterceptRule
         public String toString()
         {
             return "MethodCall(" + fromClass + '.' + fromMethod + 
fromMethodDescriptor
-                   + " -> " + toClass + '.' + toMethod + toMethodDescriptor + 
')';
+                   + " -> " + toClass + '.' + fromMethod + toMethodDescriptor 
+ ')';
         }
     }
 }
diff --git 
a/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRules.java
 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRules.java
new file mode 100644
index 0000000..fb94272
--- /dev/null
+++ 
b/simulator-asm/src/main/java/org/apache/cassandra/simulator/asm/InterceptRules.java
@@ -0,0 +1,171 @@
+/*
+ * 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.asm;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * Immutable interception-rule index keyed by the source owner of a bytecode 
instruction.
+ */
+final class InterceptRules
+{
+    private static final InterceptRules NONE = new 
InterceptRules(Collections.emptyMap());
+
+    private final Map<String, RuleOrRules> bySourceOwner;
+
+    private InterceptRules(Map<String, RuleOrRules> bySourceOwner)
+    {
+        this.bySourceOwner = bySourceOwner;
+    }
+
+    static InterceptRules none()
+    {
+        return NONE;
+    }
+
+    static InterceptRules index(Set<InterceptRule> rules)
+    {
+        if (rules.isEmpty())
+            return NONE;
+
+        Map<String, RuleOrRules> bySourceOwner = new HashMap<>();
+        for (InterceptRule rule : rules)
+        {
+            String sourceOwner = rule.sourceOwner();
+            RuleOrRules current = bySourceOwner.get(sourceOwner);
+            if (current == null)
+            {
+                bySourceOwner.put(sourceOwner, rule);
+            }
+            else if (current instanceof InterceptRule)
+            {
+                bySourceOwner.put(sourceOwner, new Rules((InterceptRule) 
current, rule));
+            }
+            else
+            {
+                ((Rules) current).add(rule);
+            }
+        }
+
+        for (RuleOrRules bucket : bySourceOwner.values())
+        {
+            if (bucket instanceof Rules)
+                ((Rules) bucket).trimToSize();
+        }
+        return new InterceptRules(Collections.unmodifiableMap(bySourceOwner));
+    }
+
+    boolean isEmpty()
+    {
+        return bySourceOwner.isEmpty();
+    }
+
+    RuleOrRules forSourceOwner(String sourceOwner)
+    {
+        return bySourceOwner.get(sourceOwner);
+    }
+
+
+    /**
+     * Allocation-free indexed traversal for the transformer hot path, with 
Iterable for callers
+     * that do not require allocation-free traversal.
+     */
+    public abstract static class RuleOrRules implements Iterable<InterceptRule>
+    {
+        abstract int size();
+
+        abstract InterceptRule get(int index);
+
+        @Override
+        public final Iterator<InterceptRule> iterator()
+        {
+            return new Iterator<>()
+            {
+                private int index;
+
+                @Override
+                public boolean hasNext()
+                {
+                    return index < size();
+                }
+
+                @Override
+                public InterceptRule next()
+                {
+                    if (!hasNext())
+                    {
+                        throw new NoSuchElementException();
+                    }
+                    return get(index++);
+                }
+            };
+        }
+    }
+
+    /**
+     * Represents the multiple-rules arm of an owner bucket. A single-rule 
bucket is represented by
+     * the InterceptRule itself and requires no wrapper allocation.
+     */
+    final static class Rules extends RuleOrRules
+    {
+        private InterceptRule[] rules;
+        private int size;
+
+        Rules(InterceptRule first, InterceptRule second)
+        {
+            rules = new InterceptRule[4];
+            rules[0] = first;
+            rules[1] = second;
+            size = 2;
+        }
+
+        void add(InterceptRule rule)
+        {
+            if (size == rules.length)
+                rules = Arrays.copyOf(rules, size * 2);
+            rules[size++] = rule;
+        }
+
+        void trimToSize()
+        {
+            if (size != rules.length)
+                rules = Arrays.copyOf(rules, size);
+        }
+
+        @Override
+        int size()
+        {
+            return size;
+        }
+
+        @Override
+        InterceptRule get(int index)
+        {
+            if (index < 0 || index >= size)
+                throw new IndexOutOfBoundsException(index);
+            return rules[index];
+        }
+    }
+}
\ No newline at end of file
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleConstructorTest.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleConstructorTest.java
new file mode 100644
index 0000000..dcb6ff4
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleConstructorTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.asm;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class InterceptRuleConstructorTest extends InterceptRuleTestSupport
+{
+    @Test
+    void matchesAllocationAndConstructorInvocation()
+    {
+        InterceptRule.Constructor rule = new 
InterceptRule.Constructor("example/Source", "example/Replacement");
+
+        assertEquals(InterceptRule.Kind.CONSTRUCTOR, rule.kind());
+        assertTrue(rule.match(Opcodes.NEW, "example/Source", null, null));
+        assertTrue(rule.match(Opcodes.INVOKESPECIAL, "example/Source", 
"<init>", "(I)V"));
+        assertFalse(rule.match(Opcodes.INVOKESPECIAL, "example/Source", 
"method", "()V"));
+    }
+
+    @Test
+    void rewritesEveryAllocationAndInitialization()
+    {
+        InterceptRule.Constructor rule = new 
InterceptRule.Constructor("example/Source", "example/Replacement");
+        RecordingVisitor output = transform(Set.of(rule));
+
+        output.transformer.visitTypeInsn(Opcodes.NEW, "example/Source");
+        output.transformer.visitMethodInsn(Opcodes.INVOKESPECIAL, 
"example/Source", "<init>", "(I)V", false);
+        output.transformer.visitTypeInsn(Opcodes.NEW, "example/Source");
+        output.transformer.visitMethodInsn(Opcodes.INVOKESPECIAL, 
"example/Source", "<init>", "()V", false);
+
+        assertEquals(List.of("type " + Opcodes.NEW + " example/Replacement",
+                             "method " + Opcodes.INVOKESPECIAL + " 
example/Replacement.<init>(I)V false",
+                             "type " + Opcodes.NEW + " example/Replacement",
+                             "method " + Opcodes.INVOKESPECIAL + " 
example/Replacement.<init>()V false"),
+                     output.events);
+    }
+}
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleFactoryMethodTest.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleFactoryMethodTest.java
new file mode 100644
index 0000000..d1cf45d
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleFactoryMethodTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.asm;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class InterceptRuleFactoryMethodTest extends InterceptRuleTestSupport
+{
+    @Test
+    void matchesOnlyTheConfiguredStaticInvocation()
+    {
+        InterceptRule.FactoryMethod rule = 
InterceptRule.FactoryMethod.make("example/Factory", "create", 
"(I)Lexample/Source;");
+
+        assertEquals(InterceptRule.Kind.FACTORY_METHOD, rule.kind());
+        assertTrue(rule.match(Opcodes.INVOKESTATIC, "example/Factory", 
"create", "(I)Lexample/Source;"));
+        assertFalse(rule.match(Opcodes.INVOKESTATIC, "example/Factory", 
"create", "()Lexample/Source;"));
+        assertFalse(rule.match(Opcodes.INVOKEVIRTUAL, "example/Factory", 
"create", "(I)Lexample/Source;"));
+    }
+
+    @Test
+    void emitsDispatcherAndReturnCast()
+    {
+        InterceptRule.FactoryMethod rule = 
InterceptRule.FactoryMethod.make("example/Factory", "create", 
"(I)Lexample/Source;");
+        RecordingVisitor output = transform(Set.of(rule));
+
+        output.transformer.visitMethodInsn(Opcodes.INVOKESTATIC,
+                                           "example/Factory",
+                                           "create",
+                                           "(I)Lexample/Source;",
+                                           false);
+
+        assertEquals(List.of("ldc example/Factory.create",
+                             "insn " + Opcodes.SWAP,
+                             "method " + Opcodes.INVOKESTATIC
+                             + " 
org/apache/cassandra/simulator/systems/InterceptorOfGlobalMethods$Global.dispatchIntArg"
+                             + "(Ljava/lang/String;I)Ljava/lang/Object; false",
+                             "type " + Opcodes.CHECKCAST + " example/Source"),
+                     output.events);
+    }
+}
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleMethodCallTest.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleMethodCallTest.java
new file mode 100644
index 0000000..6d6bc6c
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleMethodCallTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.asm;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class InterceptRuleMethodCallTest extends InterceptRuleTestSupport
+{
+    @Test
+    void matchesOnlyInstanceInvocations()
+    {
+        InterceptRule.MethodCall rule = new 
InterceptRule.MethodCall("example/Source", "add", "(I)I",
+                                                                     
"example/Redirect", "(Lexample/Source;I)I");
+
+        assertEquals(InterceptRule.Kind.METHOD_CALL, rule.kind());
+        assertTrue(rule.match(Opcodes.INVOKEVIRTUAL, "example/Source", "add", 
"(I)I"));
+        assertTrue(rule.match(Opcodes.INVOKEINTERFACE, "example/Source", 
"add", "(I)I"));
+        assertFalse(rule.match(Opcodes.INVOKESTATIC, "example/Source", "add", 
"(I)I"));
+    }
+
+    @Test
+    void requiresTheTargetDescriptorToPrependTheReceiver()
+    {
+        assertThrows(IllegalArgumentException.class,
+                     () -> new InterceptRule.MethodCall("example/Source", 
"add", "(I)I",
+                                                        "example/Redirect", 
"(I)I"));
+    }
+
+    @Test
+    void consumesOriginalReceiverAsFirstStaticArgument()
+    {
+        InterceptRule.MethodCall rule = new 
InterceptRule.MethodCall("example/Source", "add", "(I)I",
+                                                                     
"example/Redirect", "(Lexample/Source;I)I");
+        RecordingVisitor output = transform(Set.of(rule));
+
+        output.transformer.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
"example/Source", "add", "(I)I", false);
+
+        assertEquals(List.of("method " + Opcodes.INVOKESTATIC
+                             + " example/Redirect.add(Lexample/Source;I)I 
false"),
+                     output.events);
+    }
+}
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleStaticMethodCallTest.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleStaticMethodCallTest.java
new file mode 100644
index 0000000..577663d
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleStaticMethodCallTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.asm;
+
+import java.util.List;
+import java.util.Set;
+
+import org.junit.jupiter.api.Test;
+import org.objectweb.asm.Opcodes;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class InterceptRuleStaticMethodCallTest extends InterceptRuleTestSupport
+{
+    @Test
+    void matchesOnlyTheConfiguredStaticInvocation()
+    {
+        InterceptRule.StaticMethodCall rule =
+            new InterceptRule.StaticMethodCall("example/Source", "create", 
"(I)I", "example/Redirect");
+
+        assertEquals(InterceptRule.Kind.STATIC_METHOD_CALL, rule.kind());
+        assertTrue(rule.match(Opcodes.INVOKESTATIC, "example/Source", 
"create", "(I)I"));
+        assertFalse(rule.match(Opcodes.INVOKESTATIC, "example/Source", 
"other", "(I)I"));
+        assertFalse(rule.match(Opcodes.INVOKESTATIC, "example/Source", 
"create", "()I"));
+        assertFalse(rule.match(Opcodes.INVOKEVIRTUAL, "example/Source", 
"create", "(I)I"));
+    }
+
+    @Test
+    void changesOnlyTheOwner()
+    {
+        InterceptRule.StaticMethodCall rule =
+            new InterceptRule.StaticMethodCall("example/Source", "create", 
"(I)I", "example/Redirect");
+        RecordingVisitor output = transform(Set.of(rule));
+
+        output.transformer.visitMethodInsn(Opcodes.INVOKESTATIC, 
"example/Source", "create", "(I)I", false);
+
+        assertEquals(List.of("method " + Opcodes.INVOKESTATIC
+                             + " example/Redirect.create(I)I false"),
+                     output.events);
+    }
+}
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleTestSupport.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleTestSupport.java
new file mode 100644
index 0000000..0b30e27
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRuleTestSupport.java
@@ -0,0 +1,90 @@
+/*
+ * 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.asm;
+
+import java.util.ArrayList;
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import static org.apache.cassandra.simulator.asm.Flag.GLOBAL_METHODS;
+
+abstract class InterceptRuleTestSupport
+{
+    static RecordingVisitor transform(Set<InterceptRule> rules)
+    {
+        RecordingVisitor output = new RecordingVisitor();
+        InterceptRules indexedRules = InterceptRules.index(new 
LinkedHashSet<>(rules));
+        ClassTransformer classTransformer = new ClassTransformer(Opcodes.ASM9,
+                                                                 
"example/Caller",
+                                                                 
EnumSet.of(GLOBAL_METHODS),
+                                                                 null,
+                                                                 null,
+                                                                 null,
+                                                                 null,
+                                                                 indexedRules,
+                                                                 ignore -> {});
+        output.transformer = new 
GlobalMethodTransformer(EnumSet.of(GLOBAL_METHODS),
+                                                         classTransformer,
+                                                         Opcodes.ASM9,
+                                                         "call",
+                                                         output,
+                                                         indexedRules);
+        return output;
+    }
+
+    static final class RecordingVisitor extends MethodVisitor
+    {
+        final List<String> events = new ArrayList<>();
+        GlobalMethodTransformer transformer;
+
+        RecordingVisitor()
+        {
+            super(Opcodes.ASM9);
+        }
+
+        @Override
+        public void visitLdcInsn(Object value)
+        {
+            events.add("ldc " + value);
+        }
+
+        @Override
+        public void visitInsn(int opcode)
+        {
+            events.add("insn " + opcode);
+        }
+
+        @Override
+        public void visitTypeInsn(int opcode, String type)
+        {
+            events.add("type " + opcode + ' ' + type);
+        }
+
+        @Override
+        public void visitMethodInsn(int opcode, String owner, String name, 
String descriptor, boolean isInterface)
+        {
+            events.add("method " + opcode + ' ' + owner + '.' + name + 
descriptor + ' ' + isInterface);
+        }
+    }
+}
diff --git 
a/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRulesTest.java
 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRulesTest.java
new file mode 100644
index 0000000..1654651
--- /dev/null
+++ 
b/simulator-asm/src/test/java/org/apache/cassandra/simulator/asm/InterceptRulesTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.asm;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class InterceptRulesTest
+{
+    @Test
+    void usesRuleItselfForSingleRuleOwner()
+    {
+        InterceptRule rule = new InterceptRule.Constructor("example/Source", 
"example/Replacement");
+        InterceptRules rules = InterceptRules.index(new 
LinkedHashSet<>(List.of(rule)));
+
+        InterceptRuleBucket bucket = rules.forSourceOwner("example/Source");
+        assertSame(rule, bucket);
+        assertEquals(List.of(rule), copyOf(bucket));
+        assertNull(rules.forSourceOwner("example/Other"));
+    }
+
+    @Test
+    void usesOneArrayBackedBucketForMultipleRulesAndPreservesOrder()
+    {
+        InterceptRule first = 
InterceptRule.FactoryMethod.make("example/Factory", "create", 
"()Ljava/lang/Object;");
+        InterceptRule second = 
InterceptRule.FactoryMethod.make("example/Factory", "create", 
"(I)Ljava/lang/Object;");
+        InterceptRule third = new 
InterceptRule.StaticMethodCall("example/Factory", "reset", "()V", 
"example/Redirect");
+        InterceptRules rules = InterceptRules.index(new 
LinkedHashSet<>(List.of(first, second, third)));
+
+        InterceptRuleBucket bucket = rules.forSourceOwner("example/Factory");
+        assertTrue(bucket instanceof InterceptRuleArray);
+        assertEquals(List.of(first, second, third), copyOf(bucket));
+    }
+
+    private static List<InterceptRule> copyOf(InterceptRuleBucket bucket)
+    {
+        List<InterceptRule> rules = new ArrayList<>();
+        for (InterceptRule rule : bucket)
+            rules.add(rule);
+        return rules;
+    }
+}
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 7c8d918..2954e39 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
@@ -296,7 +296,6 @@ public class Simulator implements AutoCloseable
                                                                          
source.getName(),
                                                                          
methodDescriptor(source),
                                                                          
interceptor.getDeclaringClass().getName().replace('.', '/'),
-                                                                         
interceptor.getName(),
                                                                          
methodDescriptor(interceptor));
             customRules.remove(rule);
             customRules.add(rule);
diff --git 
a/simulator-core/src/test/java/com/example/MethodOverrideOutsideConfiguredPackagesTest.java
 
b/simulator-core/src/test/java/com/example/InterceptRuleMethodCallOutsideConfiguredPackagesTest.java
similarity index 96%
rename from 
simulator-core/src/test/java/com/example/MethodOverrideOutsideConfiguredPackagesTest.java
rename to 
simulator-core/src/test/java/com/example/InterceptRuleMethodCallOutsideConfiguredPackagesTest.java
index b5f4688..5e26557 100644
--- 
a/simulator-core/src/test/java/com/example/MethodOverrideOutsideConfiguredPackagesTest.java
+++ 
b/simulator-core/src/test/java/com/example/InterceptRuleMethodCallOutsideConfiguredPackagesTest.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
-public class MethodOverrideOutsideConfiguredPackagesTest
+public class InterceptRuleMethodCallOutsideConfiguredPackagesTest
 {
     @BeforeEach
     void reset()
diff --git 
a/simulator-core/src/test/java/org/apache/cassandra/simulator_test/MethodOverrideTest.java
 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/InterceptRuleMethodCallTest.java
similarity index 98%
rename from 
simulator-core/src/test/java/org/apache/cassandra/simulator_test/MethodOverrideTest.java
rename to 
simulator-core/src/test/java/org/apache/cassandra/simulator_test/InterceptRuleMethodCallTest.java
index 82c7deb..b6928ee 100644
--- 
a/simulator-core/src/test/java/org/apache/cassandra/simulator_test/MethodOverrideTest.java
+++ 
b/simulator-core/src/test/java/org/apache/cassandra/simulator_test/InterceptRuleMethodCallTest.java
@@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
-public class MethodOverrideTest
+public class InterceptRuleMethodCallTest
 {
     @BeforeEach
     void reset()


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

Reply via email to