diff options
Diffstat (limited to 'src/main/java/ftbsc/lll/processor/containers')
4 files changed, 387 insertions, 0 deletions
diff --git a/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java new file mode 100644 index 0000000..297349a --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java @@ -0,0 +1,130 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.data.ClassData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.function.Function; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a class. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class ClassContainer { + /** + * The {@link ClassData} for the class represented by this container. + */ + public final ClassData data; + + /** + * The {@link Element} corresponding to the class. + * May only be null intentionally i.e. when the associated element is + * an anonymous class or a child of an anonymous class. + */ + public final Element elem; + + /** + * Private constructor, called from {@link #from(Annotation, Function, String, ProcessorOptions)}. + * @param fqn the fully-qualified name of the target class + * @param innerNames an array of Strings containing the path to the inner class, may be null + * @param options the {@link ProcessorOptions} to be used + */ + private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) { + //find and validate + Element elem = options.env.getElementUtils().getTypeElement(fqn); + + if(elem == null) + throw new TargetNotFoundException("class", fqn); + + StringBuilder fqnBuilder = new StringBuilder( + internalNameFromType(elem.asType(), options.env).replace('/', '.') + ); + + if(innerNames != null) { + for(String inner : innerNames) { + if(inner == null) continue; + fqnBuilder.append("$").append(inner); + try { + int anonClassCounter = Integer.parseInt(inner); + //anonymous classes cannot be validated! + if(options.anonymousClassWarning) + options.env.getMessager().printMessage( + Diagnostic.Kind.WARNING, + String.format( + "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!", + fqnBuilder, anonClassCounter + ) + ); + elem = null; + break; + } catch(NumberFormatException exc) { + elem = elem + .getEnclosedElements() + .stream() + .filter(e -> e instanceof TypeElement) + .filter(e -> e.getSimpleName().contentEquals(inner)) + .findFirst() + .orElse(null); + } + if(elem == null) + throw new TargetNotFoundException("class", inner); + } + } + this.data = getClassData(fqnBuilder.toString(), options.mapper); + this.elem = elem; + } + + /** + * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. + * @param ann the annotation containing the class + * @param classFunction the annotation function returning the class + * @param innerName a string containing the inner class name or nothing + * @param options the {@link ProcessorOptions} to be used + * @param <T> the type of the annotation carrying the information + * @return the fully qualified name of the given class + * @since 0.5.0 + */ + public static <T extends Annotation> ClassContainer from(T ann, Function<T, Class<?>> classFunction, String innerName, ProcessorOptions options) { + String fqn; + String[] inner; + fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString(); + inner = innerName.equals("") ? null : innerName.split("//$"); + return new ClassContainer(fqn, inner, options); + } + + /** + * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. + * @param cl the {@link TypeElement} representing the class + * @param options the {@link ProcessorOptions} to be used + * @return the fully qualified name of the given class + * @since 0.6.0 + */ + public static ClassContainer from(TypeElement cl, ProcessorOptions options) { + return new ClassContainer(cl.getQualifiedName().toString(), null, options); + } + + /** + * Finds and builds a {@link ClassContainer} based on information contained + * within {@link Patch} or a {@link Find} annotations, else returns a fallback. + * @param fallback the {@link ClassContainer} it falls back on + * @param p the {@link Patch} annotation to get info from + * @param f the {@link Find} annotation to get info from + * @param options the {@link ProcessorOptions} to be used + * @return the built {@link ClassContainer} or the fallback if not enough information was present + * @since 0.5.0 + */ + public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) { + if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options); + ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options); + return cl.data.name.equals("java/lang/Object") ? fallback : cl; + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java new file mode 100644 index 0000000..2299c93 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java @@ -0,0 +1,109 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.FieldData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.ProcessorOptions; +import org.objectweb.asm.Type; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a field. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class FieldContainer { + /** + * The {@link FieldData} for the field represented by this container. + */ + public final FieldData data; + + /** + * The descriptor of the field. + */ + public final String descriptor; + + /** + * The obfuscated descriptor of the field. + * If the mapper passed is null, then this will be identical to {@link #descriptor}. + */ + public final String descriptorObf; + + /** + * The {@link ClassContainer} representing the parent of this field. + */ + public final ClassContainer parent; + + /** + * The {@link VariableElement} corresponding to the field. + * May only be null intentionally i.e. when the field is + * a child of an anonymous class. + */ + public final VariableElement elem; + + /** + * Private constructor, called from {@link #from(VariableElement, ProcessorOptions)}. + * @param parent the {@link ClassContainer} representing the parent + * @param name the fully-qualified name of the target field + * @param descriptor the descriptor of the target field, may be null for verifiable fields + * @param options the {@link ProcessorOptions} to be used + */ + private FieldContainer(ClassContainer parent, String name, String descriptor, ProcessorOptions options) { + this.parent = parent; + if(parent.elem == null) { //unverified + if(descriptor == null) + throw new AmbiguousDefinitionException("Cannot use name-based lookups for fields of unverifiable classes!"); + this.elem = null; + this.descriptor = descriptor; + } else { + this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true, options.env); + this.descriptor = descriptorFromType(this.elem.asType(), options.env); + name = this.elem.getSimpleName().toString(); + } + this.data = getFieldData(parent.data.name, name, options.mapper); + this.descriptorObf = options.mapper == null ? this.descriptor + : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor(); + } + + /** + * Finds a {@link FieldContainer} from a finder. + * @param finder the {@link VariableElement} annotated with {@link Find} for this field + * @param options the {@link ProcessorOptions} to be used + * @return the built {@link FieldContainer} + * @since 0.5.0 + */ + public static FieldContainer from(VariableElement finder, ProcessorOptions options) { + //the parent always has a @Patch annotation + Patch patchAnn = finder.getEnclosingElement().getAnnotation(Patch.class); + //the finder always has a @Find annotation + Find f = finder.getAnnotation(Find.class); + + ClassContainer parent = ClassContainer.findOrFallback( + ClassContainer.from((TypeElement) finder.getEnclosingElement(), options), patchAnn, f, options + ); + + String name = f.name().isEmpty() ? finder.getSimpleName().toString() : f.name(); + String descriptor; + TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, options.env); + if(fieldType.toString().equals("java.lang.Object")) { + descriptor = null; + } else { + if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive()) + descriptor = //jank af but this is temporary anyway + "L" + ClassContainer.from( + f, Find::type, f.typeInner(), options + ).data.nameMapped + ";"; + else descriptor = descriptorFromType(fieldType, options.env); + } + + return new FieldContainer(parent, name, descriptor, options); + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java new file mode 100644 index 0000000..d56d846 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java @@ -0,0 +1,46 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.processor.annotations.Injector; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.ExecutableElement; + +/** + * Container for information about a class that is to be generated. + */ +public class InjectorInfo { + /** + * The {@link ExecutableElement} corresponding to the injector method. + */ + public final ExecutableElement injector; + + /** + * The {@link ExecutableElement} corresponding to the target method stub. + */ + public final ExecutableElement targetStub; + + /** + * The reason for the injection. + */ + public final String reason; + + /** + * The {@link MethodContainer} corresponding to the target method. + */ + public final MethodContainer target; + + /** + * Public constructor. + * @param injector the injector {@link ExecutableElement} + * @param targetStub the target {@link ExecutableElement} + * @param targetAnn the relevant {@link Target} annotation + * @param options the {@link ProcessorOptions} to be used + */ + public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn, ProcessorOptions options) { + this.injector = injector; + this.targetStub = targetStub; + this.reason = injector.getAnnotation(Injector.class).reason(); + this.target = MethodContainer.from(targetStub, targetAnn, null, options); + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java new file mode 100644 index 0000000..7d15599 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java @@ -0,0 +1,102 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.MethodData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a method. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class MethodContainer { + /** + * The {@link MethodData} for the method represented by this container. + */ + public final MethodData data; + + /** + * The obfuscated descriptor of the field. + * If the mapper passed is null, this will be identical to the one inside + * {@link #data}. + */ + public final String descriptorObf; + + /** + * The {@link ClassContainer} representing the parent of this method. + */ + public final ClassContainer parent; + + /** + * The {@link ExecutableElement} corresponding to the method. + * May only be null intentionally i.e. when the method is + * a child of an anonymous class. + */ + public final ExecutableElement elem; + + /** + * Private constructor, called from + * {@link #from(ExecutableElement, Target, Find, ProcessorOptions)}. + * @param parent the {@link ClassContainer} representing the parent + * @param name the fully-qualified name of the target method + * @param descriptor the descriptor of the target method + * @param strict whether the matching should be strict (see {@link Target#strict()} for more info) + * @param bridge whether the "bridge" should be matched instead (see {@link Target#bridge()} for more info) + * @param options the {@link ProcessorOptions} to be used + */ + private MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, boolean bridge, ProcessorOptions options) { + this.parent = parent; + if(parent.elem == null) { //unverified + if(descriptor == null) + throw new AmbiguousDefinitionException("Cannot use name-based lookups for methods of unverifiable classes!"); + this.elem = null; + } else { + ExecutableElement tmp = (ExecutableElement) findMember( + parent, name, descriptor, descriptor != null && strict,false, options.env + ); + this.elem = bridge ? findSyntheticBridge((TypeElement) this.parent.elem, tmp, options.env) : tmp; + name = this.elem.getSimpleName().toString(); + descriptor = descriptorFromExecutableElement(this.elem, options.env); + } + this.data = getMethodData(parent.data.name, name, descriptor, options.mapper); + this.descriptorObf = options.mapper == null ? this.data.signature.descriptor + : MappingUtils.mapMethodDescriptor(this.data.signature.descriptor, options.mapper, false); + } + + /** + * Builds the {@link MethodContainer} corresponding to a stub annotated with {@link Target}. + * @param stub the {@link ExecutableElement} for the stub + * @param t the {@link Target} annotation relevant to this case + * @param f the {@link Find} annotation containing fallback data, may be null + * @param options the {@link ProcessorOptions} to be used + * @return the {@link MethodContainer} corresponding to the method + * @throws AmbiguousDefinitionException if it finds more than one candidate + * @throws TargetNotFoundException if it finds no valid candidate + * @since 0.3.0 + */ + public static MethodContainer from(ExecutableElement stub, Target t, Find f, ProcessorOptions options) { + //the parent always has a @Patch annotation + Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class); + ClassContainer parent = ClassContainer.findOrFallback( + ClassContainer.from((TypeElement) stub.getEnclosingElement(), options), patchAnn, f, options + ); + String name = !t.methodName().isEmpty() + ? t.methodName() //name was specified in target + : stub.getSimpleName().toString(); + String descriptor = t.strict() + ? descriptorFromExecutableElement(stub, options.env) + : null; + + return new MethodContainer(parent, name, descriptor, t.strict(), t.bridge(), options); + } +} |