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]
