diff --git a/byte-buddy-dep/pom.xml b/byte-buddy-dep/pom.xml index 8b31076df8..4d78bbec20 100644 --- a/byte-buddy-dep/pom.xml +++ b/byte-buddy-dep/pom.xml @@ -226,8 +226,7 @@ ${version.plugin.surefire} - net.bytebuddy.agent.builder.AgentBuilderDefaultApplicationTest - net.bytebuddy.pool.TypePoolDefaultAnnotationDescriptionTest + net.bytebuddy.asm.MemberSubstitution diff --git a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java index 374f8d2651..953d565801 100644 --- a/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java +++ b/byte-buddy-dep/src/main/java/net/bytebuddy/asm/MemberSubstitution.java @@ -94,15 +94,20 @@ public class MemberSubstitution implements AsmVisitorWrapper.ForDeclaredMethods. */ private final MethodGraph.Compiler methodGraphCompiler; + /** + * The type pool resolver to use. + */ + private final TypePoolResolver typePoolResolver; + /** * {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ private final boolean strict; /** - * The type pool resolver to use. + * {@code true} if the instrumentation should fail if applied to a method without match. */ - private final TypePoolResolver typePoolResolver; + private final boolean failIfNoMatch; /** * The replacement factory to use. @@ -115,7 +120,7 @@ public class MemberSubstitution implements AsmVisitorWrapper.ForDeclaredMethods. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. */ protected MemberSubstitution(boolean strict) { - this(MethodGraph.Compiler.DEFAULT, TypePoolResolver.OfImplicitPool.INSTANCE, strict, Replacement.NoOp.INSTANCE); + this(MethodGraph.Compiler.DEFAULT, TypePoolResolver.OfImplicitPool.INSTANCE, strict, false, Replacement.NoOp.INSTANCE); } /** @@ -124,11 +129,17 @@ protected MemberSubstitution(boolean strict) { * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. */ - protected MemberSubstitution(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory) { + protected MemberSubstitution(MethodGraph.Compiler methodGraphCompiler, + TypePoolResolver typePoolResolver, + boolean strict, + boolean failIfNoMatch, + Replacement.Factory replacementFactory) { this.methodGraphCompiler = methodGraphCompiler; this.typePoolResolver = typePoolResolver; + this.failIfNoMatch = failIfNoMatch; this.strict = strict; this.replacementFactory = replacementFactory; } @@ -161,7 +172,7 @@ public static MemberSubstitution relaxed() { * @return A specification that allows to determine how to substitute any interaction with byte code elements that match the supplied matcher. */ public WithoutSpecification element(ElementMatcher matcher) { - return new WithoutSpecification.ForMatchedByteCodeElement(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); + return new WithoutSpecification.ForMatchedByteCodeElement(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher); } /** @@ -171,7 +182,7 @@ public WithoutSpecification element(ElementMatcher matcher) { - return new WithoutSpecification.ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); + return new WithoutSpecification.ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher); } /** @@ -181,7 +192,7 @@ public WithoutSpecification.ForMatchedField field(ElementMatcher matcher) { - return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); + return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher); } /** @@ -201,7 +212,7 @@ public WithoutSpecification constructor(ElementMatcher matcher) { - return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher); + return new WithoutSpecification.ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher); } /** @@ -211,7 +222,7 @@ public WithoutSpecification invokable(ElementMatcher * @return A new member substitution that is equal to this but uses the specified method graph compiler. */ public MemberSubstitution with(MethodGraph.Compiler methodGraphCompiler) { - return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, replacementFactory); + return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); } /** @@ -221,7 +232,16 @@ public MemberSubstitution with(MethodGraph.Compiler methodGraphCompiler) { * @return A new instance of this member substitution that uses the supplied type pool resolver. */ public MemberSubstitution with(TypePoolResolver typePoolResolver) { - return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, replacementFactory); + return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); + } + + /** + * Specifies if this substitution should fail if applied on a method without a match. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. + * @return A new instance of this member substitution that fails if applied on a method without a match. + */ + public MemberSubstitution failIfNoMatch(boolean failIfNoMatch) { + return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); } /** @@ -250,6 +270,7 @@ public MethodVisitor wrap(TypeDescription instrumentedType, instrumentedMethod, methodGraphCompiler, strict, + failIfNoMatch, replacementFactory.make(instrumentedType, instrumentedMethod, typePool), implementationContext, typePool, @@ -277,6 +298,11 @@ public abstract static class WithoutSpecification { */ protected final boolean strict; + /** + * {@code true} if the instrumentation should fail if applied to a method without match. + */ + protected final boolean failIfNoMatch; + /** * The replacement factory to use for creating substitutions. */ @@ -288,12 +314,18 @@ public abstract static class WithoutSpecification { * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use for creating substitutions. */ - protected WithoutSpecification(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, Replacement.Factory replacementFactory) { + protected WithoutSpecification(MethodGraph.Compiler methodGraphCompiler, + TypePoolResolver typePoolResolver, + boolean strict, + boolean failIfNoMatch, + Replacement.Factory replacementFactory) { this.methodGraphCompiler = methodGraphCompiler; this.typePoolResolver = typePoolResolver; this.strict = strict; + this.failIfNoMatch = failIfNoMatch; this.replacementFactory = replacementFactory; } @@ -495,15 +527,17 @@ protected static class ForMatchedByteCodeElement extends WithoutSpecification { * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any byte code elements that should be substituted. */ protected ForMatchedByteCodeElement(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, + boolean failIfNoMatch, Replacement.Factory replacementFactory, ElementMatcher matcher) { - super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); + super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); this.matcher = matcher; } @@ -514,6 +548,7 @@ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, + failIfNoMatch, new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.of(matcher, substitutionFactory))); } } @@ -545,15 +580,17 @@ public static class ForMatchedField extends WithoutSpecification { * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any field that should be substituted. */ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, + boolean failIfNoMatch, Replacement.Factory replacementFactory, ElementMatcher matcher) { - this(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, true); + this(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, true); } /** @@ -562,6 +599,7 @@ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any field that should be substituted. * @param matchRead {@code true} if read access to a field should be substituted. @@ -570,11 +608,12 @@ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, + boolean failIfNoMatch, Replacement.Factory replacementFactory, ElementMatcher matcher, boolean matchRead, boolean matchWrite) { - super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); + super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); this.matcher = matcher; this.matchRead = matchRead; this.matchWrite = matchWrite; @@ -586,7 +625,7 @@ protected ForMatchedField(MethodGraph.Compiler methodGraphCompiler, * @return This instance with the limitation that only read access to the matched field is substituted. */ public WithoutSpecification onRead() { - return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, false); + return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, false); } /** @@ -595,7 +634,7 @@ public WithoutSpecification onRead() { * @return This instance with the limitation that only write access to the matched field is substituted. */ public WithoutSpecification onWrite() { - return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, false, true); + return new ForMatchedField(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, false, true); } /** @@ -605,6 +644,7 @@ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, + failIfNoMatch, new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.ofField(matcher, matchRead, matchWrite, substitutionFactory))); } } @@ -636,15 +676,17 @@ public static class ForMatchedMethod extends WithoutSpecification { * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any method or constructor that should be substituted. */ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, + boolean failIfNoMatch, Replacement.Factory replacementFactory, ElementMatcher matcher) { - this(methodGraphCompiler, typePoolResolver, strict, replacementFactory, matcher, true, true); + this(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory, matcher, true, true); } /** @@ -653,6 +695,7 @@ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, * @param methodGraphCompiler The method graph compiler to use. * @param typePoolResolver The type pool resolver to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacementFactory The replacement factory to use. * @param matcher A matcher for any method or constructor that should be substituted. * @param includeVirtualCalls {@code true} if this specification includes virtual invocations. @@ -661,11 +704,12 @@ protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, protected ForMatchedMethod(MethodGraph.Compiler methodGraphCompiler, TypePoolResolver typePoolResolver, boolean strict, + boolean failIfNoMatch, Replacement.Factory replacementFactory, ElementMatcher matcher, boolean includeVirtualCalls, boolean includeSuperCalls) { - super(methodGraphCompiler, typePoolResolver, strict, replacementFactory); + super(methodGraphCompiler, typePoolResolver, strict, failIfNoMatch, replacementFactory); this.matcher = matcher; this.includeVirtualCalls = includeVirtualCalls; this.includeSuperCalls = includeSuperCalls; @@ -680,6 +724,7 @@ public WithoutSpecification onVirtualCall() { return new ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, + failIfNoMatch, replacementFactory, isVirtual().and(matcher), true, @@ -695,6 +740,7 @@ public WithoutSpecification onSuperCall() { return new ForMatchedMethod(methodGraphCompiler, typePoolResolver, strict, + failIfNoMatch, replacementFactory, isVirtual().and(matcher), false, @@ -708,6 +754,7 @@ public MemberSubstitution replaceWith(Substitution.Factory substitutionFactory) return new MemberSubstitution(methodGraphCompiler, typePoolResolver, strict, + failIfNoMatch, new Replacement.Factory.Compound(this.replacementFactory, Replacement.ForElementMatchers.Factory.ofMethod(matcher, includeVirtualCalls, includeSuperCalls, substitutionFactory))); } } @@ -6986,6 +7033,11 @@ protected static class SubstitutingMethodVisitor extends LocalVariableAwareMetho */ private final boolean strict; + /** + * {@code true} if the instrumentation should fail if applied to a method without match. + */ + private final boolean failIfNoMatch; + /** * The replacement to use for creating substitutions. */ @@ -7016,6 +7068,8 @@ protected static class SubstitutingMethodVisitor extends LocalVariableAwareMetho */ private int localVariableExtension; + private boolean matched; + /** * Creates a new substituting method visitor. * @@ -7024,6 +7078,7 @@ protected static class SubstitutingMethodVisitor extends LocalVariableAwareMetho * @param instrumentedMethod The instrumented method. * @param methodGraphCompiler The method graph compiler to use. * @param strict {@code true} if the method processing should be strict where an exception is raised if a member cannot be found. + * @param failIfNoMatch {@code true} if the instrumentation should fail if applied to a method without match. * @param replacement The replacement to use for creating substitutions. * @param implementationContext The implementation context to use. * @param typePool The type pool to use. @@ -7034,6 +7089,7 @@ protected SubstitutingMethodVisitor(MethodVisitor methodVisitor, MethodDescription instrumentedMethod, MethodGraph.Compiler methodGraphCompiler, boolean strict, + boolean failIfNoMatch, Replacement replacement, Implementation.Context implementationContext, TypePool typePool, @@ -7043,6 +7099,7 @@ protected SubstitutingMethodVisitor(MethodVisitor methodVisitor, this.instrumentedMethod = instrumentedMethod; this.methodGraphCompiler = methodGraphCompiler; this.strict = strict; + this.failIfNoMatch = failIfNoMatch; this.replacement = replacement; this.implementationContext = implementationContext; this.typePool = typePool; @@ -7097,6 +7154,7 @@ public void visitFieldInsn(int opcode, String owner, String internalName, String ? FieldAccess.forField(candidates.getOnly()).read() : FieldAccess.forField(candidates.getOnly()).write(), getFreeOffset()).apply(new LocalVariableTracingMethodVisitor(mv), implementationContext).getMaximalSize()); + matched = true; return; } } else if (strict) { @@ -7166,6 +7224,7 @@ public void visitMethodInsn(int opcode, String owner, String internalName, Strin } else { stackSizeBuffer = Math.max(stackSizeBuffer, size.getMaximalSize()); } + matched = true; return; } } else if (strict) { @@ -7179,6 +7238,9 @@ public void visitMethodInsn(int opcode, String owner, String internalName, Strin @Override public void visitMaxs(int stackSize, int localVariableLength) { + if (failIfNoMatch && !matched) { + throw new IllegalStateException("No substitution found within " + instrumentedMethod + " of " + instrumentedType); + } super.visitMaxs(stackSize + stackSizeBuffer, Math.max(localVariableExtension, localVariableLength)); } @@ -7215,7 +7277,6 @@ public void visitVarInsn(int opcode, int offset) { } } - /** *

* Indicates that the annotated parameter should be mapped to the {@code this} reference of the substituted field, diff --git a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java index 439821aec5..c57d7dd16e 100644 --- a/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java +++ b/byte-buddy-dep/src/test/java/net/bytebuddy/asm/MemberSubstitutionTest.java @@ -966,6 +966,14 @@ public void testSubstitutionChainTypeAssignmentExplicit() throws Exception{ assertThat(type.getDeclaredField(BAR).get(instance), is((Object) BAZ)); } + @Test(expected = IllegalStateException.class) + public void testThrowExceptionIfNoMatch() throws Exception { + new ByteBuddy() + .redefine(FieldAccessSample.class) + .visit(MemberSubstitution.strict().failIfNoMatch(true).field(named(BAZ)).stub().on(named(RUN))) + .make(); + } + @Test(expected = IllegalStateException.class) public void testFieldNotAccessible() throws Exception { new ByteBuddy()