Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #660 #930

Merged
merged 2 commits into from
Oct 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 68 additions & 6 deletions byte-buddy-dep/src/main/java/net/bytebuddy/asm/Advice.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import net.bytebuddy.dynamic.TargetType;
import net.bytebuddy.dynamic.scaffold.FieldLocator;
import net.bytebuddy.dynamic.scaffold.InstrumentedType;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bytecode.*;
Expand Down Expand Up @@ -62,6 +63,8 @@
import java.util.*;

import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith;
import static net.bytebuddy.matcher.ElementMatchers.isGetter;
import static net.bytebuddy.matcher.ElementMatchers.isSetter;
import static net.bytebuddy.matcher.ElementMatchers.named;

/**
Expand Down Expand Up @@ -2132,7 +2135,7 @@ public Target resolve(TypeDescription instrumentedType,
Assigner assigner,
ArgumentHandler argumentHandler,
Sort sort) {
FieldDescription fieldDescription = resolve(instrumentedType);
FieldDescription fieldDescription = resolve(instrumentedType, instrumentedMethod);
if (!fieldDescription.isStatic() && instrumentedMethod.isStatic()) {
throw new IllegalStateException("Cannot read non-static field " + fieldDescription + " from static method " + instrumentedMethod);
} else if (sort.isPremature(instrumentedMethod) && !fieldDescription.isStatic()) {
Expand All @@ -2156,16 +2159,22 @@ public Target resolve(TypeDescription instrumentedType,
* Resolves the field being bound.
*
* @param instrumentedType The instrumented type.
* @param instrumentedMethod The instrumented method.
* @return The field being bound.
*/
protected abstract FieldDescription resolve(TypeDescription instrumentedType);
protected abstract FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod);

/**
* An offset mapping for a field that is resolved from the instrumented type by its name.
*/
@HashCodeAndEqualsPlugin.Enhance
public abstract static class Unresolved extends ForField {

/**
* Indicates that a name should be extracted from an accessor method.
*/
protected static final String BEAN_PROPERTY = "";

/**
* The name of the field.
*/
Expand All @@ -2185,15 +2194,37 @@ public Unresolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typ
}

@Override
protected FieldDescription resolve(TypeDescription instrumentedType) {
FieldLocator.Resolution resolution = fieldLocator(instrumentedType).locate(name);
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
FieldLocator locator = fieldLocator(instrumentedType);
FieldLocator.Resolution resolution = name.equals(BEAN_PROPERTY)
? resolveAccessor(locator, instrumentedMethod)
: locator.locate(name);
if (!resolution.isResolved()) {
throw new IllegalStateException("Cannot locate field named " + name + " for " + instrumentedType);
} else {
return resolution.getField();
}
}

/**
* Resolves a field locator for a potential accessor method.
*
* @param fieldLocator The field locator to use.
* @param methodDescription The method description that is the potential accessor.
* @return A resolution for a field locator.
*/
private static FieldLocator.Resolution resolveAccessor(FieldLocator fieldLocator, MethodDescription methodDescription) {
String fieldName;
if (isSetter().matches(methodDescription)) {
fieldName = methodDescription.getInternalName().substring(3);
} else if (isGetter().matches(methodDescription)) {
fieldName = methodDescription.getInternalName().substring(methodDescription.getInternalName().startsWith("is") ? 2 : 3);
} else {
return FieldLocator.Resolution.Illegal.INSTANCE;
}
return fieldLocator.locate(Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1));
}

/**
* Returns a field locator for this instance.
*
Expand Down Expand Up @@ -2353,7 +2384,7 @@ public Resolved(TypeDescription.Generic target, boolean readOnly, Assigner.Typin
}

@Override
protected FieldDescription resolve(TypeDescription instrumentedType) {
protected FieldDescription resolve(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
if (!fieldDescription.isStatic() && !fieldDescription.getDeclaringType().asErasure().isAssignableFrom(instrumentedType)) {
throw new IllegalStateException(fieldDescription + " is no member of " + instrumentedType);
} else if (!fieldDescription.isAccessibleTo(instrumentedType)) {
Expand Down Expand Up @@ -2501,6 +2532,9 @@ public static OffsetMapping parse(String pattern) {
case Renderer.ForJavaSignature.SYMBOL:
renderers.add(Renderer.ForJavaSignature.INSTANCE);
break;
case Renderer.ForPropertyName.SYMBOL:
renderers.add(Renderer.ForPropertyName.INSTANCE);
break;
default:
throw new IllegalStateException("Illegal sort descriptor " + pattern.charAt(to + 1) + " for " + pattern);
}
Expand Down Expand Up @@ -2710,6 +2744,29 @@ public String apply(TypeDescription instrumentedType, MethodDescription instrume
return value;
}
}

/**
* A renderer for a property name.
*/
enum ForPropertyName implements Renderer {

/**
* The singleton instance.
*/
INSTANCE;

/**
* The signature symbol.
*/
public static final char SYMBOL = 'p';

/**
* {@inheritDoc}
*/
public String apply(TypeDescription instrumentedType, MethodDescription instrumentedMethod) {
return FieldAccessor.FieldNameExtractor.ForBeanProperty.INSTANCE.resolve(instrumentedMethod);
}
}
}

