diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index e0b8d72..46822c4 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -10,6 +10,7 @@ import ftbsc.lll.processor.annotations.Injector; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; +import ftbsc.lll.processor.tools.containers.MethodContainer; import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; import ftbsc.lll.proxies.ProxyType; import ftbsc.lll.proxies.impl.TypeProxy; @@ -39,7 +40,7 @@ import static ftbsc.lll.processor.tools.JavaPoetUtils.*; */ @SupportedAnnotationTypes("ftbsc.lll.processor.annotations.Patch") @SupportedSourceVersion(SourceVersion.RELEASE_8) -@SupportedOptions({"mappingsFile", "badPracticeWarnings"}) +@SupportedOptions({"mappingsFile", "badPracticeWarnings", "anonymousClassWarning"}) public class LilleroProcessor extends AbstractProcessor { /** * A {@link Set} of {@link String}s that will contain the fully qualified names @@ -59,6 +60,12 @@ public class LilleroProcessor extends AbstractProcessor { */ public static boolean badPracticeWarnings = true; + /** + * Whether the processor should issue warnings when compiling code anonymous + * classes which can't be checked for validity. + */ + public static boolean anonymousClassWarning = true; + /** * Initializes the processor with the processing environment by * setting the {@code processingEnv} field to the value of the @@ -98,13 +105,24 @@ public class LilleroProcessor extends AbstractProcessor { String warns = processingEnv.getOptions().get("badPracticeWarnings"); if(warns == null) badPracticeWarnings = true; - else { - try { // 0 = false, any other integer = true - int i = Integer.parseInt(warns); - badPracticeWarnings = i != 0; - } catch(NumberFormatException ignored) { - badPracticeWarnings = Boolean.parseBoolean(warns); - } + else badPracticeWarnings = parseBooleanArg(warns); + String anonymousClassWarn = processingEnv.getOptions().get("anonymousClassWarning"); + if(anonymousClassWarn == null) + anonymousClassWarning = true; + else anonymousClassWarning = parseBooleanArg(anonymousClassWarn); + } + + /** + * Parses a boolean arg from a String. + * @param arg the arg to parse + * @return the parsed boolean + */ + private static boolean parseBooleanArg(String arg) { + try { // 0 = false, any other integer = true + int i = Integer.parseInt(arg); + return i != 0; + } catch(NumberFormatException ignored) { + return Boolean.parseBoolean(arg); } } @@ -174,14 +192,13 @@ public class LilleroProcessor extends AbstractProcessor { private void generateClasses(TypeElement cl) { //find class information Patch patchAnn = cl.getAnnotation(Patch.class); - ClassContainer targetClass = new ClassContainer( - getClassFullyQualifiedName( - patchAnn, - Patch::value, - patchAnn.className() - ), this.processingEnv, this.mapper + ClassContainer targetClass = ClassContainer.from( + patchAnn, + Patch::value, + patchAnn.className(), + this.processingEnv, + this.mapper ); - //find package information Element packageElement = cl.getEnclosingElement(); while (packageElement.getKind() != ElementKind.PACKAGE) @@ -209,17 +226,17 @@ public class LilleroProcessor extends AbstractProcessor { //case-specific handling if(type == ProxyType.TYPE) { //find and validate - ClassContainer clazz = findClassOrFallback(targetClass, proxyVar.getAnnotation(Find.class), this.processingEnv, this.mapper); + ClassContainer clazz = ClassContainer.findOrFallback(targetClass, proxyVar.getAnnotation(Find.class), this.processingEnv, this.mapper); //types can be generated with a single instruction constructorBuilder.addStatement( "super.$L = $T.from($S, 0, $L)", proxyVar.getSimpleName().toString(), TypeProxy.class, clazz.fqnObf, //use obf name, at runtime it will be obfuscated - mapModifiers(clazz.elem.getModifiers()) + clazz.elem == null ? 0 : mapModifiers(clazz.elem.getModifiers()) ); } else if(type == ProxyType.FIELD || type == ProxyType.METHOD) - appendMemberFinderDefinition(targetClass, proxyVar, null, constructorBuilder, this.processingEnv, this.mapper); + appendMemberFinderDefinition(targetClass, proxyVar, null, null, constructorBuilder, this.processingEnv, this.mapper); } //this will contain the classes to generate: the key is the class name @@ -236,36 +253,17 @@ public class LilleroProcessor extends AbstractProcessor { List injectorCandidates = injectors; List finderCandidates = methodFinders; - if(!targetAnn.of().equals("")) { - //case 1: find target by name - injectorCandidates = - injectorCandidates - .stream() - .filter(i -> i.getSimpleName().contentEquals(targetAnn.of())) - .collect(Collectors.toList()); - finderCandidates = - finderCandidates - .stream() - .filter(i -> i.getSimpleName().contentEquals(targetAnn.of())) - .collect(Collectors.toList()); - } else if(injectors.size() == 1 && targets.size() == 1) { - //case 2: there is only one target, must use it for that injector - finderCandidates = new ArrayList<>(); //no candidates - injectorCandidates = new ArrayList<>(); - injectorCandidates.add(injectors.get(0)); - } else { - //case 3: try to match by injectTargetName or same name for finders - injectorCandidates = - injectorCandidates - .stream() - .filter(t -> t.getSimpleName().toString().equalsIgnoreCase("inject" + tg.getSimpleName())) - .collect(Collectors.toList()); - finderCandidates = - finderCandidates - .stream() - .filter(t -> t.getSimpleName().contentEquals(tg.getSimpleName())) - .collect(Collectors.toList()); - } + //find target by name + injectorCandidates = + injectorCandidates + .stream() + .filter(i -> i.getSimpleName().contentEquals(targetAnn.of())) + .collect(Collectors.toList()); + finderCandidates = + finderCandidates + .stream() + .filter(i -> i.getSimpleName().contentEquals(targetAnn.of())) + .collect(Collectors.toList()); //throw exception if user is a moron and defined a finder and an injector with the same name if(finderCandidates.size() != 0 && injectorCandidates.size() != 0) @@ -274,7 +272,13 @@ public class LilleroProcessor extends AbstractProcessor { ); else if(finderCandidates.size() == 0 && injectorCandidates.size() == 0) processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, - String.format("Found orphan @Target annotation on method %s, it will be ignored!", tg.getSimpleName().toString())); + String.format( + "Found orphan @Target annotation on method %s.%s pointing at method %s, it will be ignored!", + cl.getSimpleName().toString(), + tg.getSimpleName().toString(), + targetAnn.of() + ) + ); else if(finderCandidates.size() == 0 && injectorCandidates.size() != 1) throw new AmbiguousDefinitionException( String.format("Found multiple candidate injectors for target %s::%s!", cl.getSimpleName(), tg.getSimpleName()) @@ -290,7 +294,7 @@ public class LilleroProcessor extends AbstractProcessor { matchedInjectors.add(injector); toGenerate.put( String.format("%sInjector%d", cl.getSimpleName(), iterationNumber), - new InjectorInfo(injector, tg) + new InjectorInfo(injector, tg, targetAnn) ); iterationNumber++; //increment is only used by injectors } else { @@ -301,6 +305,7 @@ public class LilleroProcessor extends AbstractProcessor { targetClass, finder, tg, + targetAnn, constructorBuilder, this.processingEnv, this.mapper @@ -320,8 +325,6 @@ public class LilleroProcessor extends AbstractProcessor { //iterate over the map and generate the classes for(String injName : toGenerate.keySet()) { - String targetMethodDescriptor = descriptorFromExecutableElement(toGenerate.get(injName).target); - String targetMethodName = findMemberName(targetClass.fqnObf, toGenerate.get(injName).target.getSimpleName().toString(), targetMethodDescriptor, this.mapper); MethodSpec inject = MethodSpec.methodBuilder("inject") .addModifiers(Modifier.PUBLIC) .returns(void.class) @@ -345,8 +348,8 @@ public class LilleroProcessor extends AbstractProcessor { .addMethod(buildStringReturnMethod("name", cl.getSimpleName().toString())) .addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason)) .addMethod(buildStringReturnMethod("targetClass", targetClass.fqn)) - .addMethod(buildStringReturnMethod("methodName", targetMethodName)) - .addMethod(buildStringReturnMethod("methodDesc", targetMethodDescriptor)) + .addMethod(buildStringReturnMethod("methodName", toGenerate.get(injName).target.name)) + .addMethod(buildStringReturnMethod("methodDesc", toGenerate.get(injName).target.descriptor)) .addMethods(generateDummies(targets)) .addMethod(inject) .build(); @@ -405,20 +408,21 @@ public class LilleroProcessor extends AbstractProcessor { public final String reason; /** - * The {@link ExecutableElement} corresponding to the target method. + * The {@link MethodContainer} corresponding to the target method. */ - private final ExecutableElement target; + private final MethodContainer target; /** * Public constructor. * @param injector the injector {@link ExecutableElement} * @param targetStub the target {@link ExecutableElement} + * @param targetAnn the relevant {@link Target} annotation */ - public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub) { + public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn) { this.injector = injector; this.targetStub = targetStub; this.reason = injector.getAnnotation(Injector.class).reason(); - this.target = findMethodFromStub(targetStub, null, processingEnv); + this.target = MethodContainer.from(targetStub, targetAnn, null, processingEnv, mapper); } } } \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/annotations/Find.java b/src/main/java/ftbsc/lll/processor/annotations/Find.java index 384fb60..8d28671 100644 --- a/src/main/java/ftbsc/lll/processor/annotations/Find.java +++ b/src/main/java/ftbsc/lll/processor/annotations/Find.java @@ -26,10 +26,10 @@ public @interface Find { Class value() default Object.class; /** - * For a {@link TypeProxy}, this can be either the fully-qualified name - * to be used in place of {@link #value()} or an inner class name to append - * after a $ symbol to the already acquired fully-qualified name. - * For others, this is refers to the parent class. + * This can be either the fully-qualified name to be used in place of {@link #value()} to + * represent the parent class or an inner class name to append after a $ symbol to the + * already acquired fully-qualified name. + * For a {@link TypeProxy}, this refers to the class itself rather than the parent. * @return the name of the inner class that contains the target, * defaults to empty string (not an inner class) * @since 0.5.0 @@ -50,4 +50,22 @@ public @interface Find { * @since 0.5.0 */ String name() default ""; + + /** + * This overrules the type of a field. Only to be used in the case (such as fields of + * anonymous classes) of fields whose parents cannot be reached at processing time. + * @return a {@link Class} representing the type. + * @deprecated This is only meant as a temporary solution until a better handling + * is implemented; only use this if strictly necessary as it may be + * removed or changed even across revisions. + */ + @Deprecated + Class type() default Object.class; + + /** + * @return the inner class name to be used with {@link #type()} + * @deprecated See {@link #type()}'s deprecation notice for more info. + */ + @Deprecated + String typeInner() default ""; } diff --git a/src/main/java/ftbsc/lll/processor/annotations/Target.java b/src/main/java/ftbsc/lll/processor/annotations/Target.java index ed5cc0f..e1e69f7 100644 --- a/src/main/java/ftbsc/lll/processor/annotations/Target.java +++ b/src/main/java/ftbsc/lll/processor/annotations/Target.java @@ -25,7 +25,14 @@ public @interface Target { * @return the name of the element this is supposed to apply to * @since 0.5.0 */ - String of() default ""; + String of(); + + /** + * @return a name which overrides the name of the annotated one, may be used in + * cases such as constructors + * @since 0.5.0 + */ + String methodName() default ""; /** * When set to false, tells the processor to first try to match a single method by name, diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java index 2eeb450..7fb2dc7 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java @@ -4,16 +4,18 @@ import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.MappingNotFoundException; import ftbsc.lll.exceptions.NotAProxyException; import ftbsc.lll.exceptions.TargetNotFoundException; -import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; import ftbsc.lll.proxies.ProxyType; import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; +import javax.lang.model.element.Element; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.MirroredTypeException; +import javax.lang.model.type.TypeMirror; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.List; @@ -21,6 +23,7 @@ import java.util.function.Function; import java.util.stream.Collectors; import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement; +import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromType; /** * Collection of AST-related static utils that didn't really fit into the main class. @@ -96,26 +99,22 @@ public class ASTUtils { } /** - * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. + * Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it. * @param ann the annotation containing the class - * @param parentFunction the annotation function returning the class - * @param name a string containing the FQN, the inner class name or nothing + * @param classFunction the annotation function returning the class + * @param env the {@link ProcessingEnvironment} to perform the operation in * @param the type of the annotation carrying the information - * @return the fully qualified name of the given class + * @return a {@link TypeMirror} representing the requested {@link Class} * @since 0.3.0 */ - public static String getClassFullyQualifiedName(T ann, Function> parentFunction, String name) { - if(name.contains(".")) - return name; - String fqn; + public static TypeMirror getTypeFromAnnotation( + T ann, Function> classFunction, ProcessingEnvironment env) { try { - fqn = parentFunction.apply(ann).getCanonicalName(); + String fqn = classFunction.apply(ann).getCanonicalName(); + return env.getElementUtils().getTypeElement(fqn).asType(); } catch(MirroredTypeException e) { - fqn = e.getTypeMirror().toString(); + return e.getTypeMirror(); } - if(!name.equals("")) - fqn = String.format("%s$%s", fqn, name); - return fqn; } /** @@ -133,48 +132,11 @@ public class ASTUtils { } } - /** - * Finds the class name and maps it to the correct format. - * @param fallback the (unobfuscated) FQN to fall back on - * @param finderAnn an annotation containing metadata about the target, may be null - * @return the fully qualified class name - * @since 0.5.0 - */ - public static String findClassNameFromAnnotations(String fallback, Find finderAnn) { - if(finderAnn != null) { - String fullyQualifiedName = - getClassFullyQualifiedName( - finderAnn, - Find::value, - finderAnn.className() - ); - if(!fullyQualifiedName.equals("java.lang.Object")) - return findClassName(fullyQualifiedName, null); - } - return findClassName(fallback, null); - } - - /** - * Finds the class name and maps it to the correct format. - * @param patchAnn the {@link Patch} annotation containing target class info - * @param finderAnn an annotation containing metadata about the target, may be null - * @return the fully qualified class name - * @since 0.3.0 - */ - public static String findClassNameFromAnnotations(Patch patchAnn, Find finderAnn) { - return findClassNameFromAnnotations( - getClassFullyQualifiedName( - patchAnn, - Patch::value, - patchAnn.className() - ), finderAnn); - } - /** * Finds the member name and maps it to the correct format. * @param parentFQN the already mapped FQN of the parent class * @param memberName the name of the member - * @param methodDescriptor the descriptor of the method, may be null if it's not a method + * @param methodDescriptor the descriptor of the method, may be null * @param mapper the {@link ObfuscationMapper} to use, may be null * @return the internal class name * @since 0.3.0 @@ -189,82 +151,60 @@ public class ASTUtils { /** * Finds a member given the name, the container class and (if it's a method) the descriptor. - * @param parentFQN the fully qualified name of the parent class + * @param parent the {@link ClassContainer} representing the parent * @param name the name to search for * @param descr the descriptor to search for, or null if it's not a method - * @param strict whether the search should be strict (see {@link Target#strict()} for more info), - * only applies to method searches + * @param strict whether to perform lookup in strict mode (see {@link Target#strict()} for more information) * @param field whether the member being searched is a field - * @param env the {@link ProcessingEnvironment} to perform the operation in * @return the desired member, if it exists * @throws AmbiguousDefinitionException if it finds more than one candidate * @throws TargetNotFoundException if it finds no valid candidate * @since 0.3.0 */ - public static Element findMember(String parentFQN, String name, String descr, boolean strict, boolean field, ProcessingEnvironment env) { - TypeElement parent = env.getElementUtils().getTypeElement(parentFQN); - if(parent == null) - throw new AmbiguousDefinitionException(String.format("Could not find parent class %s for member %s!", parentFQN, descr == null ? name : name + descr)); + public static Element findMember(ClassContainer parent, String name, String descr, boolean strict, boolean field) { + if(parent.elem == null) + throw new TargetNotFoundException("parent class", parent.fqn); //try to find by name - List candidates = parent.getEnclosedElements() + List candidates = parent.elem.getEnclosedElements() .stream() .filter(e -> (field && e instanceof VariableElement) || e instanceof ExecutableElement) .filter(e -> e.getSimpleName().contentEquals(name)) .collect(Collectors.toList()); if(candidates.size() == 0) - throw new TargetNotFoundException(field ? "field" : "method", name, parentFQN); - if(candidates.size() == 1 && (!strict || field)) + throw new TargetNotFoundException(field ? "field" : "method", name, parent.fqn); + + if(candidates.size() == 1 && (!strict || descr == null)) return candidates.get(0); - if(field || descr == null) { + + if(descr == null) { throw new AmbiguousDefinitionException( - String.format("Found %d members named %s in class %s!", candidates.size(), name, parentFQN) + String.format("Found %d members named %s in class %s!", candidates.size(), name, parent.fqn) ); } else { - candidates = candidates.stream() - .map(e -> (ExecutableElement) e) - .filter(strict - ? c -> descr.equals(descriptorFromExecutableElement(c)) - : c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0]) - ).collect(Collectors.toList()); + if(field) { + //fields can verify the signature for extra safety + //but there can only be 1 field with a given name + if(!descriptorFromType(candidates.get(0).asType()).equals(descr)) + throw new TargetNotFoundException("field", String.format("%s with descriptor %s", name, descr), parent.fqn); + } else { + candidates = candidates.stream() + .map(e -> (ExecutableElement) e) + .filter(strict + ? c -> descr.equals(descriptorFromExecutableElement(c)) + : c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c).split("\\)")[0]) + ).collect(Collectors.toList()); + } if(candidates.size() == 0) - throw new TargetNotFoundException("method", String.format("%s %s", name, descr), parentFQN); + throw new TargetNotFoundException("method", String.format("%s %s", name, descr), parent.fqn); if(candidates.size() > 1) throw new AmbiguousDefinitionException( - String.format("Found %d methods named %s in class %s!", candidates.size(), name, parentFQN) + String.format("Found %d methods named %s in class %s!", candidates.size(), name, parent.fqn) ); return candidates.get(0); } } - /** - * Finds the real class method corresponding to a stub annotated with {@link Target}. - * @param stub the {@link ExecutableElement} for the stub - * @param f the {@link Find} annotation containing fallback data, may be null - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the {@link ExecutableElement} 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 ExecutableElement findMethodFromStub(ExecutableElement stub, Find f, ProcessingEnvironment env) { - //the parent always has a @Patch annotation - Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class); - //there should ever only be one of these two - Target targetAnn = stub.getAnnotation(Target.class); //if this is null strict mode is always disabled - String parentFQN = findClassNameFromAnnotations(patchAnn, f); - String methodName = stub.getSimpleName().toString(); - String methodDescriptor = descriptorFromExecutableElement(stub); - return (ExecutableElement) findMember( - parentFQN, - methodName, - methodDescriptor, - targetAnn != null && targetAnn.strict(), - false, //only evaluate if target is null - env - ); - } - /** * Utility method for finding out what type of proxy a field is. * It will fail if the return type is not a known type of proxy. @@ -288,21 +228,4 @@ public class ASTUtils { throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString()); } } - - /** - * Finds and builds a {@link ClassContainer} based on information contained - * within a {@link Find} annotation, else returns a fallback. - * @param fallback the {@link ClassContainer} it falls back on - * @param f the {@link Find} annotation to get info from - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @param mapper the {@link ObfuscationMapper} to use, may be null - * @return the built {@link ClassContainer} or the fallback if not enough information was present - * @since 0.5.0 - */ - public static ClassContainer findClassOrFallback(ClassContainer fallback, Find f, ProcessingEnvironment env, ObfuscationMapper mapper) { - String fqn = getClassFullyQualifiedName(f, Find::value, f.className()); - return fqn.equals("java.lang.Object") - ? fallback - : new ClassContainer(fqn, env, mapper); - } } diff --git a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java index 5245116..6ae867b 100644 --- a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java @@ -1,10 +1,11 @@ package ftbsc.lll.processor.tools; import com.squareup.javapoet.*; -import ftbsc.lll.processor.LilleroProcessor; import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.tools.containers.ArrayContainer; +import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; +import ftbsc.lll.processor.tools.containers.FieldContainer; +import ftbsc.lll.processor.tools.containers.MethodContainer; import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; import ftbsc.lll.proxies.ProxyType; import ftbsc.lll.proxies.impl.FieldProxy; @@ -16,13 +17,12 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; -import javax.tools.Diagnostic; import java.util.Collection; import java.util.HashSet; -import static ftbsc.lll.processor.tools.ASTUtils.*; +import static ftbsc.lll.processor.tools.ASTUtils.getProxyType; +import static ftbsc.lll.processor.tools.ASTUtils.mapModifiers; /** * Collection of static utils that rely on JavaPoet to function. @@ -104,76 +104,45 @@ public class JavaPoetUtils { return methodSignature.toString(); } - /** - * Adds to the given {@link MethodSpec.Builder} the given line of code, - * containing a call to a method of a {@link MethodProxy.Builder} or a - * {@link FieldProxy.Builder}. - * @param b the {@link MethodSpec.Builder} - * @param proxyBuilderName the name of the proxy builder - * @param proxyBuilderMethod the method to call - * @param t the {@link TypeMirror} to add - * @since 0.4.0 - */ - public static void addTypeToProxyGenerator(MethodSpec.Builder b, String proxyBuilderName, String proxyBuilderMethod, TypeMirror t) { - String insn = String.format("%s.%s", proxyBuilderName, proxyBuilderMethod); - if(t.getKind().isPrimitive() || t.getKind() == TypeKind.VOID) - b.addStatement(insn + "($T.class)", t); - else { - ArrayContainer arr = new ArrayContainer(t); - TypeName type = TypeName.get(arr.innermostComponent); - if(type instanceof ParameterizedTypeName) - type = ((ParameterizedTypeName) type).rawType; - b.addStatement( - insn + "($S, $L)", - type.toString(), - arr.arrayLevel - ); - } - } - /** * Appends to a given {@link MethodSpec.Builder} definitions for a proxy. * @param fallback the {@link ClassContainer} to fall back on * @param var the {@link VariableElement} representing the proxy * @param stub the stub {@link ExecutableElement} if present or relevant, null otherwise + * @param t the {@link Target} relevant to this finder if present or relevant, null otherwise * @param con the {@link MethodSpec.Builder} to append to * @param env the {@link ProcessingEnvironment} to perform the operation in * @param mapper the {@link ObfuscationMapper} to use, may be null * @since 0.5.0 */ public static void appendMemberFinderDefinition( - ClassContainer fallback, VariableElement var, ExecutableElement stub, MethodSpec.Builder con, ProcessingEnvironment env, ObfuscationMapper mapper) { + ClassContainer fallback, VariableElement var, ExecutableElement stub, Target t, + MethodSpec.Builder con, ProcessingEnvironment env, ObfuscationMapper mapper) { ProxyType type = getProxyType(var); if(type != ProxyType.METHOD && type != ProxyType.FIELD) - return; //this method is irrelevant to everyoen else + return; //this method is irrelevant to everyone else //we need this stuff Find f = var.getAnnotation(Find.class); - ClassContainer parent = findClassOrFallback(fallback, f, env, mapper); final boolean isMethod = type == ProxyType.METHOD; final String builderName = var.getSimpleName().toString() + "Builder"; - String name, nameObf; + String descriptor, nameObf; + ClassContainer parent; Element target; if(isMethod) { - ExecutableElement executableTarget; - if(f.name().equals("")) //find and validate from stub - executableTarget = findMethodFromStub(stub, f, env); - else { //find and validate by name alone - if(LilleroProcessor.badPracticeWarnings) //warn user that he is doing bad stuff - env.getMessager().printMessage(Diagnostic.Kind.WARNING, - String.format("Matching method %s by name, this is bad practice and may lead to unexpected behaviour. Use @Target stubs instead!", f.name())); - executableTarget = (ExecutableElement) findMember(parent.fqn, f.name(), null, false, false, env); - } - name = executableTarget.getSimpleName().toString(); - nameObf = findMemberName(parent.fqnObf, name, descriptorFromExecutableElement(executableTarget), mapper); - target = executableTarget; + MethodContainer mc = MethodContainer.from(stub, t, f, env, mapper); + descriptor = mc.descriptor; + nameObf = mc.nameObf; + parent = mc.parent; + target = mc.elem; } else { - //find and validate target - name = f.name().equals("") ? var.getSimpleName().toString() : f.name(); - target = findMember(parent.fqn, name, null, false, true, env); - nameObf = findMemberName(parent.fqnObf, name, null, mapper); + FieldContainer fc = FieldContainer.from(var, env, mapper); + descriptor = fc.descriptor; + nameObf = fc.nameObf; + parent = fc.parent; + target = fc.elem; } //initialize builder @@ -189,23 +158,22 @@ public class JavaPoetUtils { "$L.setParent($S, $L)", builderName, parent.fqnObf, - mapModifiers(parent.elem.getModifiers()) + parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) ); //set modifiers con.addStatement( "$L.setModifiers($L)", builderName, - mapModifiers(target.getModifiers()) + target == null ? 0 :mapModifiers(target.getModifiers()) ); - if(isMethod) { //set parameters and return type - ExecutableElement executableTarget = (ExecutableElement) target; - for(VariableElement p : executableTarget.getParameters()) - addTypeToProxyGenerator(con, builderName, "addParameter", p.asType()); - addTypeToProxyGenerator(con, builderName, "setReturnType", executableTarget.getReturnType()); - } else //set type - addTypeToProxyGenerator(con,builderName, "setType", target.asType()); + //set type(s) + con.addStatement( + "$L.setDescriptor($S)", + builderName, + descriptor + ); //build and set con.addStatement( diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java index bde527d..0e47842 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java @@ -1,12 +1,20 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.processor.LilleroProcessor; +import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.Arrays; +import java.util.function.Function; import static ftbsc.lll.processor.tools.ASTUtils.findClassName; +import static ftbsc.lll.processor.tools.ASTUtils.getTypeFromAnnotation; /** * Container for information about a class. @@ -27,21 +35,100 @@ public class ClassContainer { /** * 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; /** * Public constructor. * @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 env the {@link ProcessingEnvironment} to be used to locate the class * @param mapper the {@link ObfuscationMapper} to be used, may be null */ - public ClassContainer(String fqn, ProcessingEnvironment env, ObfuscationMapper mapper) { - this.fqn = fqn; - this.fqnObf = findClassName(fqn, mapper); - Element elem = env.getElementUtils().getTypeElement(fqn); //at compile time we have an unobfuscated environment + public ClassContainer(String fqn, String[] innerNames, ProcessingEnvironment env, ObfuscationMapper mapper) { + //find and validate + Element elem = env.getElementUtils().getTypeElement(fqn); //use unobfuscated name if(elem == null) throw new TargetNotFoundException("class", fqn); - else this.elem = elem; + + if(innerNames != null) { + for(String inner : innerNames) { + try { + int anonClassCounter = Integer.parseInt(inner); + //anonymous classes cannot be validated! + if(LilleroProcessor.anonymousClassWarning) + env.getMessager().printMessage( + Diagnostic.Kind.WARNING, + String.format( + "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!", + fqn, 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.fqn = fqn; + this.fqnObf = findClassName(fqn, 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 className a string containing the FQN, the inner class name or nothing + * @param env the {@link ProcessingEnvironment} to be used to locate the class + * @param mapper the {@link ObfuscationMapper} to be used, may be null + * @param the type of the annotation carrying the information + * @return the fully qualified name of the given class + * @since 0.5.0 + */ + public static ClassContainer from( + T ann, Function> classFunction, String className, + ProcessingEnvironment env, ObfuscationMapper mapper) + { + String fqn; + String[] inner; + if(className.contains(".")) { + String[] split = className.split("//$"); + fqn = split[0]; + inner = split.length == 1 ? null : Arrays.copyOfRange(split, 1, split.length - 1); + } else { + fqn = getTypeFromAnnotation(ann, classFunction, env).toString(); + inner = className.equals("") ? null : className.split("//$"); + } + + return new ClassContainer(fqn, inner, env, mapper); + } + + /** + * Finds and builds a {@link ClassContainer} based on information contained + * within a {@link Find} annotation, else returns a fallback. + * @param fallback the {@link ClassContainer} it falls back on + * @param f the {@link Find} annotation to get info from + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @param mapper the {@link ObfuscationMapper} to use, may be null + * @return the built {@link ClassContainer} or the fallback if not enough information was present + * @since 0.5.0 + */ + public static ClassContainer findOrFallback(ClassContainer fallback, Find f, ProcessingEnvironment env, ObfuscationMapper mapper) { + ClassContainer cl = ClassContainer.from(f, Find::value, f.className(), env, mapper); + return cl.fqn.equals("java.lang.Object") + ? fallback + : cl; } } \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java new file mode 100644 index 0000000..7dda102 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java @@ -0,0 +1,110 @@ +package ftbsc.lll.processor.tools.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import static ftbsc.lll.processor.tools.ASTUtils.*; +import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromType; + +/** + * Container for information about a field. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class FieldContainer { + /** + * The name of the field. + */ + public final String name; + + /** + * The descriptor of the field. + */ + public final String descriptor; + + /** + * The obfuscated name of the field. + * If the mapper passed is null, then this will be identical to {@link #name}. + */ + public final String nameObf; + + /** + * The {@link ClassContainer} representing the parent of this field. + * May be null if the parent is a class type that can not be checked + * at processing time (such as an anonymous class) + */ + 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; + + /** + * Public constructor. + * @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 mapper the {@link ObfuscationMapper} to be used, may be null + */ + public FieldContainer(ClassContainer parent, String name, String descriptor, ObfuscationMapper mapper) { + 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.name = name; + this.descriptor = descriptor; + } else { + this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true); + this.name = this.elem.getSimpleName().toString(); + this.descriptor = descriptorFromType(this.elem.asType()); + } + this.nameObf = findMemberName(parent.fqnObf, name, descriptor, mapper); + } + + /** + * Finds a {@link FieldContainer} from a finder. + * @param finder the {@link VariableElement} annotated with {@link Find} for this field + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @param mapper the {@link ObfuscationMapper} to be used, may be null + * @return the built {@link FieldContainer} + * @since 0.5.0 + */ + public static FieldContainer from(VariableElement finder, ProcessingEnvironment env, ObfuscationMapper mapper) { + //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(patchAnn, Patch::value, patchAnn.className(), env, null), + f, env, mapper + ); + + String name = f.name().equals("") ? finder.getSimpleName().toString() : f.name(); + String descriptor; + TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, env); + if(fieldType.toString().equals("java.lang.Object")) + descriptor = null; + else { + if(fieldType.getKind() == TypeKind.DECLARED) + descriptor = //jank af but this is temporary anyway + "L" + new ClassContainer( + fieldType.toString(), f.typeInner().split("//$"), env, mapper + ).fqn.replace('.', '/') + ";"; + else descriptor = descriptorFromType(fieldType); + } + + return new FieldContainer(parent, name, descriptor, mapper); + } +} \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java new file mode 100644 index 0000000..fd08028 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java @@ -0,0 +1,115 @@ +package ftbsc.lll.processor.tools.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.processor.LilleroProcessor; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.ExecutableElement; +import javax.tools.Diagnostic; + +import static ftbsc.lll.processor.tools.ASTUtils.findMember; +import static ftbsc.lll.processor.tools.ASTUtils.findMemberName; +import static ftbsc.lll.processor.tools.JavaPoetUtils.descriptorFromExecutableElement; + +/** + * Container for information about a method. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class MethodContainer { + /** + * The name of the method. + */ + public final String name; + + /** + * The descriptor of the method. + */ + public final String descriptor; + + /** + * The obfuscated name of the method. + * If the mapper passed is null, then this will be identical to {@link #name}. + */ + public final String nameObf; + + /** + * The {@link ClassContainer} representing the parent of this method. + * May be null if the parent is a class type that can not be checked + * at processing time (such as an anonymous class) + */ + 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; + + /** + * Public constructor. + * @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 mapper the {@link ObfuscationMapper} to be used, may be null + */ + public MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, ObfuscationMapper mapper) { + 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; + this.name = name; + this.descriptor = descriptor; + } else { + this.elem = (ExecutableElement) findMember(parent, name, descriptor, descriptor != null && strict, false); + this.name = this.elem.getSimpleName().toString(); + this.descriptor = descriptorFromExecutableElement(this.elem); + } + this.nameObf = findMemberName(parent.fqnObf, name, descriptor, mapper); + } + + /** + * 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 env the {@link ProcessingEnvironment} to perform the operation in + * @param mapper the {@link ObfuscationMapper} to be used, may be null + * @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, ProcessingEnvironment env, ObfuscationMapper mapper) { + //the parent always has a @Patch annotation + Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class); + ClassContainer parent = ClassContainer.findOrFallback( + ClassContainer.from(patchAnn, Patch::value, patchAnn.className(), env, null), + f, env, mapper + ); + + String name, descriptor; + if(f != null && !f.name().equals("")) { //match by name alone + if(LilleroProcessor.badPracticeWarnings) //warn user that he is doing bad stuff + env.getMessager().printMessage(Diagnostic.Kind.WARNING, + String.format("Matching method %s by name, this is bad practice and may lead to unexpected behaviour. Use @Target stubs instead!", f.name())); + name = f.name(); + descriptor = null; + } else { + if(t != null && !t.methodName().equals("")) + name = t.methodName(); //name was specified in target + else name = stub.getSimpleName().toString(); + descriptor = t != null && t.strict() ? descriptorFromExecutableElement(stub) : null; + } + + return new MethodContainer(parent, name, descriptor, t != null && t.strict(), mapper); + } +} \ No newline at end of file diff --git a/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java b/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java index ad4ced3..0c42983 100644 --- a/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java +++ b/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java @@ -180,8 +180,16 @@ public class ObfuscationMapper { * @throws AmbiguousDefinitionException if not enough data was given to uniquely identify a mapping */ public String get(String memberName, String methodDescriptor) { - if(methodDescriptor == null) - return members.get(memberName); + if(methodDescriptor == null) { + String res = members.get(memberName); + if(res != null) return res; + else { + List candidates = members.keySet().stream().filter(k -> k.startsWith(memberName)).collect(Collectors.toList()); + if(candidates.size() == 1) + return candidates.get(0); + else throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + this.unobf + "::" + memberName); + } + } List candidates = members.keySet().stream().filter(m -> m.startsWith(memberName)).collect(Collectors.toList()); if(candidates.size() == 1) return members.get(candidates.get(0)); @@ -193,7 +201,7 @@ public class ObfuscationMapper { case 1: return members.get(candidates.get(0)); default: - throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + unobf + "::" + memberName); + throw new AmbiguousDefinitionException("Mapper could not uniquely identify method " + this.unobf + "::" + memberName); } } }