Skip to content

Commit

Permalink
finish testing for everything implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Machine-Maker committed Jun 20, 2024
1 parent b57125f commit 5923b23
Show file tree
Hide file tree
Showing 38 changed files with 670 additions and 122 deletions.
15 changes: 2 additions & 13 deletions src/main/java/io/papermc/asm/rules/builder/RuleFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.lang.reflect.Method;
import java.util.Set;
import java.util.function.Consumer;
import org.checkerframework.checker.nullness.qual.Nullable;

import static io.papermc.asm.util.DescriptorUtils.desc;

Expand Down Expand Up @@ -57,31 +58,19 @@ default void changeReturnTypeToSub(final Class<?> oldReturnType, final Class<?>

void changeReturnTypeToSub(ClassDesc oldReturnType, ClassDesc newReturnType, Consumer<? super MethodMatcher.Builder> builderConsumer);

default void changeReturnTypeFuzzy(final Class<?> newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.changeReturnTypeFuzzy(desc(newReturnType), staticHandler, builderConsumer);
}

void changeReturnTypeFuzzy(ClassDesc newReturnType, Method staticHandler, Consumer<? super TargetedMethodMatcher.Builder> builderConsumer);

default void changeReturnTypeDirect(final Class<?> newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.changeReturnTypeDirect(desc(newReturnType), staticHandler, builderConsumer);
}

void changeReturnTypeDirect(ClassDesc newReturnType, Method staticHandler, Consumer<? super TargetedMethodMatcher.Builder> builderConsumer);

default void changeReturnTypeFuzzyWithContext(final Class<?> newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.changeReturnTypeFuzzyWithContext(desc(newReturnType), staticHandler, builderConsumer);
}

void changeReturnTypeFuzzyWithContext(ClassDesc newReturnType, Method staticHandler, Consumer<? super TargetedMethodMatcher.Builder> builderConsumer);

default void changeReturnTypeDirectWithContext(final Class<?> newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.changeReturnTypeDirectWithContext(desc(newReturnType), staticHandler, builderConsumer);
}

void changeReturnTypeDirectWithContext(ClassDesc newReturnType, Method staticHandler, Consumer<? super TargetedMethodMatcher.Builder> builderConsumer);

void renameField(String newName, Consumer<? super FieldMatcher.Builder> builderConsumer);
void changeFieldToMethod(FieldMatcher fieldMatcher, @Nullable String getterName, @Nullable String setterName, boolean isInterfaceMethod);

void addRule(RewriteRule rule);

Expand Down
26 changes: 8 additions & 18 deletions src/main/java/io/papermc/asm/rules/builder/RuleFactoryImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.checkerframework.checker.nullness.qual.Nullable;

class RuleFactoryImpl implements RuleFactory {

Expand Down Expand Up @@ -57,31 +58,24 @@ public void changeReturnTypeToSub(final ClassDesc oldReturnType, final ClassDesc
this.addRule(new MethodRewrites.SubTypeReturn(this.owners, build(builderConsumer, MethodMatcher::builder), oldReturnType, newReturnType));
}

@Override
public void changeReturnTypeFuzzy(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.addRule(StaticRewrites.returnRewrite(this.owners, newReturnType, build(builderConsumer, MethodMatcher::targeted), isStatic(staticHandler), StaticRewrites.OBJECT_DESC, false));
}

@Override
public void changeReturnTypeDirect(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
final TargetedMethodMatcher matcher = build(builderConsumer, MethodMatcher::targeted);
this.addRule(StaticRewrites.returnRewrite(this.owners, newReturnType, matcher, isStatic(staticHandler), matcher.targetType(), false));
this.changeReturnTypeDirect(newReturnType, staticHandler, builderConsumer, false);
}

@Override
public void changeReturnTypeFuzzyWithContext(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.addRule(StaticRewrites.returnRewrite(this.owners, newReturnType, build(builderConsumer, MethodMatcher::targeted), isStatic(staticHandler), StaticRewrites.OBJECT_DESC, true));
public void changeReturnTypeDirectWithContext(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
this.changeReturnTypeDirect(newReturnType, staticHandler, builderConsumer, true);
}

@Override
public void changeReturnTypeDirectWithContext(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer) {
private void changeReturnTypeDirect(final ClassDesc newReturnType, final Method staticHandler, final Consumer<? super TargetedMethodMatcher.Builder> builderConsumer, final boolean includeOwnerContext) {
final TargetedMethodMatcher matcher = build(builderConsumer, MethodMatcher::targeted);
this.addRule(StaticRewrites.returnRewrite(this.owners, newReturnType, matcher, isStatic(staticHandler), matcher.targetType(), true));
this.addRule(new StaticRewrites.DirectReturn(this.owners, newReturnType, matcher, isStatic(staticHandler), includeOwnerContext));
}

@Override
public void renameField(final String newName, final Consumer<? super FieldMatcher.Builder> builderConsumer) {
this.addRule(new FieldRewrites.Rename(this.owners, build(builderConsumer, FieldMatcher::builder), newName));
public void changeFieldToMethod(final FieldMatcher fieldMatcher, final @Nullable String getterName, final @Nullable String setterName, final boolean isInterfaceMethod) {
this.addRule(new FieldRewrites.ToMethodSameOwner(this.owners, fieldMatcher, getterName, setterName, isInterfaceMethod));
}

@Override
Expand All @@ -96,10 +90,6 @@ private static Method isStatic(final Method staticHandler) {
return staticHandler;
}

private static Supplier<ClassDesc> convert(final Supplier<Class<?>> classSupplier) {
return () -> classSupplier.get().describeConstable().orElseThrow();
}

@Override
public RewriteRule build() {
if (this.rules.size() == 1) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.papermc.asm.rules.builder.matcher;

import io.papermc.asm.rules.NameAndDescPredicate;
import io.papermc.asm.rules.method.StaticRewrite;
import java.lang.constant.MethodTypeDesc;
import java.util.Arrays;
import java.util.Collection;
Expand Down Expand Up @@ -54,6 +55,10 @@ public Builder build() {
}
}

public MatchBuilder ctor() {
return this.match(StaticRewrite.CONSTRUCTOR_METHOD_NAME);
}

public MatchBuilder match(final String name) {
return this.match(Collections.singleton(name));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.papermc.asm.rules.builder.matcher;

import io.papermc.asm.rules.method.StaticRewrite;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.Collection;
Expand All @@ -22,6 +23,10 @@ class Builder implements io.papermc.asm.util.Builder<TargetedMethodMatcher> {
Builder() {
}

public Builder ctor() {
return this.names(StaticRewrite.CONSTRUCTOR_METHOD_NAME);
}

public Builder names(final String... names) {
return this.names(List.of(names));
}
Expand Down
24 changes: 1 addition & 23 deletions src/main/java/io/papermc/asm/rules/field/FieldRewriteRule.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import io.papermc.asm.ClassProcessingContext;
import io.papermc.asm.rules.RewriteRule;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
Expand Down Expand Up @@ -38,30 +37,9 @@ public void visitFieldInsn(final int opcode, final String owner, final String na
};
}

@Nullable Rewrite rewrite(ClassProcessingContext context, int opcode, String owner, String name, ClassDesc desc);
@Nullable Rewrite rewrite(ClassProcessingContext context, int opcode, String owner, String name, ClassDesc fieldTypeDesc);

interface Rewrite {
void apply(MethodVisitor delegate);
}

record RewriteField(int opcode, String owner, String name, ClassDesc descriptor) implements Rewrite {

@Override
public void apply(final MethodVisitor delegate) {
delegate.visitFieldInsn(this.opcode(), this.owner(), this.name(), this.descriptor().descriptorString());
}
}

record RewriteToMethod(int opcode, String owner, String name, MethodTypeDesc descriptor, boolean isInterface) implements Rewrite {
public RewriteToMethod {
if (descriptor.parameterCount() != 0 && descriptor.parameterCount() != 1) {
throw new IllegalArgumentException("Can only have 0 or 1 param on a method replacing a field");
}
}

@Override
public void apply(final MethodVisitor delegate) {
delegate.visitMethodInsn(this.opcode(), this.owner(), this.name(), this.descriptor().descriptorString(), this.isInterface());
}
}
}
72 changes: 65 additions & 7 deletions src/main/java/io/papermc/asm/rules/field/FieldRewrites.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,81 @@
import io.papermc.asm.ClassProcessingContext;
import io.papermc.asm.rules.builder.matcher.FieldMatcher;
import java.lang.constant.ClassDesc;
import java.lang.constant.MethodTypeDesc;
import java.util.Set;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.objectweb.asm.Opcodes;

public final class FieldRewrites {

private static final ClassDesc VOID = void.class.describeConstable().orElseThrow();

private FieldRewrites() {
}

// Keep in mind that you have to include all subtype owners as well as the field could be referenced via them as well
public record Rename(Set<Class<?>> owners, FieldMatcher fieldMatcher, String newName) implements FilteredFieldRewriteRule {
public record ToMethodSameOwner(Set<Class<?>> owners, FieldMatcher fieldMatcher, @Nullable String getterName, @Nullable String setterName, boolean isInterfaceMethod) implements FilteredFieldRewriteRule {

@Override
public @Nullable Rewrite rewrite(final ClassProcessingContext context, final int opcode, final String owner, final String name, final ClassDesc desc) {
if (!name.equals(this.newName())) {
return new RewriteField(opcode, owner, this.newName(), desc);
public ToMethodSameOwner {
if (getterName == null && setterName == null) {
throw new IllegalArgumentException("At least one of getterName or setterName must be non-null");
}
return null;
}

enum Type {
GETTER {
@Override
int opcode(final int fieldOpcode) {
return switch (fieldOpcode) {
case Opcodes.GETFIELD -> Opcodes.INVOKEVIRTUAL;
case Opcodes.GETSTATIC -> Opcodes.INVOKESTATIC;
default -> throw new IllegalArgumentException("Unexpected opcode: " + fieldOpcode);
};
}

@Override
MethodTypeDesc desc(final ClassDesc fieldTypeDesc) {
return MethodTypeDesc.of(fieldTypeDesc);
}
},
SETTER {
@Override
int opcode(final int fieldOpcode) {
return switch (fieldOpcode) {
case Opcodes.PUTFIELD -> Opcodes.INVOKEVIRTUAL;
case Opcodes.PUTSTATIC -> Opcodes.INVOKESTATIC;
default -> throw new IllegalArgumentException("Unexpected opcode: " + fieldOpcode);
};
}

@Override
MethodTypeDesc desc(final ClassDesc fieldTypeDesc) {
return MethodTypeDesc.of(VOID, fieldTypeDesc);
}
};

abstract int opcode(final int fieldOpcode);

abstract MethodTypeDesc desc(final ClassDesc fieldTypeDesc);
}

@Override
public Rewrite rewrite(final ClassProcessingContext context, final int opcode, final String owner, final String name, final ClassDesc fieldTypeDesc) {
return (delegate) -> {
final Type type = switch (opcode) {
case Opcodes.GETFIELD, Opcodes.GETSTATIC -> Type.GETTER;
case Opcodes.PUTFIELD, Opcodes.PUTSTATIC -> Type.SETTER;
default -> throw new IllegalArgumentException("Unexpected opcode: " + opcode);
};
final @Nullable String methodName = switch (type) {
case GETTER -> this.getterName;
case SETTER -> this.setterName;
};
if (methodName == null) {
return;
}

delegate.visitMethodInsn(type.opcode(opcode), owner, methodName, type.desc(fieldTypeDesc).descriptorString(), this.isInterfaceMethod);
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

public interface GeneratedMethodSource<C> extends GeneratedMethodHolder {

ClassDesc VOID = Void.TYPE.describeConstable().orElseThrow();

/**
* Transforms the descriptor of the generated method to the descriptor
* of the method that will be called within the generated method. This
Expand All @@ -37,12 +39,13 @@ default void generateConstructor(final RewriteRule.GeneratorAdapterFactory facto
final ClassDesc methodOwner = original.owner();
final C context = this.createNewContext();
final GeneratorAdapter methodGenerator = factory.create(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC | Opcodes.ACC_STATIC, modified.name(), modified.descriptor().descriptorString());
final MethodTypeDesc transformedInvokedDescriptor = this.transformInvokedDescriptor(original.descriptor(), context);
final MethodTypeDesc transformedInvokedDescriptor = this.transformInvokedDescriptor(modified.descriptor(), context);
final Type type = Type.getType(methodOwner.descriptorString());
methodGenerator.newInstance(type);
methodGenerator.dup();
this.generateParameters(methodGenerator, modified.descriptor(), context);
methodGenerator.invokeConstructor(type, new Method(StaticRewrite.CONSTRUCTOR_METHOD_NAME, transformedInvokedDescriptor.descriptorString()));
// change return type to VOID because we are calling a constructor
methodGenerator.invokeConstructor(type, new Method(StaticRewrite.CONSTRUCTOR_METHOD_NAME, transformedInvokedDescriptor.changeReturnType(VOID).descriptorString()));
this.generateReturnValue(methodGenerator, original);
methodGenerator.endMethod();
}
Expand Down
37 changes: 19 additions & 18 deletions src/main/java/io/papermc/asm/rules/method/StaticRewrites.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.lang.reflect.Method;
import java.util.Set;

import static io.papermc.asm.util.DescriptorUtils.desc;
import static io.papermc.asm.util.DescriptorUtils.replaceParameters;
import static java.util.function.Predicate.isEqual;

Expand Down Expand Up @@ -48,27 +49,27 @@ public MethodTypeDesc transformInvokedDescriptor(final MethodTypeDesc original,
public record DirectParam(Set<Class<?>> owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler) implements StaticRewrite.Generated.Param {
}

public static StaticRewrite.Generated.Return returnRewrite(final Set<Class<?>> owners, final ClassDesc existingType, final TargetedMethodMatcher methodMatcher, final Method staticHandler, final ClassDesc intermediateType, final boolean includeOwnerContext) {
if (includeOwnerContext && owners.size() > 1) {
throw new IllegalArgumentException("Can't include owner context with multiple owners");
}
final Class<?> owner = owners.iterator().next();
if (!staticHandler.getReturnType().describeConstable().orElseThrow().equals(methodMatcher.targetType())) {
throw new IllegalArgumentException("Return type of staticHandler doesn't match target from methodMatcher");
}
if (staticHandler.getParameterCount() != (includeOwnerContext ? 2 : 1)) {
throw new IllegalArgumentException("staticHandler should only have %s parameter of type %s".formatted(includeOwnerContext ? 2 : 1, (includeOwnerContext ? owner + " and " : "") + intermediateType));
}
if (!staticHandler.getParameterTypes()[includeOwnerContext ? 1 : 0].describeConstable().orElseThrow().equals(existingType)) {
throw new IllegalArgumentException("staticHandler param type isn't " + existingType);
}
return new Return(owners, existingType, methodMatcher, staticHandler, includeOwnerContext);
}

// Uses the methodMatcher against bytecode from plugins. Any matching descriptors will have the method name/owner changed to point towards
// a generated method of the same descriptor. That generated method will call the original method and pass the return value
// to staticHandler. staticHandler will then convert the object to the plugin bytecode's expected type.
private record Return(Set<Class<?>> owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler, boolean includeOwnerContext) implements StaticRewrite.Generated.Return {
public record DirectReturn(Set<Class<?>> owners, ClassDesc existingType, TargetedMethodMatcher methodMatcher, Method staticHandler, boolean includeOwnerContext) implements StaticRewrite.Generated.Return {

public DirectReturn {
if (includeOwnerContext && owners.size() > 1) {
throw new IllegalArgumentException("Can't include owner context with multiple owners");
}
final Class<?> owner = owners.iterator().next();
if (!desc(staticHandler.getReturnType()).equals(methodMatcher.targetType())) {
throw new IllegalArgumentException("Return type of staticHandler doesn't match target from methodMatcher");
}
final int expectedStaticHandlerParamCount = includeOwnerContext ? 2 : 1;
if (staticHandler.getParameterCount() != expectedStaticHandlerParamCount) {
throw new IllegalArgumentException("staticHandler should only have %s parameter(s) of type %s".formatted(expectedStaticHandlerParamCount, (includeOwnerContext ? owner + " and " : "") + methodMatcher.targetType()));
}
if (!staticHandler.getParameterTypes()[includeOwnerContext ? 1 : 0].describeConstable().orElseThrow().equals(existingType)) {
throw new IllegalArgumentException("staticHandler param type isn't " + existingType);
}
}
}

// does a plain static rewrite with exact matching parameters
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.papermc.asm.rules.rename;

import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AnnotationRemapper;
import org.objectweb.asm.commons.Remapper;

/**
* Custom annotation remapper that includes remapping enum values in annotations.
*/
public final class FixedAnnotationRemapper extends AnnotationRemapper {

FixedAnnotationRemapper(final int api, final String descriptor, final AnnotationVisitor annotationVisitor, final Remapper remapper) {
super(api, descriptor, annotationVisitor, remapper);
}

@Override
public void visitEnum(final String name, final String descriptor, final String value) {
final String enumOwner = Type.getType(descriptor).getInternalName();
super.visitEnum(name, descriptor, this.remapper.mapFieldName(enumOwner, value, descriptor));
}

@Override
protected AnnotationVisitor createAnnotationRemapper(final String descriptor, final AnnotationVisitor annotationVisitor) {
return new FixedAnnotationRemapper(this.api, descriptor, annotationVisitor, this.remapper);
}
}
Loading

0 comments on commit 5923b23

Please sign in to comment.