/**
Expand Down Expand Up @@ -10871,6 +10928,10 @@ public void visitEnd() {
* Indicates that the annotated parameter should be mapped to a field in the scope of the instrumented method.
* </p>
* <p>
* Setting {@link FieldValue#value()} is optional. If the value is not set, the field value attempts to bind a setter's
* or getter's field if the intercepted method is an accessor method. Otherwise, the binding renders the target method
* to be an illegal candidate for binding.
* </p>
* <b>Important</b>: Parameters with this option must not be used when from a constructor in combination with
* {@link OnMethodEnter} and a non-static field where the {@code this} reference is not available.
* </p>
Expand All @@ -10895,7 +10956,7 @@ public void visitEnd() {
*
* @return The name of the field.
*/
String value();
String value() default OffsetMapping.ForField.Unresolved.BEAN_PROPERTY;

/**
* Returns the type that declares the field that should be mapped to the annotated parameter. If this property
Expand Down Expand Up @@ -10963,6 +11024,7 @@ public void visitEnd() {
* <li>{@code #d} for the method's descriptor.</li>
* <li>{@code #s} for the method's signature.</li>
* <li>{@code #r} for the method's return type.</li>
* <li>{@code #p} for the property's name.</li>
* </ul>
* Any other {@code #} character must be escaped by {@code \} which can be escaped by itself. This property is ignored if the annotated
* parameter is of type {@link Class}.
Expand Down
54 changes: 52 additions & 2 deletions byte-buddy-dep/src/test/java/net/bytebuddy/asm/AdviceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import net.bytebuddy.implementation.bytecode.assign.Assigner;
import net.bytebuddy.implementation.bytecode.constant.ClassConstant;
import net.bytebuddy.implementation.bytecode.member.MethodVariableAccess;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.pool.TypePool;
import net.bytebuddy.test.packaging.AdviceTestHelper;
import net.bytebuddy.test.utility.JavaVersionRule;
Expand All @@ -39,9 +40,15 @@
import java.util.Map;

import static junit.framework.TestCase.fail;
import static net.bytebuddy.matcher.ElementMatchers.*;
import static net.bytebuddy.matcher.ElementMatchers.isConstructor;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.*;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
Expand Down Expand Up @@ -941,6 +948,18 @@ public void testIllegalReturnValueSubstitution() throws Exception {
.make();
}

@Test
public void testFieldAdviceBean() throws Exception {
Class<?> type = new ByteBuddy()
.redefine(Bean.class)
.visit(Advice.to(FieldAdviceBean.class).on(ElementMatchers.isSetter()))
.make()
.load(ClassLoadingStrategy.BOOTSTRAP_LOADER, ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
type.getDeclaredMethod("setFoo", String.class).invoke(type.getDeclaredConstructor().newInstance(), BAR);
assertThat(type.getDeclaredField(ENTER).get(null), is((Object) 1));
}

@Test
public void testFieldAdviceImplicit() throws Exception {
Class<?> type = new ByteBuddy()
Expand Down Expand Up @@ -2697,6 +2716,37 @@ public static String bar() {
}
}

@SuppressWarnings("unused")
public static class Bean {

public static int enter;

private String foo;

public String getFoo() {
return foo;
}

public void setFoo(String foo) {
this.foo = foo;
}
}

@SuppressWarnings("unused")
public static class FieldAdviceBean {

@Advice.OnMethodExit
private static void enter(@Advice.FieldValue String propertyValue, @Advice.Origin("#p") String propertyName) {
Bean.enter++;
if (!propertyValue.equals(BAR)) {
throw new AssertionError();
}
if (!propertyName.equals(FOO)) {
throw new AssertionError();
}
}
}

@SuppressWarnings("unused")
public static class FieldAdviceImplicit {

Expand Down