From ed70355a86f357b644bb7ecd7b90de5a5a6f6f76 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sun, 11 Jun 2023 15:32:07 +0200 Subject: chore: moved obfuscation stuff to mapping library --- build.gradle | 3 +- .../lll/exceptions/MappingNotFoundException.java | 27 --- .../java/ftbsc/lll/processor/tools/ASTUtils.java | 6 +- .../lll/processor/tools/ProcessorOptions.java | 14 +- .../processor/tools/containers/FieldContainer.java | 3 +- .../tools/containers/MethodContainer.java | 3 +- .../tools/obfuscation/ObfuscationMapper.java | 225 --------------------- 7 files changed, 16 insertions(+), 265 deletions(-) delete mode 100644 src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java diff --git a/build.gradle b/build.gradle index 3f51438..5109f86 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,8 @@ repositories { dependencies { implementation 'com.squareup:javapoet:1.13.0' implementation 'org.ow2.asm:asm-commons:9.5' - implementation 'ftbsc:lll:0.4.1' + implementation 'ftbsc:lll:0.4.2' + implementation 'ftbsc.lll:mapper:0.0.2' } jar { diff --git a/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java b/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java deleted file mode 100644 index e943c01..0000000 --- a/src/main/java/ftbsc/lll/exceptions/MappingNotFoundException.java +++ /dev/null @@ -1,27 +0,0 @@ -package ftbsc.lll.exceptions; - -import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; - -/** - * Thrown upon failure to find the requested mapping within a loaded {@link ObfuscationMapper}. - */ -public class MappingNotFoundException extends RuntimeException { - - /** - * Constructs a new mapping not found exception for the specified mapping. - * @param mapping the relevant mapping - */ - public MappingNotFoundException(String mapping) { - super(String.format("Could not find mapping for %s!", mapping)); - } - - /** - * Constructs a new mapping not found exception for the specified mapping - * with the specified reason. - * @param mapping the relevant mapping - * @param reason the reason message - */ - public MappingNotFoundException(String mapping, String reason) { - this(mapping + ": " + reason); - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java index 0037886..b33a906 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java @@ -6,7 +6,7 @@ import ftbsc.lll.exceptions.NotAProxyException; import ftbsc.lll.exceptions.TargetNotFoundException; import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; +import ftbsc.lll.mapper.IMapper; import ftbsc.lll.proxies.ProxyType; import javax.annotation.processing.ProcessingEnvironment; @@ -229,11 +229,11 @@ public class ASTUtils { * @param parentFQN the unobfuscated FQN of the parent class * @param memberName the name of the member * @param methodDescriptor the descriptor of the method, may be null - * @param mapper the {@link ObfuscationMapper} to use, may be null + * @param mapper the {@link IMapper} to use, may be null * @return the internal class name * @since 0.3.0 */ - public static String findMemberName(String parentFQN, String memberName, String methodDescriptor, ObfuscationMapper mapper) { + public static String findMemberName(String parentFQN, String memberName, String methodDescriptor, IMapper mapper) { try { return mapper == null ? memberName : mapper.obfuscateMember(parentFQN, memberName, methodDescriptor); } catch(MappingNotFoundException e) { diff --git a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java index 0ef8a18..de751bc 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java +++ b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java @@ -2,7 +2,7 @@ package ftbsc.lll.processor.tools; import ftbsc.lll.IInjector; import ftbsc.lll.exceptions.InvalidResourceException; -import ftbsc.lll.processor.tools.obfuscation.ObfuscationMapper; +import ftbsc.lll.mapper.IMapper; import javax.annotation.processing.ProcessingEnvironment; import java.io.*; @@ -12,6 +12,7 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; import java.util.Set; +import java.util.stream.Collectors; /** * Class in charge of containing, parsing and processing all processor options, @@ -33,10 +34,10 @@ public class ProcessorOptions { public final ProcessingEnvironment env; /** - * The {@link ObfuscationMapper} used to convert classes and variables + * The {@link IMapper} used to convert classes and variables * to their obfuscated equivalent. Will be null when no mapper is in use. */ - public final ObfuscationMapper mapper; + public final IMapper mapper; /** * Whether the processor should issue warnings when compiling code anonymous @@ -80,10 +81,9 @@ public class ProcessorOptions { throw new InvalidResourceException(location); } } - //assuming its tsrg file - //todo: replace crappy homebaked parser with actual library - this.mapper = new ObfuscationMapper(new BufferedReader(new InputStreamReader(targetStream, - StandardCharsets.UTF_8)).lines()); + this.mapper = IMapper.getMappers(new BufferedReader( + new InputStreamReader(targetStream, StandardCharsets.UTF_8)).lines().collect(Collectors.toList()) + ).iterator().next(); //TODO: add logic for choosing a specific one } this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java index 2dd2ecb..0074ebe 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java @@ -1,6 +1,7 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.mapper.tools.MappingUtils; import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.tools.ProcessorOptions; @@ -73,7 +74,7 @@ public class FieldContainer { this.name = this.elem.getSimpleName().toString(); this.descriptor = descriptorFromType(this.elem.asType(), options.env); } - this.descriptorObf = options.mapper == null ? this.descriptor : options.mapper.obfuscateType(Type.getType(this.descriptor)).getDescriptor(); + this.descriptorObf = options.mapper == null ? this.descriptor : MappingUtils.obfuscateType(Type.getType(this.descriptor), options.mapper).getDescriptor(); this.nameObf = findMemberName(parent.fqn, this.name, null, options.mapper); } diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java index 714ee54..f646a1c 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java @@ -2,6 +2,7 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.MappingUtils; import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.annotations.Target; @@ -78,7 +79,7 @@ public class MethodContainer { this.name = this.elem.getSimpleName().toString(); this.descriptor = descriptorFromExecutableElement(this.elem, options.env); } - this.descriptorObf = options.mapper == null ? this.descriptor : options.mapper.obfuscateMethodDescriptor(this.descriptor); + this.descriptorObf = options.mapper == null ? this.descriptor : MappingUtils.obfuscateMethodDescriptor(this.descriptor, options.mapper); this.nameObf = findMemberName(parent.fqn, this.name, this.descriptor, options.mapper); } diff --git a/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java b/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java deleted file mode 100644 index 5938de3..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/obfuscation/ObfuscationMapper.java +++ /dev/null @@ -1,225 +0,0 @@ -package ftbsc.lll.processor.tools.obfuscation; - -import ftbsc.lll.exceptions.AmbiguousDefinitionException; -import ftbsc.lll.exceptions.MappingNotFoundException; -import ftbsc.lll.tools.DescriptorBuilder; -import org.objectweb.asm.Type; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -/** - * Parses a .tsrg file into a mapper capable of converting from - * deobfuscated names to obfuscated ones. - * Obviously, it may only be used at runtime if the .tsrg file is - * included in the resources. However, in that case, I'd recommend - * using the built-in Forge one and refrain from including an extra - * resource for no good reason. - * TODO: CSV format - * @since 0.2.0 - */ -public class ObfuscationMapper { - - /** - * A Map using the deobfuscated names as keys, - * holding information for that class as value. - */ - private final Map mapper = new HashMap<>(); - - /** - * The public constructor. - * Should be passed a {@link Stream} of Strings, one representing each line. - * Whether they contain line endings or not is irrelevant. - * @param str a {@link Stream} of strings - */ - public ObfuscationMapper(Stream str) { - AtomicReference currentClass = new AtomicReference<>(""); - str.forEach(l -> { - if(l == null) return; - if(l.startsWith("\t")) - mapper.get(currentClass.get()).addMember(l); - else { - String[] sp = l.split(" "); - ObfuscationData s = new ObfuscationData(sp[0], sp[1]); - currentClass.set(s.unobf); - mapper.put(s.unobf, s); - } - }); - } - - /** - * Gets the obfuscated name of the class. - * @param name the unobfuscated internal name of the desired class - * @return the obfuscated name of the class - * @throws MappingNotFoundException if no mapping is found - */ - public String obfuscateClass(String name) { - ObfuscationData data = mapper.get(name.replace('.', '/')); - if(data == null) - throw new MappingNotFoundException(name); - else return data.obf; - } - - /** - * Gets the obfuscated name of a class member (field or method). - * The method signature must be in this format: "methodName methodDescriptor", - * with a space, because that's how it is in .tsrg files. - * @param parentName the unobfuscated internal name of the parent class - * @param memberName the field name or method signature - * @param methodDescriptor the optional descriptor of the member, may be null or partial - * @return the obfuscated name of the given member - * @throws MappingNotFoundException if no mapping is found - */ - public String obfuscateMember(String parentName, String memberName, String methodDescriptor) { - ObfuscationData data = mapper.get(parentName.replace('.', '/')); - if(data == null) - throw new MappingNotFoundException(parentName + "::" + memberName); - return data.get(memberName, methodDescriptor); - } - - /** - * Obfuscates a method descriptor, replacing its class references - * with their obfuscated counterparts. - * @param descriptor a {@link String} containing the descriptor - * @return the obfuscated descriptor - * @since 0.5.1 - */ - public String obfuscateMethodDescriptor(String descriptor) { - Type method = Type.getMethodType(descriptor); - Type[] arguments = method.getArgumentTypes(); - Type returnType = method.getReturnType(); - - Type[] obfArguments = new Type[arguments.length]; - for(int i = 0; i < obfArguments.length; i++) - obfArguments[i] = this.obfuscateType(arguments[i]); - - return Type.getMethodDescriptor(this.obfuscateType(returnType), obfArguments); - } - - /** - * Given a {@link Type} it returns its obfuscated counterpart. - * @param type the type in question - * @return the obfuscated type - * @since 0.5.1 - */ - public Type obfuscateType(Type type) { - //unwrap arrays - Type unwrapped = type; - int arrayLevel = 0; - while(unwrapped.getSort() == org.objectweb.asm.Type.ARRAY) { - unwrapped = unwrapped.getElementType(); - arrayLevel++; - } - - //if it's a primitive no operation is needed - if(type.getSort() < org.objectweb.asm.Type.ARRAY) - return type; - - String internalName = type.getInternalName(); - - String internalNameObf; - try { - internalNameObf = this.obfuscateClass(internalName); - return Type.getType(DescriptorBuilder.nameToDescriptor(internalNameObf, arrayLevel)); - } catch(MappingNotFoundException e) { - return type; - } - } - - /** - * Private class used internally for storing information about each - * class. It's private because there is no good reason anyone would - * want to access this outside of this class. - */ - private static class ObfuscationData { - /** - * The unobfuscated name (FQN with '/' instad of '.') of the class. - */ - private final String unobf; - - /** - * The obfuscated internal name (FQN with '/' instad of '.') of the class. - */ - private final String obf; - - /** - * A {@link Map} tying each member's name or signature to its - * obfuscated counterpart. - */ - private final Map members; - - /** - * The constructor. It takes in the names (obfuscated and non-obfuscated) - * of a class. - * @param unobf the unobfuscated name - * @param obf the obfuscated name - */ - private ObfuscationData(String unobf, String obf) { - this.unobf = unobf; - this.obf = obf; - this.members = new HashMap<>(); - } - - /** - * Adds a member to the target class. - * For fields only the names are required; for methods, - * this takes in the full signature ({@code name + " " + space}). - * @param s the String representing the declaration line - */ - public void addMember(String s) { - String[] split = s.trim().split(" "); - if(split.length == 2) //field - members.put(split[0], split[1]); - else if (split.length == 3) //method - members.put(split[0] + " " + split[1], split[2]); - } - - /** - * Gets an obfuscated member given the method name and a method descriptor, - * which may be partial (i.e. not include return type) or null if the member - * is not a method. - * @param memberName member name - * @param methodDescriptor the method descriptor, or null if it's not a method - * @return the requested obfuscated name, or null if nothing was found - * @throws AmbiguousDefinitionException if not enough data was given to uniquely identify a mapping - */ - public String get(String memberName, String methodDescriptor) { - - //find all keys that start with the name - List candidates = members.keySet().stream().filter( - m -> m.split(" ")[0].equals(memberName) - ).collect(Collectors.toList()); - - if(methodDescriptor != null) { - String signature = String.format("%s %s", memberName, methodDescriptor); - candidates = candidates.stream().filter( - m -> m.equals(signature) - ).collect(Collectors.toList()); - } - - switch(candidates.size()) { - case 0: - throw new MappingNotFoundException(String.format( - "%s.%s%s", - this.unobf, - memberName, - methodDescriptor == null ? "" : "()" - )); - case 1: - return members.get(candidates.get(0)); - default: - throw new AmbiguousDefinitionException(String.format( - "Mapper could not uniquely identify member %s.%s%s, found %d!", - this.unobf, - memberName, - methodDescriptor == null ? "" : "()", - candidates.size() - )); - } - } - } -} -- cgit v1.2.3-56-ga3b1 From 3f730c69d3360ae7af030e9e5a4ebd065f8bb05a Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sun, 11 Jun 2023 15:55:22 +0200 Subject: fix: don't call processingEnv before it's initialised --- .../java/ftbsc/lll/processor/LilleroProcessor.java | 32 ++++++++++++++-------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index b1d1f1e..0e170ee 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -48,7 +48,7 @@ public class LilleroProcessor extends AbstractProcessor { /** * An object representing the various options passed to the processor. */ - public final ProcessorOptions options = new ProcessorOptions(processingEnv); + private ProcessorOptions options = null; /** * Method overriding default implementation to manually pass supported options. @@ -59,6 +59,16 @@ public class LilleroProcessor extends AbstractProcessor { return ProcessorOptions.SUPPORTED; } + /** + * Returns the {@link ProcessorOptions} for this instance, creating the object if + * it hasn't been already. + * @return the {@link ProcessorOptions} for this instance + */ + public ProcessorOptions getProcessorOptions() { + if(this.options == null) this.options = new ProcessorOptions(this.processingEnv); + return this.options; + } + /** * Where the actual processing happens. * It filters through whatever annotated class it's fed, and checks whether it contains @@ -90,7 +100,7 @@ public class LilleroProcessor extends AbstractProcessor { } } } - if (!this.options.noServiceProvider && !this.injectors.isEmpty()) { + if (!this.getProcessorOptions().noServiceProvider && !this.injectors.isEmpty()) { generateServiceProvider(); return true; } else return false; @@ -134,7 +144,7 @@ public class LilleroProcessor extends AbstractProcessor { //find class information Patch patchAnn = cl.getAnnotation(Patch.class); ClassContainer targetClass = ClassContainer.from( - patchAnn, Patch::value, patchAnn.innerName(), this.options + patchAnn, Patch::value, patchAnn.innerName(), this.getProcessorOptions() ); //find package information Element packageElement = cl.getEnclosingElement(); @@ -165,10 +175,10 @@ public class LilleroProcessor extends AbstractProcessor { if(type == ProxyType.TYPE) { //find and validate ClassContainer clazz = ClassContainer.findOrFallback( - ClassContainer.from(cl, this.options), + ClassContainer.from(cl, this.getProcessorOptions()), patchAnn, proxyVar.getAnnotation(Find.class), - this.options + this.getProcessorOptions() ); //types can be generated with a single instruction constructorBuilder.addStatement( @@ -184,7 +194,7 @@ public class LilleroProcessor extends AbstractProcessor { null, null, constructorBuilder, - this.options + this.getProcessorOptions() ); } @@ -243,7 +253,7 @@ public class LilleroProcessor extends AbstractProcessor { matchedInjectors.add(injector); toGenerate.put( String.format("%sInjector%d", cl.getSimpleName(), iterationNumber), - new InjectorInfo(injector, tg, targetAnn, this.options) + new InjectorInfo(injector, tg, targetAnn, this.getProcessorOptions()) ); iterationNumber++; //increment is only used by injectors } else { @@ -255,7 +265,7 @@ public class LilleroProcessor extends AbstractProcessor { tg, targetAnn, constructorBuilder, - this.options + this.getProcessorOptions() ); } } @@ -280,9 +290,9 @@ public class LilleroProcessor extends AbstractProcessor { .addMethod(constructorBuilder.build()) .addMethod(buildStringReturnMethod("name", injName)) .addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason)) - .addMethod(buildStringReturnMethod("targetClass", this.options.obfuscateInjectorMetadata ? targetClass.fqnObf : targetClass.fqn)) - .addMethod(buildStringReturnMethod("methodName", this.options.obfuscateInjectorMetadata ? target.nameObf : target.name)) - .addMethod(buildStringReturnMethod("methodDesc", this.options.obfuscateInjectorMetadata ? target.descriptorObf : target.descriptor)) + .addMethod(buildStringReturnMethod("targetClass", this.getProcessorOptions().obfuscateInjectorMetadata ? targetClass.fqnObf : targetClass.fqn)) + .addMethod(buildStringReturnMethod("methodName", this.getProcessorOptions().obfuscateInjectorMetadata ? target.nameObf : target.name)) + .addMethod(buildStringReturnMethod("methodDesc", this.getProcessorOptions().obfuscateInjectorMetadata ? target.descriptorObf : target.descriptor)) .addMethods(generateDummies(cl)) .addMethod(generateInjector(toGenerate.get(injName), this.processingEnv)) .build(); -- cgit v1.2.3-56-ga3b1 From 8e3284a904633b5434104668936be5251e520241 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sat, 26 Aug 2023 22:37:47 +0200 Subject: feat: updated to use mapper 0.1 --- build.gradle | 2 +- .../lll/processor/tools/ProcessorOptions.java | 35 +++++----------------- .../processor/tools/containers/FieldContainer.java | 3 +- .../tools/containers/MethodContainer.java | 3 +- 4 files changed, 12 insertions(+), 31 deletions(-) diff --git a/build.gradle b/build.gradle index 5109f86..74794ea 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation 'com.squareup:javapoet:1.13.0' implementation 'org.ow2.asm:asm-commons:9.5' implementation 'ftbsc:lll:0.4.2' - implementation 'ftbsc.lll:mapper:0.0.2' + implementation 'ftbsc.lll:mapper:0.1.0' } jar { diff --git a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java index de751bc..77d0dad 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java +++ b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java @@ -1,18 +1,14 @@ package ftbsc.lll.processor.tools; import ftbsc.lll.IInjector; -import ftbsc.lll.exceptions.InvalidResourceException; import ftbsc.lll.mapper.IMapper; +import ftbsc.lll.mapper.MapperProvider; import javax.annotation.processing.ProcessingEnvironment; -import java.io.*; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; -import java.util.stream.Collectors; /** * Class in charge of containing, parsing and processing all processor options, @@ -63,28 +59,11 @@ public class ProcessorOptions { public ProcessorOptions(ProcessingEnvironment env) { this.env = env; String location = env.getOptions().get("mappingsFile"); - if(location == null) - this.mapper = null; - else { - InputStream targetStream; - try { - URI target = new URI(location); - targetStream = target.toURL().openStream(); - } catch(URISyntaxException | IOException e) { - //may be a local file path - File f = new File(location); - if(!f.exists()) - throw new InvalidResourceException(location); - try { - targetStream = new FileInputStream(f); - } catch(FileNotFoundException ex) { - throw new InvalidResourceException(location); - } - } - this.mapper = IMapper.getMappers(new BufferedReader( - new InputStreamReader(targetStream, StandardCharsets.UTF_8)).lines().collect(Collectors.toList()) - ).iterator().next(); //TODO: add logic for choosing a specific one - } + if(location != null) { + List lines = MapperProvider.fetchFromLocalOrRemote(location); + this.mapper = MapperProvider.getMapper(lines); + this.mapper.populate(lines, true); + } else this.mapper = null; this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); this.noServiceProvider = parseBooleanArg(env.getOptions().get("noServiceProvider"), false); diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java index 0074ebe..9154193 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java @@ -74,7 +74,8 @@ public class FieldContainer { this.name = this.elem.getSimpleName().toString(); this.descriptor = descriptorFromType(this.elem.asType(), options.env); } - this.descriptorObf = options.mapper == null ? this.descriptor : MappingUtils.obfuscateType(Type.getType(this.descriptor), options.mapper).getDescriptor(); + this.descriptorObf = options.mapper == null ? this.descriptor + : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor(); this.nameObf = findMemberName(parent.fqn, this.name, null, options.mapper); } diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java index f646a1c..2878ec6 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java @@ -79,7 +79,8 @@ public class MethodContainer { this.name = this.elem.getSimpleName().toString(); this.descriptor = descriptorFromExecutableElement(this.elem, options.env); } - this.descriptorObf = options.mapper == null ? this.descriptor : MappingUtils.obfuscateMethodDescriptor(this.descriptor, options.mapper); + this.descriptorObf = options.mapper == null ? this.descriptor + : MappingUtils.mapMethodDescriptor(this.descriptor, options.mapper, false); this.nameObf = findMemberName(parent.fqn, this.name, this.descriptor, options.mapper); } -- cgit v1.2.3-56-ga3b1 From 30f8e080ae5d7bd811abb5b96bc6b85aa89350a8 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sat, 26 Aug 2023 23:55:11 +0200 Subject: feat: updated to use mapper lib 0.2.0 --- build.gradle | 2 +- .../java/ftbsc/lll/processor/LilleroProcessor.java | 22 ++--- .../java/ftbsc/lll/processor/tools/ASTUtils.java | 94 ++++++++++++++-------- .../ftbsc/lll/processor/tools/JavaPoetUtils.java | 6 +- .../processor/tools/containers/ClassContainer.java | 16 ++-- .../processor/tools/containers/FieldContainer.java | 20 ++--- .../tools/containers/MethodContainer.java | 33 +++----- 7 files changed, 101 insertions(+), 92 deletions(-) diff --git a/build.gradle b/build.gradle index 74794ea..2f83ee6 100644 --- a/build.gradle +++ b/build.gradle @@ -22,7 +22,7 @@ dependencies { implementation 'com.squareup:javapoet:1.13.0' implementation 'org.ow2.asm:asm-commons:9.5' implementation 'ftbsc:lll:0.4.2' - implementation 'ftbsc.lll:mapper:0.1.0' + implementation 'ftbsc.lll:mapper:0.2.0' } jar { diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index 0e170ee..55eb81f 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -166,7 +166,7 @@ public class LilleroProcessor extends AbstractProcessor { //take care of TypeProxies and FieldProxies first for(VariableElement proxyVar : finders) { ProxyType type = getProxyType(proxyVar); - if(type == ProxyType.METHOD && proxyVar.getAnnotation(Find.class).name().equals("")) { + if(type == ProxyType.METHOD && proxyVar.getAnnotation(Find.class).name().isEmpty()) { //methods without a specified name will be handled later methodFinders.add(proxyVar); continue; @@ -185,7 +185,7 @@ public class LilleroProcessor extends AbstractProcessor { "super.$L = $T.from($S, 0, $L)", proxyVar.getSimpleName().toString(), TypeProxy.class, - clazz.fqnObf, //use obf name, at runtime it will be obfuscated + clazz.data.nameMapped.replace('/', '.'), //use obf name, at runtime it will be obfuscated clazz.elem == null ? 0 : mapModifiers(clazz.elem.getModifiers()) ); } else if(type == ProxyType.FIELD) @@ -225,11 +225,11 @@ public class LilleroProcessor extends AbstractProcessor { .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) + if(!finderCandidates.isEmpty() && !injectorCandidates.isEmpty()) throw new AmbiguousDefinitionException( String.format("Target specified user %s, but name was used by both a finder and injector.", targetAnn.of()) ); - else if(finderCandidates.size() == 0 && injectorCandidates.size() == 0) + else if(finderCandidates.isEmpty() && injectorCandidates.isEmpty()) processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, String.format( "Found orphan @Target annotation on method %s.%s pointing at method %s, it will be ignored!", @@ -238,11 +238,11 @@ public class LilleroProcessor extends AbstractProcessor { targetAnn.of() ) ); - else if(finderCandidates.size() == 0 && injectorCandidates.size() != 1) + else if(finderCandidates.isEmpty() && injectorCandidates.size() != 1) throw new AmbiguousDefinitionException( String.format("Found multiple candidate injectors for target %s::%s!", cl.getSimpleName(), tg.getSimpleName()) ); - else if(injectorCandidates.size() == 0 && finderCandidates.size() != 1) + else if(injectorCandidates.isEmpty() && finderCandidates.size() != 1) throw new AmbiguousDefinitionException( String.format("Found multiple candidate finders for target %s::%s!", cl.getSimpleName(), tg.getSimpleName()) ); @@ -290,9 +290,13 @@ public class LilleroProcessor extends AbstractProcessor { .addMethod(constructorBuilder.build()) .addMethod(buildStringReturnMethod("name", injName)) .addMethod(buildStringReturnMethod("reason", toGenerate.get(injName).reason)) - .addMethod(buildStringReturnMethod("targetClass", this.getProcessorOptions().obfuscateInjectorMetadata ? targetClass.fqnObf : targetClass.fqn)) - .addMethod(buildStringReturnMethod("methodName", this.getProcessorOptions().obfuscateInjectorMetadata ? target.nameObf : target.name)) - .addMethod(buildStringReturnMethod("methodDesc", this.getProcessorOptions().obfuscateInjectorMetadata ? target.descriptorObf : target.descriptor)) + .addMethod(buildStringReturnMethod("targetClass", this.getProcessorOptions().obfuscateInjectorMetadata + ? targetClass.data.nameMapped.replace('/', '.') + : targetClass.data.name.replace('/', '.'))) + .addMethod(buildStringReturnMethod("methodName", this.getProcessorOptions().obfuscateInjectorMetadata + ? target.data.nameMapped : target.data.signature.name)) + .addMethod(buildStringReturnMethod("methodDesc", this.getProcessorOptions().obfuscateInjectorMetadata + ? target.descriptorObf : target.data.signature.name)) .addMethods(generateDummies(cl)) .addMethod(generateInjector(toGenerate.get(injName), this.processingEnv)) .build(); diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java index b33a906..952abd1 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java @@ -4,6 +4,9 @@ import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.MappingNotFoundException; import ftbsc.lll.exceptions.NotAProxyException; import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.data.ClassData; +import ftbsc.lll.mapper.tools.data.FieldData; +import ftbsc.lll.mapper.tools.data.MethodData; import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; import ftbsc.lll.mapper.IMapper; @@ -210,35 +213,59 @@ public class ASTUtils { } /** - * Finds the class name and maps it to the correct format. - * @param name the fully qualified name of the class to convert - * @param options the {@link ProcessorOptions} to be used + * Gets the {@link ClassData} corresponding to the given fully-qualified name, + * or creates a false one with the same, non-obfuscated name twice. + * @param name the internal name of the class to convert + * @param mapper the {@link IMapper} to use, may be null * @return the fully qualified class name - * @since 0.3.0 + * @since 0.6.1 */ - public static String findClassName(String name, ProcessorOptions options) { + public static ClassData getClassData(String name, IMapper mapper) { try { - return options.mapper == null ? name : options.mapper.obfuscateClass(name).replace('/', '.'); - } catch(MappingNotFoundException e) { - return name; - } + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getClassData(name); + } catch(MappingNotFoundException ignored) {} + return new ClassData(name, name); } /** - * Finds the member name and maps it to the correct format. - * @param parentFQN the unobfuscated FQN of the parent class - * @param memberName the name of the member - * @param methodDescriptor the descriptor of the method, may be null + * Gets the {@link MethodData} corresponding to the method matching the given + * name, parent and descriptor, or creates a dummy one with fake data if no + * valid mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param descriptor the descriptor of the method * @param mapper the {@link IMapper} to use, may be null - * @return the internal class name - * @since 0.3.0 + * @return the fully qualified class name + * @since 0.6.1 */ - public static String findMemberName(String parentFQN, String memberName, String methodDescriptor, IMapper mapper) { + public static MethodData getMethodData(String parent, String name, String descriptor, IMapper mapper) { try { - return mapper == null ? memberName : mapper.obfuscateMember(parentFQN, memberName, methodDescriptor); - } catch(MappingNotFoundException e) { - return memberName; - } + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getMethodData(parent, name, descriptor); + } catch(MappingNotFoundException ignored) {} + return new MethodData(getClassData(name, mapper), name, name, descriptor); + } + + /** + * Gets the {@link FieldData} corresponding to the field matching the given + * name and parent, or creates a dummy one with fake data if no valid + * mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param mapper the {@link IMapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static FieldData getFieldData(String parent, String name, IMapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getFieldData(parent, name); + } catch(MappingNotFoundException ignored) {} + return new FieldData(getClassData(name, mapper), name, name); } /** @@ -258,7 +285,7 @@ public class ASTUtils { ClassContainer parent, String name, String descr, boolean strict, boolean field, ProcessingEnvironment env) { if(parent.elem == null) - throw new TargetNotFoundException("parent class", parent.fqn); + throw new TargetNotFoundException("parent class", parent.data.name); //try to find by name List candidates = parent.elem.getEnclosedElements() .stream() @@ -266,36 +293,37 @@ public class ASTUtils { .filter(e -> e.getSimpleName().contentEquals(name)) .collect(Collectors.toList()); - if(candidates.size() == 0) - throw new TargetNotFoundException(field ? "field" : "method", name, parent.fqn); + if(candidates.isEmpty()) + throw new TargetNotFoundException(field ? "field" : "method", name, parent.data.name); if(candidates.size() == 1 && (!strict || descr == null)) return candidates.get(0); if(descr == null) { - throw new AmbiguousDefinitionException( - String.format("Found %d members named %s in class %s!", candidates.size(), name, parent.fqn) - ); + throw new AmbiguousDefinitionException(String.format( + "Found %d members named %s in class %s!", candidates.size(), name, parent.data.name)); } else { 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(), env).equals(descr)) - throw new TargetNotFoundException("field", String.format("%s with descriptor %s", name, descr), parent.fqn); + throw new TargetNotFoundException("field", String.format( + "%s with descriptor %s", name, descr), parent.data.name); } else { candidates = candidates.stream() .map(e -> (ExecutableElement) e) .filter(strict ? c -> descr.equals(descriptorFromExecutableElement(c, env)) - : c -> descr.split("\\)")[0].equalsIgnoreCase(descriptorFromExecutableElement(c, env).split("\\)")[0]) + : c -> descr.split("\\)")[0].equalsIgnoreCase( + descriptorFromExecutableElement(c, env).split("\\)")[0]) ).collect(Collectors.toList()); } - if(candidates.size() == 0) - throw new TargetNotFoundException("method", String.format("%s %s", name, descr), parent.fqn); + if(candidates.isEmpty()) + throw new TargetNotFoundException("method", String.format( + "%s %s", name, descr), parent.data.name); if(candidates.size() > 1) - throw new AmbiguousDefinitionException( - String.format("Found %d methods named %s in class %s!", candidates.size(), name, parent.fqn) - ); + throw new AmbiguousDefinitionException(String.format( + "Found %d methods named %s in class %s!", candidates.size(), name, parent.data.name)); return candidates.get(0); } } diff --git a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java index 77bbc2b..5709574 100644 --- a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java @@ -65,13 +65,13 @@ public class JavaPoetUtils { if(isMethod) { MethodContainer mc = MethodContainer.from(stub, t, f, options); descriptorObf = mc.descriptorObf; - nameObf = mc.nameObf; + nameObf = mc.data.nameMapped; parent = mc.parent; target = mc.elem; } else { FieldContainer fc = FieldContainer.from(var, options); descriptorObf = fc.descriptorObf; - nameObf = fc.nameObf; + nameObf = fc.data.nameMapped; parent = fc.parent; target = fc.elem; } @@ -88,7 +88,7 @@ public class JavaPoetUtils { con.addStatement( "$L.setParent($S, $L)", builderName, - parent.fqnObf, + parent.data.nameMapped.replace('/', '.'), parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) ); 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 2c10dd6..b145e71 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java @@ -1,6 +1,7 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.data.ClassData; import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.tools.ProcessorOptions; @@ -20,15 +21,9 @@ import static ftbsc.lll.processor.tools.ASTUtils.*; */ public class ClassContainer { /** - * The fully-qualified name of the class. + * The {@link ClassData} for the class represented by this container. */ - public final String fqn; - - /** - * The obfuscated fully-qualified name of the class. - * If the mapper passed is null, then this will be identical to {@link #fqn} - */ - public final String fqnObf; + public final ClassData data; /** * The {@link Element} corresponding to the class. @@ -84,8 +79,7 @@ public class ClassContainer { throw new TargetNotFoundException("class", inner); } } - this.fqn = fqnBuilder.toString(); - this.fqnObf = findClassName(this.fqn, options); + this.data = getClassData(fqnBuilder.toString(), options.mapper); this.elem = elem; } @@ -131,6 +125,6 @@ public class ClassContainer { public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) { if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options); ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options); - return cl.fqn.equals("java.lang.Object") ? fallback : cl; + return cl.data.name.equals("java/lang/Object") ? fallback : cl; } } diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java index 9154193..6ef4741 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java @@ -2,6 +2,7 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.FieldData; import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.tools.ProcessorOptions; @@ -21,21 +22,15 @@ import static ftbsc.lll.processor.tools.ASTUtils.*; */ public class FieldContainer { /** - * The name of the field. + * The {@link FieldData} for the field represented by this container. */ - public final String name; + public final FieldData data; /** * 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 obfuscated descriptor of the field. * If the mapper passed is null, then this will be identical to {@link #descriptor}. @@ -67,16 +62,15 @@ public class FieldContainer { 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, options.env); - this.name = this.elem.getSimpleName().toString(); this.descriptor = descriptorFromType(this.elem.asType(), options.env); + name = this.elem.getSimpleName().toString(); } + this.data = getFieldData(parent.data.name, name, options.mapper); this.descriptorObf = options.mapper == null ? this.descriptor : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor(); - this.nameObf = findMemberName(parent.fqn, this.name, null, options.mapper); } /** @@ -96,7 +90,7 @@ public class FieldContainer { ClassContainer.from((TypeElement) finder.getEnclosingElement(), options), patchAnn, f, options ); - String name = f.name().equals("") ? finder.getSimpleName().toString() : f.name(); + String name = f.name().isEmpty() ? finder.getSimpleName().toString() : f.name(); String descriptor; TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, options.env); if(fieldType.toString().equals("java.lang.Object")) { @@ -106,7 +100,7 @@ public class FieldContainer { descriptor = //jank af but this is temporary anyway "L" + ClassContainer.from( f, Find::type, f.typeInner(), options - ).fqnObf.replace('.', '/') + ";"; + ).data.nameMapped + ";"; else descriptor = descriptorFromType(fieldType, options.env); } diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java index 2878ec6..266858d 100644 --- a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java +++ b/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java @@ -3,6 +3,7 @@ package ftbsc.lll.processor.tools.containers; import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.TargetNotFoundException; import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.MethodData; import ftbsc.lll.processor.annotations.Find; import ftbsc.lll.processor.annotations.Patch; import ftbsc.lll.processor.annotations.Target; @@ -20,24 +21,14 @@ import static ftbsc.lll.processor.tools.ASTUtils.*; */ public class MethodContainer { /** - * The name of the method. + * The {@link MethodData} for the method represented by this container. */ - 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; + public final MethodData data; /** * The obfuscated descriptor of the field. - * If the mapper passed is null, then this will be identical to {@link #descriptor}. + * If the mapper passed is null, this will be identical to the one inside + * {@link #data}. */ public final String descriptorObf; @@ -69,19 +60,17 @@ public class MethodContainer { 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 { ExecutableElement tmp = (ExecutableElement) findMember( parent, name, descriptor, descriptor != null && strict,false, options.env ); this.elem = bridge ? findSyntheticBridge((TypeElement) this.parent.elem, tmp, options.env) : tmp; - this.name = this.elem.getSimpleName().toString(); - this.descriptor = descriptorFromExecutableElement(this.elem, options.env); + name = this.elem.getSimpleName().toString(); + descriptor = descriptorFromExecutableElement(this.elem, options.env); } - this.descriptorObf = options.mapper == null ? this.descriptor - : MappingUtils.mapMethodDescriptor(this.descriptor, options.mapper, false); - this.nameObf = findMemberName(parent.fqn, this.name, this.descriptor, options.mapper); + this.data = getMethodData(parent.data.name, name, descriptor, options.mapper); + this.descriptorObf = options.mapper == null ? this.data.signature.descriptor + : MappingUtils.mapMethodDescriptor(this.data.signature.descriptor, options.mapper, false); } /** @@ -101,7 +90,7 @@ public class MethodContainer { ClassContainer parent = ClassContainer.findOrFallback( ClassContainer.from((TypeElement) stub.getEnclosingElement(), options), patchAnn, f, options ); - String name = !t.methodName().equals("") + String name = !t.methodName().isEmpty() ? t.methodName() //name was specified in target : stub.getSimpleName().toString(); String descriptor = t.strict() -- cgit v1.2.3-56-ga3b1 From 3dd86a41872585b7cbba4f6b4e1407316bf889c3 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Fri, 1 Sep 2023 12:42:19 +0200 Subject: chore: updated to mapping library 0.3.0 --- build.gradle | 5 ++--- src/main/java/ftbsc/lll/processor/tools/ASTUtils.java | 14 +++++++------- .../java/ftbsc/lll/processor/tools/ProcessorOptions.java | 9 ++++----- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/build.gradle b/build.gradle index 2f83ee6..cdcad7b 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,7 @@ archivesBaseName = 'processor' version = gitVersion() java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = targetCompatibility JavaVersion.VERSION_1_8 withSourcesJar() withJavadocJar() } @@ -22,7 +21,7 @@ dependencies { implementation 'com.squareup:javapoet:1.13.0' implementation 'org.ow2.asm:asm-commons:9.5' implementation 'ftbsc:lll:0.4.2' - implementation 'ftbsc.lll:mapper:0.2.0' + implementation 'ftbsc.lll:mapper:0.3.0' } jar { diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java index 952abd1..326f52d 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java @@ -4,12 +4,12 @@ import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.MappingNotFoundException; import ftbsc.lll.exceptions.NotAProxyException; import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.Mapper; import ftbsc.lll.mapper.tools.data.ClassData; import ftbsc.lll.mapper.tools.data.FieldData; import ftbsc.lll.mapper.tools.data.MethodData; import ftbsc.lll.processor.annotations.Target; import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.mapper.IMapper; import ftbsc.lll.proxies.ProxyType; import javax.annotation.processing.ProcessingEnvironment; @@ -216,11 +216,11 @@ public class ASTUtils { * Gets the {@link ClassData} corresponding to the given fully-qualified name, * or creates a false one with the same, non-obfuscated name twice. * @param name the internal name of the class to convert - * @param mapper the {@link IMapper} to use, may be null + * @param mapper the {@link Mapper} to use, may be null * @return the fully qualified class name * @since 0.6.1 */ - public static ClassData getClassData(String name, IMapper mapper) { + public static ClassData getClassData(String name, Mapper mapper) { try { name = name.replace('.', '/'); //just in case if(mapper != null) @@ -236,11 +236,11 @@ public class ASTUtils { * @param parent the internal name of the parent class * @param name the name of the member * @param descriptor the descriptor of the method - * @param mapper the {@link IMapper} to use, may be null + * @param mapper the {@link Mapper} to use, may be null * @return the fully qualified class name * @since 0.6.1 */ - public static MethodData getMethodData(String parent, String name, String descriptor, IMapper mapper) { + public static MethodData getMethodData(String parent, String name, String descriptor, Mapper mapper) { try { name = name.replace('.', '/'); //just in case if(mapper != null) @@ -255,11 +255,11 @@ public class ASTUtils { * mapping is found. * @param parent the internal name of the parent class * @param name the name of the member - * @param mapper the {@link IMapper} to use, may be null + * @param mapper the {@link Mapper} to use, may be null * @return the fully qualified class name * @since 0.6.1 */ - public static FieldData getFieldData(String parent, String name, IMapper mapper) { + public static FieldData getFieldData(String parent, String name, Mapper mapper) { try { name = name.replace('.', '/'); //just in case if(mapper != null) diff --git a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java index 77d0dad..70d43cc 100644 --- a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java +++ b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java @@ -1,8 +1,8 @@ package ftbsc.lll.processor.tools; import ftbsc.lll.IInjector; -import ftbsc.lll.mapper.IMapper; import ftbsc.lll.mapper.MapperProvider; +import ftbsc.lll.mapper.tools.Mapper; import javax.annotation.processing.ProcessingEnvironment; import java.util.Arrays; @@ -30,10 +30,10 @@ public class ProcessorOptions { public final ProcessingEnvironment env; /** - * The {@link IMapper} used to convert classes and variables + * The {@link Mapper} used to convert classes and variables * to their obfuscated equivalent. Will be null when no mapper is in use. */ - public final IMapper mapper; + public final Mapper mapper; /** * Whether the processor should issue warnings when compiling code anonymous @@ -61,8 +61,7 @@ public class ProcessorOptions { String location = env.getOptions().get("mappingsFile"); if(location != null) { List lines = MapperProvider.fetchFromLocalOrRemote(location); - this.mapper = MapperProvider.getMapper(lines); - this.mapper.populate(lines, true); + this.mapper = MapperProvider.getMapper(lines).getMapper(lines, true); } else this.mapper = null; this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); -- cgit v1.2.3-56-ga3b1 From bf32e56a863e83b82efe9732c585979afb0e62c3 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 24 Jan 2024 17:21:42 +0100 Subject: chore: internal reorganisation --- .../java/ftbsc/lll/processor/LilleroProcessor.java | 11 +- .../java/ftbsc/lll/processor/ProcessorOptions.java | 85 +++++ .../lll/processor/containers/ClassContainer.java | 130 +++++++ .../lll/processor/containers/FieldContainer.java | 109 ++++++ .../lll/processor/containers/InjectorInfo.java | 46 +++ .../lll/processor/containers/MethodContainer.java | 102 +++++ .../java/ftbsc/lll/processor/tools/ASTUtils.java | 409 --------------------- .../ftbsc/lll/processor/tools/JavaPoetUtils.java | 170 --------- .../lll/processor/tools/ProcessorOptions.java | 85 ----- .../processor/tools/containers/ClassContainer.java | 130 ------- .../processor/tools/containers/FieldContainer.java | 109 ------ .../processor/tools/containers/InjectorInfo.java | 46 --- .../tools/containers/MethodContainer.java | 102 ----- .../java/ftbsc/lll/processor/utils/ASTUtils.java | 409 +++++++++++++++++++++ .../ftbsc/lll/processor/utils/JavaPoetUtils.java | 171 +++++++++ 15 files changed, 1057 insertions(+), 1057 deletions(-) create mode 100644 src/main/java/ftbsc/lll/processor/ProcessorOptions.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/ClassContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/FieldContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java create mode 100644 src/main/java/ftbsc/lll/processor/containers/MethodContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/ASTUtils.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java delete mode 100644 src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java create mode 100644 src/main/java/ftbsc/lll/processor/utils/ASTUtils.java create mode 100644 src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java diff --git a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java index 55eb81f..b693592 100644 --- a/src/main/java/ftbsc/lll/processor/LilleroProcessor.java +++ b/src/main/java/ftbsc/lll/processor/LilleroProcessor.java @@ -8,10 +8,9 @@ import ftbsc.lll.IInjector; import ftbsc.lll.exceptions.AmbiguousDefinitionException; import ftbsc.lll.exceptions.OrphanElementException; import ftbsc.lll.processor.annotations.*; -import ftbsc.lll.processor.tools.ProcessorOptions; -import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.processor.tools.containers.InjectorInfo; -import ftbsc.lll.processor.tools.containers.MethodContainer; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.processor.containers.InjectorInfo; +import ftbsc.lll.processor.containers.MethodContainer; import ftbsc.lll.proxies.ProxyType; import ftbsc.lll.proxies.impl.TypeProxy; @@ -29,8 +28,8 @@ import java.io.PrintWriter; import java.util.*; import java.util.stream.Collectors; -import static ftbsc.lll.processor.tools.ASTUtils.*; -import static ftbsc.lll.processor.tools.JavaPoetUtils.*; +import static ftbsc.lll.processor.utils.ASTUtils.*; +import static ftbsc.lll.processor.utils.JavaPoetUtils.*; /** * The actual annotation processor behind the magic. diff --git a/src/main/java/ftbsc/lll/processor/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/ProcessorOptions.java new file mode 100644 index 0000000..5972631 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/ProcessorOptions.java @@ -0,0 +1,85 @@ +package ftbsc.lll.processor; + +import ftbsc.lll.IInjector; +import ftbsc.lll.mapper.MapperProvider; +import ftbsc.lll.mapper.tools.Mapper; + +import javax.annotation.processing.ProcessingEnvironment; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Class in charge of containing, parsing and processing all processor options, + * from the simpler booleans to the more complicated mapper. + */ +public class ProcessorOptions { + + /** + * A {@link Set} of options currently supported by the processor. + */ + public static final Set SUPPORTED = new HashSet<>(Arrays.asList( + "mappingsFile", "anonymousClassWarning", "obfuscateInjectorMetadata", + "noServiceProvider" + )); + + /** + * The environment the processor is acting in. + */ + public final ProcessingEnvironment env; + + /** + * The {@link Mapper} used to convert classes and variables + * to their obfuscated equivalent. Will be null when no mapper is in use. + */ + public final Mapper mapper; + + /** + * Whether the processor should issue warnings when compiling code anonymous + * classes which can't be checked for validity. + */ + public final boolean anonymousClassWarning; + + /** + * Whether injector metadata (what is returned by the functions of {@link IInjector}) + * is to use obfuscated names instead of its normal names. + */ + public final boolean obfuscateInjectorMetadata; + + /** + * Whether the processor should skip the generation of the service provider. + */ + public final boolean noServiceProvider; + + /** + * The public constructor, parses and stores all given arguments. + * @param env the environment the processor is working in + */ + public ProcessorOptions(ProcessingEnvironment env) { + this.env = env; + String location = env.getOptions().get("mappingsFile"); + if(location != null) { + List lines = MapperProvider.fetchFromLocalOrRemote(location); + this.mapper = MapperProvider.getMapper(lines).getMapper(lines, true); + } else this.mapper = null; + this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); + this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); + this.noServiceProvider = parseBooleanArg(env.getOptions().get("noServiceProvider"), false); + } + + /** + * Parses a boolean arg from a String. + * @param arg the arg to parse + * @return the parsed boolean + */ + private static boolean parseBooleanArg(String arg, boolean defaultValue) { + if(arg == null) return defaultValue; + try { // 0 = false, any other integer = true + int i = Integer.parseInt(arg); + return i != 0; + } catch(NumberFormatException ignored) { + return Boolean.parseBoolean(arg); + } + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java new file mode 100644 index 0000000..297349a --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/ClassContainer.java @@ -0,0 +1,130 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.data.ClassData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import java.lang.annotation.Annotation; +import java.util.function.Function; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a class. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class ClassContainer { + /** + * The {@link ClassData} for the class represented by this container. + */ + public final ClassData data; + + /** + * The {@link Element} corresponding to the class. + * May only be null intentionally i.e. when the associated element is + * an anonymous class or a child of an anonymous class. + */ + public final Element elem; + + /** + * Private constructor, called from {@link #from(Annotation, Function, String, ProcessorOptions)}. + * @param fqn the fully-qualified name of the target class + * @param innerNames an array of Strings containing the path to the inner class, may be null + * @param options the {@link ProcessorOptions} to be used + */ + private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) { + //find and validate + Element elem = options.env.getElementUtils().getTypeElement(fqn); + + if(elem == null) + throw new TargetNotFoundException("class", fqn); + + StringBuilder fqnBuilder = new StringBuilder( + internalNameFromType(elem.asType(), options.env).replace('/', '.') + ); + + if(innerNames != null) { + for(String inner : innerNames) { + if(inner == null) continue; + fqnBuilder.append("$").append(inner); + try { + int anonClassCounter = Integer.parseInt(inner); + //anonymous classes cannot be validated! + if(options.anonymousClassWarning) + options.env.getMessager().printMessage( + Diagnostic.Kind.WARNING, + String.format( + "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!", + fqnBuilder, anonClassCounter + ) + ); + elem = null; + break; + } catch(NumberFormatException exc) { + elem = elem + .getEnclosedElements() + .stream() + .filter(e -> e instanceof TypeElement) + .filter(e -> e.getSimpleName().contentEquals(inner)) + .findFirst() + .orElse(null); + } + if(elem == null) + throw new TargetNotFoundException("class", inner); + } + } + this.data = getClassData(fqnBuilder.toString(), options.mapper); + this.elem = elem; + } + + /** + * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. + * @param ann the annotation containing the class + * @param classFunction the annotation function returning the class + * @param innerName a string containing the inner class name or nothing + * @param options the {@link ProcessorOptions} to be used + * @param 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 innerName, ProcessorOptions options) { + String fqn; + String[] inner; + fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString(); + inner = innerName.equals("") ? null : innerName.split("//$"); + return new ClassContainer(fqn, inner, options); + } + + /** + * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. + * @param cl the {@link TypeElement} representing the class + * @param options the {@link ProcessorOptions} to be used + * @return the fully qualified name of the given class + * @since 0.6.0 + */ + public static ClassContainer from(TypeElement cl, ProcessorOptions options) { + return new ClassContainer(cl.getQualifiedName().toString(), null, options); + } + + /** + * Finds and builds a {@link ClassContainer} based on information contained + * within {@link Patch} or a {@link Find} annotations, else returns a fallback. + * @param fallback the {@link ClassContainer} it falls back on + * @param p the {@link Patch} annotation to get info from + * @param f the {@link Find} annotation to get info from + * @param options the {@link ProcessorOptions} to be used + * @return the built {@link ClassContainer} or the fallback if not enough information was present + * @since 0.5.0 + */ + public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) { + if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options); + ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options); + return cl.data.name.equals("java/lang/Object") ? fallback : cl; + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java new file mode 100644 index 0000000..2299c93 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/FieldContainer.java @@ -0,0 +1,109 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.FieldData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.ProcessorOptions; +import org.objectweb.asm.Type; + +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a field. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class FieldContainer { + /** + * The {@link FieldData} for the field represented by this container. + */ + public final FieldData data; + + /** + * The descriptor of the field. + */ + public final String descriptor; + + /** + * The obfuscated descriptor of the field. + * If the mapper passed is null, then this will be identical to {@link #descriptor}. + */ + public final String descriptorObf; + + /** + * The {@link ClassContainer} representing the parent of this field. + */ + public final ClassContainer parent; + + /** + * The {@link VariableElement} corresponding to the field. + * May only be null intentionally i.e. when the field is + * a child of an anonymous class. + */ + public final VariableElement elem; + + /** + * Private constructor, called from {@link #from(VariableElement, ProcessorOptions)}. + * @param parent the {@link ClassContainer} representing the parent + * @param name the fully-qualified name of the target field + * @param descriptor the descriptor of the target field, may be null for verifiable fields + * @param options the {@link ProcessorOptions} to be used + */ + private FieldContainer(ClassContainer parent, String name, String descriptor, ProcessorOptions options) { + this.parent = parent; + if(parent.elem == null) { //unverified + if(descriptor == null) + throw new AmbiguousDefinitionException("Cannot use name-based lookups for fields of unverifiable classes!"); + this.elem = null; + this.descriptor = descriptor; + } else { + this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true, options.env); + this.descriptor = descriptorFromType(this.elem.asType(), options.env); + name = this.elem.getSimpleName().toString(); + } + this.data = getFieldData(parent.data.name, name, options.mapper); + this.descriptorObf = options.mapper == null ? this.descriptor + : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor(); + } + + /** + * Finds a {@link FieldContainer} from a finder. + * @param finder the {@link VariableElement} annotated with {@link Find} for this field + * @param options the {@link ProcessorOptions} to be used + * @return the built {@link FieldContainer} + * @since 0.5.0 + */ + public static FieldContainer from(VariableElement finder, ProcessorOptions options) { + //the parent always has a @Patch annotation + Patch patchAnn = finder.getEnclosingElement().getAnnotation(Patch.class); + //the finder always has a @Find annotation + Find f = finder.getAnnotation(Find.class); + + ClassContainer parent = ClassContainer.findOrFallback( + ClassContainer.from((TypeElement) finder.getEnclosingElement(), options), patchAnn, f, options + ); + + String name = f.name().isEmpty() ? finder.getSimpleName().toString() : f.name(); + String descriptor; + TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, options.env); + if(fieldType.toString().equals("java.lang.Object")) { + descriptor = null; + } else { + if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive()) + descriptor = //jank af but this is temporary anyway + "L" + ClassContainer.from( + f, Find::type, f.typeInner(), options + ).data.nameMapped + ";"; + else descriptor = descriptorFromType(fieldType, options.env); + } + + return new FieldContainer(parent, name, descriptor, options); + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java new file mode 100644 index 0000000..d56d846 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/InjectorInfo.java @@ -0,0 +1,46 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.processor.annotations.Injector; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.ExecutableElement; + +/** + * Container for information about a class that is to be generated. + */ +public class InjectorInfo { + /** + * The {@link ExecutableElement} corresponding to the injector method. + */ + public final ExecutableElement injector; + + /** + * The {@link ExecutableElement} corresponding to the target method stub. + */ + public final ExecutableElement targetStub; + + /** + * The reason for the injection. + */ + public final String reason; + + /** + * The {@link MethodContainer} corresponding to the target method. + */ + public final MethodContainer target; + + /** + * Public constructor. + * @param injector the injector {@link ExecutableElement} + * @param targetStub the target {@link ExecutableElement} + * @param targetAnn the relevant {@link Target} annotation + * @param options the {@link ProcessorOptions} to be used + */ + public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn, ProcessorOptions options) { + this.injector = injector; + this.targetStub = targetStub; + this.reason = injector.getAnnotation(Injector.class).reason(); + this.target = MethodContainer.from(targetStub, targetAnn, null, options); + } +} diff --git a/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java new file mode 100644 index 0000000..7d15599 --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/containers/MethodContainer.java @@ -0,0 +1,102 @@ +package ftbsc.lll.processor.containers; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.MappingUtils; +import ftbsc.lll.mapper.tools.data.MethodData; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Patch; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.ProcessorOptions; + +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.TypeElement; + +import static ftbsc.lll.processor.utils.ASTUtils.*; + +/** + * Container for information about a method. + * Used internally for efficiency reasons. + * @since 0.5.0 + */ +public class MethodContainer { + /** + * The {@link MethodData} for the method represented by this container. + */ + public final MethodData data; + + /** + * The obfuscated descriptor of the field. + * If the mapper passed is null, this will be identical to the one inside + * {@link #data}. + */ + public final String descriptorObf; + + /** + * The {@link ClassContainer} representing the parent of this method. + */ + public final ClassContainer parent; + + /** + * The {@link ExecutableElement} corresponding to the method. + * May only be null intentionally i.e. when the method is + * a child of an anonymous class. + */ + public final ExecutableElement elem; + + /** + * Private constructor, called from + * {@link #from(ExecutableElement, Target, Find, ProcessorOptions)}. + * @param parent the {@link ClassContainer} representing the parent + * @param name the fully-qualified name of the target method + * @param descriptor the descriptor of the target method + * @param strict whether the matching should be strict (see {@link Target#strict()} for more info) + * @param bridge whether the "bridge" should be matched instead (see {@link Target#bridge()} for more info) + * @param options the {@link ProcessorOptions} to be used + */ + private MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, boolean bridge, ProcessorOptions options) { + this.parent = parent; + if(parent.elem == null) { //unverified + if(descriptor == null) + throw new AmbiguousDefinitionException("Cannot use name-based lookups for methods of unverifiable classes!"); + this.elem = null; + } else { + ExecutableElement tmp = (ExecutableElement) findMember( + parent, name, descriptor, descriptor != null && strict,false, options.env + ); + this.elem = bridge ? findSyntheticBridge((TypeElement) this.parent.elem, tmp, options.env) : tmp; + name = this.elem.getSimpleName().toString(); + descriptor = descriptorFromExecutableElement(this.elem, options.env); + } + this.data = getMethodData(parent.data.name, name, descriptor, options.mapper); + this.descriptorObf = options.mapper == null ? this.data.signature.descriptor + : MappingUtils.mapMethodDescriptor(this.data.signature.descriptor, options.mapper, false); + } + + /** + * Builds the {@link MethodContainer} corresponding to a stub annotated with {@link Target}. + * @param stub the {@link ExecutableElement} for the stub + * @param t the {@link Target} annotation relevant to this case + * @param f the {@link Find} annotation containing fallback data, may be null + * @param options the {@link ProcessorOptions} to be used + * @return the {@link MethodContainer} corresponding to the method + * @throws AmbiguousDefinitionException if it finds more than one candidate + * @throws TargetNotFoundException if it finds no valid candidate + * @since 0.3.0 + */ + public static MethodContainer from(ExecutableElement stub, Target t, Find f, ProcessorOptions options) { + //the parent always has a @Patch annotation + Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class); + ClassContainer parent = ClassContainer.findOrFallback( + ClassContainer.from((TypeElement) stub.getEnclosingElement(), options), patchAnn, f, options + ); + String name = !t.methodName().isEmpty() + ? t.methodName() //name was specified in target + : stub.getSimpleName().toString(); + String descriptor = t.strict() + ? descriptorFromExecutableElement(stub, options.env) + : null; + + return new MethodContainer(parent, name, descriptor, t.strict(), t.bridge(), options); + } +} diff --git a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java b/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java deleted file mode 100644 index 326f52d..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/ASTUtils.java +++ /dev/null @@ -1,409 +0,0 @@ -package ftbsc.lll.processor.tools; - -import ftbsc.lll.exceptions.AmbiguousDefinitionException; -import ftbsc.lll.exceptions.MappingNotFoundException; -import ftbsc.lll.exceptions.NotAProxyException; -import ftbsc.lll.exceptions.TargetNotFoundException; -import ftbsc.lll.mapper.tools.Mapper; -import ftbsc.lll.mapper.tools.data.ClassData; -import ftbsc.lll.mapper.tools.data.FieldData; -import ftbsc.lll.mapper.tools.data.MethodData; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.containers.ClassContainer; -import ftbsc.lll.proxies.ProxyType; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import javax.lang.model.type.*; -import java.lang.annotation.Annotation; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; - -/** - * Collection of AST-related static utils that didn't really fit into the main class. - */ -public class ASTUtils { - /** - * Finds, among the methods of a class cl, the one annotated with ann, and tries to build - * an {@link Element} from it. - * @param parent the parent {@link Element} to the desired element - * @param ann the {@link Class} corresponding to the desired annotation - * @param the type of {@link Element} to use - * @return a {@link List} of {@link Element}s annotated with the given annotation - * @since 0.2.0 - */ - @SuppressWarnings("unchecked") - public static List findAnnotatedElement(Element parent, Class ann) { - return parent.getEnclosedElements() - .stream() - .filter(e -> e.getAnnotationsByType(ann).length != 0) - .map(e -> (T) e) - .collect(Collectors.toList()); - } - - /** - * Maps a {@link javax.lang.model.element.Modifier} to its reflective - * {@link java.lang.reflect.Modifier} equivalent. - * @param m the {@link Modifier} to map - * @return an integer representing the modifier - * @see java.lang.reflect.Modifier - * @since 0.2.0 - */ - public static int mapModifier(Modifier m) { - switch(m) { - case PUBLIC: - return java.lang.reflect.Modifier.PUBLIC; - case PROTECTED: - return java.lang.reflect.Modifier.PROTECTED; - case PRIVATE: - return java.lang.reflect.Modifier.PRIVATE; - case ABSTRACT: - return java.lang.reflect.Modifier.ABSTRACT; - case STATIC: - return java.lang.reflect.Modifier.STATIC; - case FINAL: - return java.lang.reflect.Modifier.FINAL; - case TRANSIENT: - return java.lang.reflect.Modifier.TRANSIENT; - case VOLATILE: - return java.lang.reflect.Modifier.VOLATILE; - case SYNCHRONIZED: - return java.lang.reflect.Modifier.SYNCHRONIZED; - case NATIVE: - return java.lang.reflect.Modifier.NATIVE; - case STRICTFP: - return java.lang.reflect.Modifier.STRICT; - default: - return 0; - } - } - - /** - * Takes in a {@link Collection} of AST {@link Modifier}s and - * returns them mapped to their reflective integer equivalent. - * @param modifiers the {@link Modifier}s - * @return an integer value representing them - * @since 0.5.0 - */ - public static int mapModifiers(Collection modifiers) { - int i = 0; - for(Modifier m : modifiers) - i |= mapModifier(m); - return i; - } - - /** - * Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it. - * @param ann the annotation containing the class - * @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 a {@link TypeMirror} representing the requested {@link Class} - * @since 0.3.0 - */ - public static TypeMirror getTypeFromAnnotation( - T ann, Function> classFunction, ProcessingEnvironment env) { - try { - String fqn = classFunction.apply(ann).getCanonicalName(); - if(fqn == null) - fqn = ""; - return env.getElementUtils().getTypeElement(fqn).asType(); - } catch(MirroredTypeException e) { - return e.getTypeMirror(); - } - } - - /** - * Gets the internal name from an {@link TypeMirror}. - * @param type the {@link TypeMirror} in question - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the internal name at compile time, or null if it wasn't a qualifiable - * @since 0.5.1 - */ - public static String internalNameFromType(TypeMirror type, ProcessingEnvironment env) { - //needed to actually turn elem into a TypeVariable, find it ignoring generics - Element elem = env.getTypeUtils().asElement(env.getTypeUtils().erasure(type)); - StringBuilder fqnBuilder = new StringBuilder(); - while(elem.getEnclosingElement() != null && elem.getEnclosingElement().getKind() != ElementKind.PACKAGE) { - fqnBuilder - .insert(0, elem.getSimpleName().toString()) - .insert(0, "$"); - elem = elem.getEnclosingElement(); - } - return fqnBuilder - .insert(0, env.getTypeUtils().erasure(elem.asType()).toString()) - .toString() - .replace('.', '/'); - } - - /** - * Builds a type descriptor from the given {@link TypeMirror}. - * @param t the {@link TypeMirror} representing the desired type - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return a {@link String} containing the relevant descriptor - */ - public static String descriptorFromType(TypeMirror t, ProcessingEnvironment env) { - t = env.getTypeUtils().erasure(t); //type erasure - - StringBuilder desc = new StringBuilder(); - //add array brackets - while(t.getKind() == TypeKind.ARRAY) { - desc.append("["); - t = ((ArrayType) t).getComponentType(); - } - - if(t.getKind() == TypeKind.TYPEVAR) - t = ((TypeVariable) t).getUpperBound(); - - if(t.getKind() == TypeKind.DECLARED) - desc - .append("L") - .append(internalNameFromType(t, env)) - .append(";"); - else { - switch(t.getKind()) { - case BOOLEAN: - desc.append("Z"); - break; - case CHAR: - desc.append("C"); - break; - case BYTE: - desc.append("B"); - break; - case SHORT: - desc.append("S"); - break; - case INT: - desc.append("I"); - break; - case FLOAT: - desc.append("F"); - break; - case LONG: - desc.append("J"); - break; - case DOUBLE: - desc.append("D"); - break; - case VOID: - desc.append("V"); - break; - } - } - - return desc.toString(); - } - - /** - * Builds a method descriptor from the given {@link ExecutableElement}. - * @param m the {@link ExecutableElement} for the method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return a {@link String} containing the relevant descriptor - */ - public static String descriptorFromExecutableElement(ExecutableElement m, ProcessingEnvironment env) { - StringBuilder methodSignature = new StringBuilder(); - methodSignature.append("("); - m.getParameters().forEach(p -> methodSignature.append(descriptorFromType(p.asType(), env))); - methodSignature.append(")"); - methodSignature.append(descriptorFromType(m.getReturnType(), env)); - return methodSignature.toString(); - } - - /** - * Gets the {@link ClassData} corresponding to the given fully-qualified name, - * or creates a false one with the same, non-obfuscated name twice. - * @param name the internal name of the class to convert - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static ClassData getClassData(String name, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getClassData(name); - } catch(MappingNotFoundException ignored) {} - return new ClassData(name, name); - } - - /** - * Gets the {@link MethodData} corresponding to the method matching the given - * name, parent and descriptor, or creates a dummy one with fake data if no - * valid mapping is found. - * @param parent the internal name of the parent class - * @param name the name of the member - * @param descriptor the descriptor of the method - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static MethodData getMethodData(String parent, String name, String descriptor, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getMethodData(parent, name, descriptor); - } catch(MappingNotFoundException ignored) {} - return new MethodData(getClassData(name, mapper), name, name, descriptor); - } - - /** - * Gets the {@link FieldData} corresponding to the field matching the given - * name and parent, or creates a dummy one with fake data if no valid - * mapping is found. - * @param parent the internal name of the parent class - * @param name the name of the member - * @param mapper the {@link Mapper} to use, may be null - * @return the fully qualified class name - * @since 0.6.1 - */ - public static FieldData getFieldData(String parent, String name, Mapper mapper) { - try { - name = name.replace('.', '/'); //just in case - if(mapper != null) - return mapper.getFieldData(parent, name); - } catch(MappingNotFoundException ignored) {} - return new FieldData(getClassData(name, mapper), name, name); - } - - /** - * Finds a member given the name, the container class and (if it's a method) the descriptor. - * @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 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( - ClassContainer parent, String name, String descr, - boolean strict, boolean field, ProcessingEnvironment env) { - if(parent.elem == null) - throw new TargetNotFoundException("parent class", parent.data.name); - //try to find by name - 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.isEmpty()) - throw new TargetNotFoundException(field ? "field" : "method", name, parent.data.name); - - if(candidates.size() == 1 && (!strict || descr == null)) - return candidates.get(0); - - if(descr == null) { - throw new AmbiguousDefinitionException(String.format( - "Found %d members named %s in class %s!", candidates.size(), name, parent.data.name)); - } else { - 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(), env).equals(descr)) - throw new TargetNotFoundException("field", String.format( - "%s with descriptor %s", name, descr), parent.data.name); - } else { - candidates = candidates.stream() - .map(e -> (ExecutableElement) e) - .filter(strict - ? c -> descr.equals(descriptorFromExecutableElement(c, env)) - : c -> descr.split("\\)")[0].equalsIgnoreCase( - descriptorFromExecutableElement(c, env).split("\\)")[0]) - ).collect(Collectors.toList()); - } - if(candidates.isEmpty()) - throw new TargetNotFoundException("method", String.format( - "%s %s", name, descr), parent.data.name); - if(candidates.size() > 1) - throw new AmbiguousDefinitionException(String.format( - "Found %d methods named %s in class %s!", candidates.size(), name, parent.data.name)); - return candidates.get(0); - } - } - - /** - * Tries to find the method being overloaded by the given {@link ExecutableElement}. - * In case of multiple layers of overloading, it finds the original one. In case of - * no overloading, it returns the given method. - * @param context the {@link TypeElement} representing the parent class - * @param method an {@link ExecutableElement} representing the overloading method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the original overloaded method, or the given method if it was not found - * @since 0.5.2 - */ - public static ExecutableElement findOverloadedMethod( - TypeElement context, ExecutableElement method, ProcessingEnvironment env) { - if (context.getSuperclass().getKind() == TypeKind.NONE) - return method; - - for (Element elem : context.getEnclosedElements()) { - if (elem.getKind() != ElementKind.METHOD) - continue; - if (env.getElementUtils().overrides(method, (ExecutableElement) elem, context)) { - method = (ExecutableElement) elem; - break; //found - } - } - - return findOverloadedMethod( - (TypeElement) env.getTypeUtils().asElement(context.getSuperclass()), - method, env - ); - } - - /** - * Tries to find the "synthetic bridge" generated by the compiler for a certain overridden - * method. A "bridge" only exists in cases where type erasure is involved (i.e. when the - * method being overridden uses a generic parameter that is not preserved in the overriding - * method). - * @param context the {@link TypeElement} representing the parent class - * @param method an {@link ExecutableElement} stub representing the overloading method - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the "bridge" - * @throws TargetNotFoundException if the method in question was not overriding anything, or - * if the method it was overriding does not require a bridge - * @since 0.5.2 - */ - public static ExecutableElement findSyntheticBridge( - TypeElement context, ExecutableElement method, ProcessingEnvironment env) throws TargetNotFoundException { - ExecutableElement overridding = findOverloadedMethod(context, method, env); - if(descriptorFromExecutableElement(overridding, env).equals(descriptorFromExecutableElement(method, env))) - throw new TargetNotFoundException( - "bridge method for", - overridding.getSimpleName().toString(), - context.getQualifiedName().toString() - ); - else return overridding; - } - - /** - * 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} - * @return the {@link ProxyType} for the element - * @throws NotAProxyException if it's neither - * @since 0.4.0 - */ - public static ProxyType getProxyType(VariableElement v) { - String returnTypeFQN = v.asType().toString(); - switch(returnTypeFQN) { - case "ftbsc.lll.proxies.impl.FieldProxy": - return ProxyType.FIELD; - case "ftbsc.lll.proxies.impl.MethodProxy": - return ProxyType.METHOD; - case "ftbsc.lll.proxies.impl.TypeProxy": - return ProxyType.TYPE; - case "ftbsc.lll.proxies.impl.PackageProxy": - return ProxyType.PACKAGE; - default: - throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString()); - } - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java deleted file mode 100644 index 5709574..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/JavaPoetUtils.java +++ /dev/null @@ -1,170 +0,0 @@ -package ftbsc.lll.processor.tools; - -import com.squareup.javapoet.*; -import ftbsc.lll.processor.annotations.Find; -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.InjectorInfo; -import ftbsc.lll.processor.tools.containers.MethodContainer; -import ftbsc.lll.proxies.ProxyType; -import ftbsc.lll.proxies.impl.FieldProxy; -import ftbsc.lll.proxies.impl.MethodProxy; - -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.*; -import java.util.HashSet; - -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. - */ -public class JavaPoetUtils { - /** - * Builds a {@link MethodSpec} for a public method whose body simply returns a {@link String}. - * @param name the name of the method - * @param returnString the {@link String} to return - * @return the built {@link MethodSpec} - */ - public static MethodSpec buildStringReturnMethod(String name, String returnString) { - return MethodSpec.methodBuilder(name) - .addModifiers(Modifier.PUBLIC) - .addAnnotation(Override.class) - .returns(String.class) - .addStatement("return $S", returnString) - .build(); - } - - /** - * Appends to a given {@link MethodSpec.Builder} definitions for a proxy. - * @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 options the {@link ProcessorOptions} to be used - * @since 0.5.0 - */ - public static void appendMemberFinderDefinition( - VariableElement var, ExecutableElement stub, Target t, - MethodSpec.Builder con, ProcessorOptions options) { - ProxyType type = getProxyType(var); - if(type != ProxyType.METHOD && type != ProxyType.FIELD) - return; //this method is irrelevant to everyone else - - //we need this stuff - Find f = var.getAnnotation(Find.class); - final boolean isMethod = type == ProxyType.METHOD; - final String builderName = var.getSimpleName().toString() + "Builder"; - - String descriptorObf, nameObf; - ClassContainer parent; - Element target; - - if(isMethod) { - MethodContainer mc = MethodContainer.from(stub, t, f, options); - descriptorObf = mc.descriptorObf; - nameObf = mc.data.nameMapped; - parent = mc.parent; - target = mc.elem; - } else { - FieldContainer fc = FieldContainer.from(var, options); - descriptorObf = fc.descriptorObf; - nameObf = fc.data.nameMapped; - parent = fc.parent; - target = fc.elem; - } - - //initialize builder - con.addStatement("$T $L = $T.builder($S)", - isMethod ? MethodProxy.Builder.class : FieldProxy.Builder.class, - builderName, //variable name is always unique by definition - isMethod ? MethodProxy.class : FieldProxy.class, - nameObf - ); - - //set parent - con.addStatement( - "$L.setParent($S, $L)", - builderName, - parent.data.nameMapped.replace('/', '.'), - parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) - ); - - //set modifiers - con.addStatement( - "$L.setModifiers($L)", - builderName, - target == null ? 0 : mapModifiers(target.getModifiers()) - ); - - //set type(s) - con.addStatement( - "$L.setDescriptor($S)", - builderName, - descriptorObf - ); - - //build and set - con.addStatement( - "super.$L = $L.build()", - var.getSimpleName().toString(), - builderName - ); - } - - /** - * Generates a {@link HashSet} of dummy overrides for every abstract method in a given class, - * represented as a {@link TypeElement}. - * @param clazz the given class - * @return a {@link HashSet} containing the generated {@link MethodSpec}s - * @since 0.5.0 - */ - public static HashSet generateDummies(TypeElement clazz) { - HashSet specs = new HashSet<>(); - clazz - .getEnclosedElements() - .stream() - .filter(e -> e instanceof ExecutableElement) - .map(e -> (ExecutableElement) e) - .forEach(e -> { - if(e.getModifiers().contains(Modifier.ABSTRACT)) - specs.add(MethodSpec.overriding(e) - .addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called") - .build() - ); - }); - return specs; - } - - /** - * Generates the wrapper around a certain injector. - * @param inj the {@link InjectorInfo} carrying the information about the target injector - * @param env the {@link ProcessingEnvironment} to perform the operation in - * @return the generated {@link MethodSpec} for the injector - * @since 0.6.0 - */ - public static MethodSpec generateInjector(InjectorInfo inj, ProcessingEnvironment env) { - MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject") - .addModifiers(Modifier.PUBLIC) - .returns(void.class) - .addAnnotation(Override.class) - .addParameter(ParameterSpec.builder( - TypeName.get(env - .getElementUtils() - .getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz") - .build()) - .addParameter(ParameterSpec.builder( - TypeName.get(env - .getElementUtils() - .getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main") - .build()); - - if(inj.injector.getParameters().size() == 2) - injectBuilder.addStatement("super.$L(clazz, main)", inj.injector.getSimpleName()); - else injectBuilder.addStatement("super.$L(main)", inj.injector.getSimpleName()); - - return injectBuilder.build(); - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java b/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java deleted file mode 100644 index 70d43cc..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/ProcessorOptions.java +++ /dev/null @@ -1,85 +0,0 @@ -package ftbsc.lll.processor.tools; - -import ftbsc.lll.IInjector; -import ftbsc.lll.mapper.MapperProvider; -import ftbsc.lll.mapper.tools.Mapper; - -import javax.annotation.processing.ProcessingEnvironment; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -/** - * Class in charge of containing, parsing and processing all processor options, - * from the simpler booleans to the more complicated mapper. - */ -public class ProcessorOptions { - - /** - * A {@link Set} of options currently supported by the processor. - */ - public static final Set SUPPORTED = new HashSet<>(Arrays.asList( - "mappingsFile", "anonymousClassWarning", "obfuscateInjectorMetadata", - "noServiceProvider" - )); - - /** - * The environment the processor is acting in. - */ - public final ProcessingEnvironment env; - - /** - * The {@link Mapper} used to convert classes and variables - * to their obfuscated equivalent. Will be null when no mapper is in use. - */ - public final Mapper mapper; - - /** - * Whether the processor should issue warnings when compiling code anonymous - * classes which can't be checked for validity. - */ - public final boolean anonymousClassWarning; - - /** - * Whether injector metadata (what is returned by the functions of {@link IInjector}) - * is to use obfuscated names instead of its normal names. - */ - public final boolean obfuscateInjectorMetadata; - - /** - * Whether the processor should skip the generation of the service provider. - */ - public final boolean noServiceProvider; - - /** - * The public constructor, parses and stores all given arguments. - * @param env the environment the processor is working in - */ - public ProcessorOptions(ProcessingEnvironment env) { - this.env = env; - String location = env.getOptions().get("mappingsFile"); - if(location != null) { - List lines = MapperProvider.fetchFromLocalOrRemote(location); - this.mapper = MapperProvider.getMapper(lines).getMapper(lines, true); - } else this.mapper = null; - this.anonymousClassWarning = parseBooleanArg(env.getOptions().get("anonymousClassWarning"), true); - this.obfuscateInjectorMetadata = parseBooleanArg(env.getOptions().get("obfuscateInjectorMetadata"), true); - this.noServiceProvider = parseBooleanArg(env.getOptions().get("noServiceProvider"), false); - } - - /** - * Parses a boolean arg from a String. - * @param arg the arg to parse - * @return the parsed boolean - */ - private static boolean parseBooleanArg(String arg, boolean defaultValue) { - if(arg == null) return defaultValue; - try { // 0 = false, any other integer = true - int i = Integer.parseInt(arg); - return i != 0; - } catch(NumberFormatException ignored) { - return Boolean.parseBoolean(arg); - } - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java deleted file mode 100644 index b145e71..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/ClassContainer.java +++ /dev/null @@ -1,130 +0,0 @@ -package ftbsc.lll.processor.tools.containers; - -import ftbsc.lll.exceptions.TargetNotFoundException; -import ftbsc.lll.mapper.tools.data.ClassData; -import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.annotations.Patch; -import ftbsc.lll.processor.tools.ProcessorOptions; - -import javax.lang.model.element.Element; -import javax.lang.model.element.TypeElement; -import javax.tools.Diagnostic; -import java.lang.annotation.Annotation; -import java.util.function.Function; - -import static ftbsc.lll.processor.tools.ASTUtils.*; - -/** - * Container for information about a class. - * Used internally for efficiency reasons. - * @since 0.5.0 - */ -public class ClassContainer { - /** - * The {@link ClassData} for the class represented by this container. - */ - public final ClassData data; - - /** - * The {@link Element} corresponding to the class. - * May only be null intentionally i.e. when the associated element is - * an anonymous class or a child of an anonymous class. - */ - public final Element elem; - - /** - * Private constructor, called from {@link #from(Annotation, Function, String, ProcessorOptions)}. - * @param fqn the fully-qualified name of the target class - * @param innerNames an array of Strings containing the path to the inner class, may be null - * @param options the {@link ProcessorOptions} to be used - */ - private ClassContainer(String fqn, String[] innerNames, ProcessorOptions options) { - //find and validate - Element elem = options.env.getElementUtils().getTypeElement(fqn); - - if(elem == null) - throw new TargetNotFoundException("class", fqn); - - StringBuilder fqnBuilder = new StringBuilder( - internalNameFromType(elem.asType(), options.env).replace('/', '.') - ); - - if(innerNames != null) { - for(String inner : innerNames) { - if(inner == null) continue; - fqnBuilder.append("$").append(inner); - try { - int anonClassCounter = Integer.parseInt(inner); - //anonymous classes cannot be validated! - if(options.anonymousClassWarning) - options.env.getMessager().printMessage( - Diagnostic.Kind.WARNING, - String.format( - "Anonymous classes cannot be verified by the processor. The existence of %s$%s is not guaranteed!", - fqnBuilder, anonClassCounter - ) - ); - elem = null; - break; - } catch(NumberFormatException exc) { - elem = elem - .getEnclosedElements() - .stream() - .filter(e -> e instanceof TypeElement) - .filter(e -> e.getSimpleName().contentEquals(inner)) - .findFirst() - .orElse(null); - } - if(elem == null) - throw new TargetNotFoundException("class", inner); - } - } - this.data = getClassData(fqnBuilder.toString(), options.mapper); - this.elem = elem; - } - - /** - * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. - * @param ann the annotation containing the class - * @param classFunction the annotation function returning the class - * @param innerName a string containing the inner class name or nothing - * @param options the {@link ProcessorOptions} to be used - * @param 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 innerName, ProcessorOptions options) { - String fqn; - String[] inner; - fqn = getTypeFromAnnotation(ann, classFunction, options.env).toString(); - inner = innerName.equals("") ? null : innerName.split("//$"); - return new ClassContainer(fqn, inner, options); - } - - /** - * Safely extracts a {@link Class} from an annotation and gets its fully qualified name. - * @param cl the {@link TypeElement} representing the class - * @param options the {@link ProcessorOptions} to be used - * @return the fully qualified name of the given class - * @since 0.6.0 - */ - public static ClassContainer from(TypeElement cl, ProcessorOptions options) { - return new ClassContainer(cl.getQualifiedName().toString(), null, options); - } - - /** - * Finds and builds a {@link ClassContainer} based on information contained - * within {@link Patch} or a {@link Find} annotations, else returns a fallback. - * @param fallback the {@link ClassContainer} it falls back on - * @param p the {@link Patch} annotation to get info from - * @param f the {@link Find} annotation to get info from - * @param options the {@link ProcessorOptions} to be used - * @return the built {@link ClassContainer} or the fallback if not enough information was present - * @since 0.5.0 - */ - public static ClassContainer findOrFallback(ClassContainer fallback, Patch p, Find f, ProcessorOptions options) { - if(f == null) return ClassContainer.from(p, Patch::value, p.innerName(), options); - ClassContainer cl = ClassContainer.from(f, Find::value, f.innerName(), options); - return cl.data.name.equals("java/lang/Object") ? fallback : cl; - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java b/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java deleted file mode 100644 index 6ef4741..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/FieldContainer.java +++ /dev/null @@ -1,109 +0,0 @@ -package ftbsc.lll.processor.tools.containers; - -import ftbsc.lll.exceptions.AmbiguousDefinitionException; -import ftbsc.lll.mapper.tools.MappingUtils; -import ftbsc.lll.mapper.tools.data.FieldData; -import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.annotations.Patch; -import ftbsc.lll.processor.tools.ProcessorOptions; -import org.objectweb.asm.Type; - -import javax.lang.model.element.TypeElement; -import javax.lang.model.element.VariableElement; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; - -import static ftbsc.lll.processor.tools.ASTUtils.*; - -/** - * Container for information about a field. - * Used internally for efficiency reasons. - * @since 0.5.0 - */ -public class FieldContainer { - /** - * The {@link FieldData} for the field represented by this container. - */ - public final FieldData data; - - /** - * The descriptor of the field. - */ - public final String descriptor; - - /** - * The obfuscated descriptor of the field. - * If the mapper passed is null, then this will be identical to {@link #descriptor}. - */ - public final String descriptorObf; - - /** - * The {@link ClassContainer} representing the parent of this field. - */ - public final ClassContainer parent; - - /** - * The {@link VariableElement} corresponding to the field. - * May only be null intentionally i.e. when the field is - * a child of an anonymous class. - */ - public final VariableElement elem; - - /** - * Private constructor, called from {@link #from(VariableElement, ProcessorOptions)}. - * @param parent the {@link ClassContainer} representing the parent - * @param name the fully-qualified name of the target field - * @param descriptor the descriptor of the target field, may be null for verifiable fields - * @param options the {@link ProcessorOptions} to be used - */ - private FieldContainer(ClassContainer parent, String name, String descriptor, ProcessorOptions options) { - this.parent = parent; - if(parent.elem == null) { //unverified - if(descriptor == null) - throw new AmbiguousDefinitionException("Cannot use name-based lookups for fields of unverifiable classes!"); - this.elem = null; - this.descriptor = descriptor; - } else { - this.elem = (VariableElement) findMember(parent, name, descriptor, descriptor != null, true, options.env); - this.descriptor = descriptorFromType(this.elem.asType(), options.env); - name = this.elem.getSimpleName().toString(); - } - this.data = getFieldData(parent.data.name, name, options.mapper); - this.descriptorObf = options.mapper == null ? this.descriptor - : MappingUtils.mapType(Type.getType(this.descriptor), options.mapper, false).getDescriptor(); - } - - /** - * Finds a {@link FieldContainer} from a finder. - * @param finder the {@link VariableElement} annotated with {@link Find} for this field - * @param options the {@link ProcessorOptions} to be used - * @return the built {@link FieldContainer} - * @since 0.5.0 - */ - public static FieldContainer from(VariableElement finder, ProcessorOptions options) { - //the parent always has a @Patch annotation - Patch patchAnn = finder.getEnclosingElement().getAnnotation(Patch.class); - //the finder always has a @Find annotation - Find f = finder.getAnnotation(Find.class); - - ClassContainer parent = ClassContainer.findOrFallback( - ClassContainer.from((TypeElement) finder.getEnclosingElement(), options), patchAnn, f, options - ); - - String name = f.name().isEmpty() ? finder.getSimpleName().toString() : f.name(); - String descriptor; - TypeMirror fieldType = getTypeFromAnnotation(f, Find::type, options.env); - if(fieldType.toString().equals("java.lang.Object")) { - descriptor = null; - } else { - if(fieldType.getKind() != TypeKind.VOID && !fieldType.getKind().isPrimitive()) - descriptor = //jank af but this is temporary anyway - "L" + ClassContainer.from( - f, Find::type, f.typeInner(), options - ).data.nameMapped + ";"; - else descriptor = descriptorFromType(fieldType, options.env); - } - - return new FieldContainer(parent, name, descriptor, options); - } -} diff --git a/src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java b/src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java deleted file mode 100644 index 460401b..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/InjectorInfo.java +++ /dev/null @@ -1,46 +0,0 @@ -package ftbsc.lll.processor.tools.containers; - -import ftbsc.lll.processor.annotations.Injector; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.ProcessorOptions; - -import javax.lang.model.element.ExecutableElement; - -/** - * Container for information about a class that is to be generated. - */ -public class InjectorInfo { - /** - * The {@link ExecutableElement} corresponding to the injector method. - */ - public final ExecutableElement injector; - - /** - * The {@link ExecutableElement} corresponding to the target method stub. - */ - public final ExecutableElement targetStub; - - /** - * The reason for the injection. - */ - public final String reason; - - /** - * The {@link MethodContainer} corresponding to the target method. - */ - public final MethodContainer target; - - /** - * Public constructor. - * @param injector the injector {@link ExecutableElement} - * @param targetStub the target {@link ExecutableElement} - * @param targetAnn the relevant {@link Target} annotation - * @param options the {@link ProcessorOptions} to be used - */ - public InjectorInfo(ExecutableElement injector, ExecutableElement targetStub, Target targetAnn, ProcessorOptions options) { - this.injector = injector; - this.targetStub = targetStub; - this.reason = injector.getAnnotation(Injector.class).reason(); - this.target = MethodContainer.from(targetStub, targetAnn, null, options); - } -} \ 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 deleted file mode 100644 index 266858d..0000000 --- a/src/main/java/ftbsc/lll/processor/tools/containers/MethodContainer.java +++ /dev/null @@ -1,102 +0,0 @@ -package ftbsc.lll.processor.tools.containers; - -import ftbsc.lll.exceptions.AmbiguousDefinitionException; -import ftbsc.lll.exceptions.TargetNotFoundException; -import ftbsc.lll.mapper.tools.MappingUtils; -import ftbsc.lll.mapper.tools.data.MethodData; -import ftbsc.lll.processor.annotations.Find; -import ftbsc.lll.processor.annotations.Patch; -import ftbsc.lll.processor.annotations.Target; -import ftbsc.lll.processor.tools.ProcessorOptions; - -import javax.lang.model.element.ExecutableElement; -import javax.lang.model.element.TypeElement; - -import static ftbsc.lll.processor.tools.ASTUtils.*; - -/** - * Container for information about a method. - * Used internally for efficiency reasons. - * @since 0.5.0 - */ -public class MethodContainer { - /** - * The {@link MethodData} for the method represented by this container. - */ - public final MethodData data; - - /** - * The obfuscated descriptor of the field. - * If the mapper passed is null, this will be identical to the one inside - * {@link #data}. - */ - public final String descriptorObf; - - /** - * The {@link ClassContainer} representing the parent of this method. - */ - public final ClassContainer parent; - - /** - * The {@link ExecutableElement} corresponding to the method. - * May only be null intentionally i.e. when the method is - * a child of an anonymous class. - */ - public final ExecutableElement elem; - - /** - * Private constructor, called from - * {@link #from(ExecutableElement, Target, Find, ProcessorOptions)}. - * @param parent the {@link ClassContainer} representing the parent - * @param name the fully-qualified name of the target method - * @param descriptor the descriptor of the target method - * @param strict whether the matching should be strict (see {@link Target#strict()} for more info) - * @param bridge whether the "bridge" should be matched instead (see {@link Target#bridge()} for more info) - * @param options the {@link ProcessorOptions} to be used - */ - private MethodContainer(ClassContainer parent, String name, String descriptor, boolean strict, boolean bridge, ProcessorOptions options) { - this.parent = parent; - if(parent.elem == null) { //unverified - if(descriptor == null) - throw new AmbiguousDefinitionException("Cannot use name-based lookups for methods of unverifiable classes!"); - this.elem = null; - } else { - ExecutableElement tmp = (ExecutableElement) findMember( - parent, name, descriptor, descriptor != null && strict,false, options.env - ); - this.elem = bridge ? findSyntheticBridge((TypeElement) this.parent.elem, tmp, options.env) : tmp; - name = this.elem.getSimpleName().toString(); - descriptor = descriptorFromExecutableElement(this.elem, options.env); - } - this.data = getMethodData(parent.data.name, name, descriptor, options.mapper); - this.descriptorObf = options.mapper == null ? this.data.signature.descriptor - : MappingUtils.mapMethodDescriptor(this.data.signature.descriptor, options.mapper, false); - } - - /** - * Builds the {@link MethodContainer} corresponding to a stub annotated with {@link Target}. - * @param stub the {@link ExecutableElement} for the stub - * @param t the {@link Target} annotation relevant to this case - * @param f the {@link Find} annotation containing fallback data, may be null - * @param options the {@link ProcessorOptions} to be used - * @return the {@link MethodContainer} corresponding to the method - * @throws AmbiguousDefinitionException if it finds more than one candidate - * @throws TargetNotFoundException if it finds no valid candidate - * @since 0.3.0 - */ - public static MethodContainer from(ExecutableElement stub, Target t, Find f, ProcessorOptions options) { - //the parent always has a @Patch annotation - Patch patchAnn = stub.getEnclosingElement().getAnnotation(Patch.class); - ClassContainer parent = ClassContainer.findOrFallback( - ClassContainer.from((TypeElement) stub.getEnclosingElement(), options), patchAnn, f, options - ); - String name = !t.methodName().isEmpty() - ? t.methodName() //name was specified in target - : stub.getSimpleName().toString(); - String descriptor = t.strict() - ? descriptorFromExecutableElement(stub, options.env) - : null; - - return new MethodContainer(parent, name, descriptor, t.strict(), t.bridge(), options); - } -} diff --git a/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java new file mode 100644 index 0000000..04da70e --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/utils/ASTUtils.java @@ -0,0 +1,409 @@ +package ftbsc.lll.processor.utils; + +import ftbsc.lll.exceptions.AmbiguousDefinitionException; +import ftbsc.lll.exceptions.MappingNotFoundException; +import ftbsc.lll.exceptions.NotAProxyException; +import ftbsc.lll.exceptions.TargetNotFoundException; +import ftbsc.lll.mapper.tools.Mapper; +import ftbsc.lll.mapper.tools.data.ClassData; +import ftbsc.lll.mapper.tools.data.FieldData; +import ftbsc.lll.mapper.tools.data.MethodData; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.proxies.ProxyType; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import java.lang.annotation.Annotation; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * Collection of AST-related static utils that didn't really fit into the main class. + */ +public class ASTUtils { + /** + * Finds, among the methods of a class cl, the one annotated with ann, and tries to build + * an {@link Element} from it. + * @param parent the parent {@link Element} to the desired element + * @param ann the {@link Class} corresponding to the desired annotation + * @param the type of {@link Element} to use + * @return a {@link List} of {@link Element}s annotated with the given annotation + * @since 0.2.0 + */ + @SuppressWarnings("unchecked") + public static List findAnnotatedElement(Element parent, Class ann) { + return parent.getEnclosedElements() + .stream() + .filter(e -> e.getAnnotationsByType(ann).length != 0) + .map(e -> (T) e) + .collect(Collectors.toList()); + } + + /** + * Maps a {@link javax.lang.model.element.Modifier} to its reflective + * {@link java.lang.reflect.Modifier} equivalent. + * @param m the {@link Modifier} to map + * @return an integer representing the modifier + * @see java.lang.reflect.Modifier + * @since 0.2.0 + */ + public static int mapModifier(Modifier m) { + switch(m) { + case PUBLIC: + return java.lang.reflect.Modifier.PUBLIC; + case PROTECTED: + return java.lang.reflect.Modifier.PROTECTED; + case PRIVATE: + return java.lang.reflect.Modifier.PRIVATE; + case ABSTRACT: + return java.lang.reflect.Modifier.ABSTRACT; + case STATIC: + return java.lang.reflect.Modifier.STATIC; + case FINAL: + return java.lang.reflect.Modifier.FINAL; + case TRANSIENT: + return java.lang.reflect.Modifier.TRANSIENT; + case VOLATILE: + return java.lang.reflect.Modifier.VOLATILE; + case SYNCHRONIZED: + return java.lang.reflect.Modifier.SYNCHRONIZED; + case NATIVE: + return java.lang.reflect.Modifier.NATIVE; + case STRICTFP: + return java.lang.reflect.Modifier.STRICT; + default: + return 0; + } + } + + /** + * Takes in a {@link Collection} of AST {@link Modifier}s and + * returns them mapped to their reflective integer equivalent. + * @param modifiers the {@link Modifier}s + * @return an integer value representing them + * @since 0.5.0 + */ + public static int mapModifiers(Collection modifiers) { + int i = 0; + for(Modifier m : modifiers) + i |= mapModifier(m); + return i; + } + + /** + * Safely extracts a {@link Class} from an annotation and gets a {@link TypeMirror} representing it. + * @param ann the annotation containing the class + * @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 a {@link TypeMirror} representing the requested {@link Class} + * @since 0.3.0 + */ + public static TypeMirror getTypeFromAnnotation( + T ann, Function> classFunction, ProcessingEnvironment env) { + try { + String fqn = classFunction.apply(ann).getCanonicalName(); + if(fqn == null) + fqn = ""; + return env.getElementUtils().getTypeElement(fqn).asType(); + } catch(MirroredTypeException e) { + return e.getTypeMirror(); + } + } + + /** + * Gets the internal name from an {@link TypeMirror}. + * @param type the {@link TypeMirror} in question + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the internal name at compile time, or null if it wasn't a qualifiable + * @since 0.5.1 + */ + public static String internalNameFromType(TypeMirror type, ProcessingEnvironment env) { + //needed to actually turn elem into a TypeVariable, find it ignoring generics + Element elem = env.getTypeUtils().asElement(env.getTypeUtils().erasure(type)); + StringBuilder fqnBuilder = new StringBuilder(); + while(elem.getEnclosingElement() != null && elem.getEnclosingElement().getKind() != ElementKind.PACKAGE) { + fqnBuilder + .insert(0, elem.getSimpleName().toString()) + .insert(0, "$"); + elem = elem.getEnclosingElement(); + } + return fqnBuilder + .insert(0, env.getTypeUtils().erasure(elem.asType()).toString()) + .toString() + .replace('.', '/'); + } + + /** + * Builds a type descriptor from the given {@link TypeMirror}. + * @param t the {@link TypeMirror} representing the desired type + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return a {@link String} containing the relevant descriptor + */ + public static String descriptorFromType(TypeMirror t, ProcessingEnvironment env) { + t = env.getTypeUtils().erasure(t); //type erasure + + StringBuilder desc = new StringBuilder(); + //add array brackets + while(t.getKind() == TypeKind.ARRAY) { + desc.append("["); + t = ((ArrayType) t).getComponentType(); + } + + if(t.getKind() == TypeKind.TYPEVAR) + t = ((TypeVariable) t).getUpperBound(); + + if(t.getKind() == TypeKind.DECLARED) + desc + .append("L") + .append(internalNameFromType(t, env)) + .append(";"); + else { + switch(t.getKind()) { + case BOOLEAN: + desc.append("Z"); + break; + case CHAR: + desc.append("C"); + break; + case BYTE: + desc.append("B"); + break; + case SHORT: + desc.append("S"); + break; + case INT: + desc.append("I"); + break; + case FLOAT: + desc.append("F"); + break; + case LONG: + desc.append("J"); + break; + case DOUBLE: + desc.append("D"); + break; + case VOID: + desc.append("V"); + break; + } + } + + return desc.toString(); + } + + /** + * Builds a method descriptor from the given {@link ExecutableElement}. + * @param m the {@link ExecutableElement} for the method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return a {@link String} containing the relevant descriptor + */ + public static String descriptorFromExecutableElement(ExecutableElement m, ProcessingEnvironment env) { + StringBuilder methodSignature = new StringBuilder(); + methodSignature.append("("); + m.getParameters().forEach(p -> methodSignature.append(descriptorFromType(p.asType(), env))); + methodSignature.append(")"); + methodSignature.append(descriptorFromType(m.getReturnType(), env)); + return methodSignature.toString(); + } + + /** + * Gets the {@link ClassData} corresponding to the given fully-qualified name, + * or creates a false one with the same, non-obfuscated name twice. + * @param name the internal name of the class to convert + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static ClassData getClassData(String name, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getClassData(name); + } catch(MappingNotFoundException ignored) {} + return new ClassData(name, name); + } + + /** + * Gets the {@link MethodData} corresponding to the method matching the given + * name, parent and descriptor, or creates a dummy one with fake data if no + * valid mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param descriptor the descriptor of the method + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static MethodData getMethodData(String parent, String name, String descriptor, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getMethodData(parent, name, descriptor); + } catch(MappingNotFoundException ignored) {} + return new MethodData(getClassData(name, mapper), name, name, descriptor); + } + + /** + * Gets the {@link FieldData} corresponding to the field matching the given + * name and parent, or creates a dummy one with fake data if no valid + * mapping is found. + * @param parent the internal name of the parent class + * @param name the name of the member + * @param mapper the {@link Mapper} to use, may be null + * @return the fully qualified class name + * @since 0.6.1 + */ + public static FieldData getFieldData(String parent, String name, Mapper mapper) { + try { + name = name.replace('.', '/'); //just in case + if(mapper != null) + return mapper.getFieldData(parent, name); + } catch(MappingNotFoundException ignored) {} + return new FieldData(getClassData(name, mapper), name, name); + } + + /** + * Finds a member given the name, the container class and (if it's a method) the descriptor. + * @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 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( + ClassContainer parent, String name, String descr, + boolean strict, boolean field, ProcessingEnvironment env) { + if(parent.elem == null) + throw new TargetNotFoundException("parent class", parent.data.name); + //try to find by name + 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.isEmpty()) + throw new TargetNotFoundException(field ? "field" : "method", name, parent.data.name); + + if(candidates.size() == 1 && (!strict || descr == null)) + return candidates.get(0); + + if(descr == null) { + throw new AmbiguousDefinitionException(String.format( + "Found %d members named %s in class %s!", candidates.size(), name, parent.data.name)); + } else { + 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(), env).equals(descr)) + throw new TargetNotFoundException("field", String.format( + "%s with descriptor %s", name, descr), parent.data.name); + } else { + candidates = candidates.stream() + .map(e -> (ExecutableElement) e) + .filter(strict + ? c -> descr.equals(descriptorFromExecutableElement(c, env)) + : c -> descr.split("\\)")[0].equalsIgnoreCase( + descriptorFromExecutableElement(c, env).split("\\)")[0]) + ).collect(Collectors.toList()); + } + if(candidates.isEmpty()) + throw new TargetNotFoundException("method", String.format( + "%s %s", name, descr), parent.data.name); + if(candidates.size() > 1) + throw new AmbiguousDefinitionException(String.format( + "Found %d methods named %s in class %s!", candidates.size(), name, parent.data.name)); + return candidates.get(0); + } + } + + /** + * Tries to find the method being overloaded by the given {@link ExecutableElement}. + * In case of multiple layers of overloading, it finds the original one. In case of + * no overloading, it returns the given method. + * @param context the {@link TypeElement} representing the parent class + * @param method an {@link ExecutableElement} representing the overloading method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the original overloaded method, or the given method if it was not found + * @since 0.5.2 + */ + public static ExecutableElement findOverloadedMethod( + TypeElement context, ExecutableElement method, ProcessingEnvironment env) { + if (context.getSuperclass().getKind() == TypeKind.NONE) + return method; + + for (Element elem : context.getEnclosedElements()) { + if (elem.getKind() != ElementKind.METHOD) + continue; + if (env.getElementUtils().overrides(method, (ExecutableElement) elem, context)) { + method = (ExecutableElement) elem; + break; //found + } + } + + return findOverloadedMethod( + (TypeElement) env.getTypeUtils().asElement(context.getSuperclass()), + method, env + ); + } + + /** + * Tries to find the "synthetic bridge" generated by the compiler for a certain overridden + * method. A "bridge" only exists in cases where type erasure is involved (i.e. when the + * method being overridden uses a generic parameter that is not preserved in the overriding + * method). + * @param context the {@link TypeElement} representing the parent class + * @param method an {@link ExecutableElement} stub representing the overloading method + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the "bridge" + * @throws TargetNotFoundException if the method in question was not overriding anything, or + * if the method it was overriding does not require a bridge + * @since 0.5.2 + */ + public static ExecutableElement findSyntheticBridge( + TypeElement context, ExecutableElement method, ProcessingEnvironment env) throws TargetNotFoundException { + ExecutableElement overridding = findOverloadedMethod(context, method, env); + if(descriptorFromExecutableElement(overridding, env).equals(descriptorFromExecutableElement(method, env))) + throw new TargetNotFoundException( + "bridge method for", + overridding.getSimpleName().toString(), + context.getQualifiedName().toString() + ); + else return overridding; + } + + /** + * 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} + * @return the {@link ProxyType} for the element + * @throws NotAProxyException if it's neither + * @since 0.4.0 + */ + public static ProxyType getProxyType(VariableElement v) { + String returnTypeFQN = v.asType().toString(); + switch(returnTypeFQN) { + case "ftbsc.lll.proxies.impl.FieldProxy": + return ProxyType.FIELD; + case "ftbsc.lll.proxies.impl.MethodProxy": + return ProxyType.METHOD; + case "ftbsc.lll.proxies.impl.TypeProxy": + return ProxyType.TYPE; + case "ftbsc.lll.proxies.impl.PackageProxy": + return ProxyType.PACKAGE; + default: + throw new NotAProxyException(v.getEnclosingElement().getSimpleName().toString(), v.getSimpleName().toString()); + } + } +} diff --git a/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java b/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java new file mode 100644 index 0000000..629f77a --- /dev/null +++ b/src/main/java/ftbsc/lll/processor/utils/JavaPoetUtils.java @@ -0,0 +1,171 @@ +package ftbsc.lll.processor.utils; + +import com.squareup.javapoet.*; +import ftbsc.lll.processor.ProcessorOptions; +import ftbsc.lll.processor.annotations.Find; +import ftbsc.lll.processor.annotations.Target; +import ftbsc.lll.processor.containers.ClassContainer; +import ftbsc.lll.processor.containers.FieldContainer; +import ftbsc.lll.processor.containers.InjectorInfo; +import ftbsc.lll.processor.containers.MethodContainer; +import ftbsc.lll.proxies.ProxyType; +import ftbsc.lll.proxies.impl.FieldProxy; +import ftbsc.lll.proxies.impl.MethodProxy; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.*; +import java.util.HashSet; + +import static ftbsc.lll.processor.utils.ASTUtils.getProxyType; +import static ftbsc.lll.processor.utils.ASTUtils.mapModifiers; + +/** + * Collection of static utils that rely on JavaPoet to function. + */ +public class JavaPoetUtils { + /** + * Builds a {@link MethodSpec} for a public method whose body simply returns a {@link String}. + * @param name the name of the method + * @param returnString the {@link String} to return + * @return the built {@link MethodSpec} + */ + public static MethodSpec buildStringReturnMethod(String name, String returnString) { + return MethodSpec.methodBuilder(name) + .addModifiers(Modifier.PUBLIC) + .addAnnotation(Override.class) + .returns(String.class) + .addStatement("return $S", returnString) + .build(); + } + + /** + * Appends to a given {@link MethodSpec.Builder} definitions for a proxy. + * @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 options the {@link ProcessorOptions} to be used + * @since 0.5.0 + */ + public static void appendMemberFinderDefinition( + VariableElement var, ExecutableElement stub, Target t, + MethodSpec.Builder con, ProcessorOptions options) { + ProxyType type = getProxyType(var); + if(type != ProxyType.METHOD && type != ProxyType.FIELD) + return; //this method is irrelevant to everyone else + + //we need this stuff + Find f = var.getAnnotation(Find.class); + final boolean isMethod = type == ProxyType.METHOD; + final String builderName = var.getSimpleName().toString() + "Builder"; + + String descriptorObf, nameObf; + ClassContainer parent; + Element target; + + if(isMethod) { + MethodContainer mc = MethodContainer.from(stub, t, f, options); + descriptorObf = mc.descriptorObf; + nameObf = mc.data.nameMapped; + parent = mc.parent; + target = mc.elem; + } else { + FieldContainer fc = FieldContainer.from(var, options); + descriptorObf = fc.descriptorObf; + nameObf = fc.data.nameMapped; + parent = fc.parent; + target = fc.elem; + } + + //initialize builder + con.addStatement("$T $L = $T.builder($S)", + isMethod ? MethodProxy.Builder.class : FieldProxy.Builder.class, + builderName, //variable name is always unique by definition + isMethod ? MethodProxy.class : FieldProxy.class, + nameObf + ); + + //set parent + con.addStatement( + "$L.setParent($S, $L)", + builderName, + parent.data.nameMapped.replace('/', '.'), + parent.elem == null ? 0 : mapModifiers(parent.elem.getModifiers()) + ); + + //set modifiers + con.addStatement( + "$L.setModifiers($L)", + builderName, + target == null ? 0 : mapModifiers(target.getModifiers()) + ); + + //set type(s) + con.addStatement( + "$L.setDescriptor($S)", + builderName, + descriptorObf + ); + + //build and set + con.addStatement( + "super.$L = $L.build()", + var.getSimpleName().toString(), + builderName + ); + } + + /** + * Generates a {@link HashSet} of dummy overrides for every abstract method in a given class, + * represented as a {@link TypeElement}. + * @param clazz the given class + * @return a {@link HashSet} containing the generated {@link MethodSpec}s + * @since 0.5.0 + */ + public static HashSet generateDummies(TypeElement clazz) { + HashSet specs = new HashSet<>(); + clazz + .getEnclosedElements() + .stream() + .filter(e -> e instanceof ExecutableElement) + .map(e -> (ExecutableElement) e) + .forEach(e -> { + if(e.getModifiers().contains(Modifier.ABSTRACT)) + specs.add(MethodSpec.overriding(e) + .addStatement("throw new $T($S)", RuntimeException.class, "This is a stub and should not have been called") + .build() + ); + }); + return specs; + } + + /** + * Generates the wrapper around a certain injector. + * @param inj the {@link InjectorInfo} carrying the information about the target injector + * @param env the {@link ProcessingEnvironment} to perform the operation in + * @return the generated {@link MethodSpec} for the injector + * @since 0.6.0 + */ + public static MethodSpec generateInjector(InjectorInfo inj, ProcessingEnvironment env) { + MethodSpec.Builder injectBuilder = MethodSpec.methodBuilder("inject") + .addModifiers(Modifier.PUBLIC) + .returns(void.class) + .addAnnotation(Override.class) + .addParameter(ParameterSpec.builder( + TypeName.get(env + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.ClassNode").asType()), "clazz") + .build()) + .addParameter(ParameterSpec.builder( + TypeName.get(env + .getElementUtils() + .getTypeElement("org.objectweb.asm.tree.MethodNode").asType()), "main") + .build()); + + if(inj.injector.getParameters().size() == 2) + injectBuilder.addStatement("super.$L(clazz, main)", inj.injector.getSimpleName()); + else injectBuilder.addStatement("super.$L(main)", inj.injector.getSimpleName()); + + return injectBuilder.build(); + } +} -- cgit v1.2.3-56-ga3b1 From d98eab7e7db9c32713da17399fce6a7e91127a38 Mon Sep 17 00:00:00 2001 From: zaaarf Date: Wed, 24 Jan 2024 17:29:23 +0100 Subject: chore: version bump --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cdcad7b..0719376 100644 --- a/build.gradle +++ b/build.gradle @@ -20,7 +20,7 @@ repositories { dependencies { implementation 'com.squareup:javapoet:1.13.0' implementation 'org.ow2.asm:asm-commons:9.5' - implementation 'ftbsc:lll:0.4.2' + implementation 'ftbsc:lll:0.5.0' implementation 'ftbsc.lll:mapper:0.3.0' } -- cgit v1.2.3-56-ga3b1 From 9cc6ed16d37bff66b7d128a19abfcb497db6a66f Mon Sep 17 00:00:00 2001 From: zaaarf Date: Sat, 11 May 2024 15:08:46 +0200 Subject: fix: use gitversion properly --- build.gradle | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index 0719376..f7dcd94 100644 --- a/build.gradle +++ b/build.gradle @@ -4,10 +4,10 @@ plugins { } archivesBaseName = 'processor' -version = gitVersion() +version = versionDetails().lastTag java { - sourceCompatibility = targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8 withSourcesJar() withJavadocJar() } @@ -23,7 +23,3 @@ dependencies { implementation 'ftbsc:lll:0.5.0' implementation 'ftbsc.lll:mapper:0.3.0' } - -jar { - archiveFileName = "${jar.archiveBaseName.get()}-${project.version.split('-')[0]}.${jar.archiveExtension.get()}" -} -- cgit v1.2.3-56-ga3b1