aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/main/java/ftbsc/lll/processor/LilleroProcessor.java120
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Find.java26
-rw-r--r--src/main/java/ftbsc/lll/processor/annotations/Target.java9
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/ASTUtils.java163
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java90
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java97
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java110
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java115
-rw-r--r--src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java14
9 files changed, 492 insertions, 252 deletions
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
@@ -60,6 +61,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
* {@code processingEnv} argument.
@@ -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<ExecutableElement> injectorCandidates = injectors;
List<VariableElement> 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 <T> 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 <T extends Annotation> String getClassFullyQualifiedName(T ann, Function<T, Class<?>> parentFunction, String name) {
- if(name.contains("."))
- return name;
- String fqn;
+ public static <T extends Annotation> TypeMirror getTypeFromAnnotation(
+ T ann, Function<T, Class<?>> 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;
}
/**
@@ -134,47 +133,10 @@ 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,83 +151,61 @@ 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<Element> candidates = parent.getEnclosedElements()
+ List<Element> 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.
* @param v the annotated {@link VariableElement}
@@ -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.
@@ -105,75 +105,44 @@ public class JavaPoetUtils {
}
/**
- * 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 <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 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<String> 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<String> 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);
}
}
}