/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.tools.obfuscation; import java.util.List; import javax.annotation.processing.Messager; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName; import org.spongepowered.asm.obfuscation.mapping.IMapping; import org.spongepowered.asm.obfuscation.mapping.common.MappingField; import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; import org.spongepowered.asm.util.ConstraintParser; import org.spongepowered.asm.util.ConstraintParser.Constraint; import org.spongepowered.asm.util.throwables.ConstraintViolationException; import org.spongepowered.asm.util.throwables.InvalidConstraintException; import org.spongepowered.tools.obfuscation.interfaces.IMessagerSuppressible; import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor; import org.spongepowered.tools.obfuscation.interfaces.IObfuscationManager; import org.spongepowered.tools.obfuscation.mapping.IMappingConsumer; import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle; import org.spongepowered.tools.obfuscation.mirror.FieldHandle; import org.spongepowered.tools.obfuscation.mirror.MethodHandle; import org.spongepowered.tools.obfuscation.mirror.TypeHandle; import org.spongepowered.tools.obfuscation.mirror.TypeUtils; import org.spongepowered.tools.obfuscation.mirror.Visibility; /** * Base class for module for {@link AnnotatedMixin} which handle different * aspects of mixin target classes */ abstract class AnnotatedMixinElementHandler { /** * An annotated element to be processed by this element handler * * @param <E> type of inner element */ abstract static class AnnotatedElement<E extends Element> { protected final E element; protected final AnnotationHandle annotation; private final String desc; public AnnotatedElement(E element, AnnotationHandle annotation) { this.element = element; this.annotation = annotation; this.desc = TypeUtils.getDescriptor(element); } public E getElement() { return this.element; } public AnnotationHandle getAnnotation() { return this.annotation; } public String getSimpleName() { return this.getElement().getSimpleName().toString(); } public String getDesc() { return this.desc; } public final void printMessage(Messager messager, Diagnostic.Kind kind, CharSequence msg) { messager.printMessage(kind, msg, this.element, this.annotation.asMirror()); } public final void printMessage(IMessagerSuppressible messager, Diagnostic.Kind kind, CharSequence msg, SuppressedBy suppressedBy) { messager.printMessage(kind, msg, this.element, this.annotation.asMirror(), suppressedBy); } } /** * A name of an element which may have aliases */ static class AliasedElementName { /** * The original name including any original prefix (the "actual" name) */ protected final String originalName; /** * Aliases declared by the annotation (if any), never null */ private final List<String> aliases; private boolean caseSensitive; public AliasedElementName(Element element, AnnotationHandle annotation) { this.originalName = element.getSimpleName().toString(); this.aliases = annotation.<String>getList("aliases"); } public AliasedElementName setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; return this; } public boolean isCaseSensitive() { return this.caseSensitive; } /** * Get whether this member has any aliases defined */ public boolean hasAliases() { return this.aliases.size() > 0; } /** * Get this member's aliases */ public List<String> getAliases() { return this.aliases; } /** * Gets the original name of the member (including prefix) */ public String elementName() { return this.originalName; } public String baseName() { return this.originalName; } public boolean hasPrefix() { return false; } } /** * Convenience class to store information about an * {@link org.spongepowered.asm.mixin.Shadow}ed member's names */ static class ShadowElementName extends AliasedElementName { /** * True if the real element is prefixed */ private final boolean hasPrefix; /** * Expected prefix read from the annotation, this is set even if * {@link #hasPrefix} is false */ private final String prefix; /** * The base name without the prefix */ private final String baseName; /** * Obfuscated name (once determined) */ private String obfuscated; ShadowElementName(Element element, AnnotationHandle shadow) { super(element, shadow); this.prefix = shadow.<String>getValue("prefix", "shadow$"); boolean hasPrefix = false; String name = this.originalName; if (name.startsWith(this.prefix)) { hasPrefix = true; name = name.substring(this.prefix.length()); } this.hasPrefix = hasPrefix; this.obfuscated = this.baseName = name; } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return this.baseName; } @Override public String baseName() { return this.baseName; } /** * Sets the obfuscated name for this element * * @param name Mapping containing new name * @return fluent interface */ public ShadowElementName setObfuscatedName(IMapping<?> name) { this.obfuscated = name.getName(); return this; } /** * Sets the obfuscated name for this element * * @param name New name * @return fluent interface */ public ShadowElementName setObfuscatedName(String name) { this.obfuscated = name; return this; } @Override public boolean hasPrefix() { return this.hasPrefix; } /** * Get the prefix (if set), does not return the expected prefix */ public String prefix() { return this.hasPrefix ? this.prefix : ""; } /** * Get the base name */ public String name() { return this.prefix(this.baseName); } /** * Gets the obfuscated name (including prefix where appropriate */ public String obfuscated() { return this.prefix(this.obfuscated); } /** * Apply the prefix (if any) to the specified string * * @param name String to prefix * @return Prefixed string or original string if no prefix */ public String prefix(String name) { return this.hasPrefix ? this.prefix + name : name; } } /** * Mixin */ protected final AnnotatedMixin mixin; protected final String classRef; /** * Annotation processor */ protected final IMixinAnnotationProcessor ap; protected final IObfuscationManager obf; private IMappingConsumer mappings; AnnotatedMixinElementHandler(IMixinAnnotationProcessor ap, AnnotatedMixin mixin) { this.ap = ap; this.mixin = mixin; this.classRef = mixin.getClassRef(); this.obf = ap.getObfuscationManager(); } private IMappingConsumer getMappings() { if (this.mappings == null) { IMappingConsumer mappingConsumer = this.mixin.getMappings(); if (mappingConsumer instanceof Mappings) { this.mappings = ((Mappings)mappingConsumer).asUnique(); } else { this.mappings = mappingConsumer; } } return this.mappings; } protected final void addFieldMappings(String mcpName, String mcpSignature, ObfuscationData<MappingField> obfData) { for (ObfuscationType type : obfData) { MappingField obfField = obfData.get(type); this.addFieldMapping(type, mcpName, obfField.getSimpleName(), mcpSignature, obfField.getDesc()); } } /** * Add a field mapping to the local table */ protected final void addFieldMapping(ObfuscationType type, ShadowElementName name, String mcpSignature, String obfSignature) { this.addFieldMapping(type, name.name(), name.obfuscated(), mcpSignature, obfSignature); } /** * Add a field mapping to the local table */ protected final void addFieldMapping(ObfuscationType type, String mcpName, String obfName, String mcpSignature, String obfSignature) { MappingField from = new MappingField(this.classRef, mcpName, mcpSignature); MappingField to = new MappingField(this.classRef, obfName, obfSignature); this.getMappings().addFieldMapping(type, from, to); } protected final void addMethodMappings(String mcpName, String mcpSignature, ObfuscationData<MappingMethod> obfData) { for (ObfuscationType type : obfData) { MappingMethod obfMethod = obfData.get(type); this.addMethodMapping(type, mcpName, obfMethod.getSimpleName(), mcpSignature, obfMethod.getDesc()); } } /** * Add a method mapping to the local table */ protected final void addMethodMapping(ObfuscationType type, ShadowElementName name, String mcpSignature, String obfSignature) { this.addMethodMapping(type, name.name(), name.obfuscated(), mcpSignature, obfSignature); } /** * Add a method mapping to the local table */ protected final void addMethodMapping(ObfuscationType type, String mcpName, String obfName, String mcpSignature, String obfSignature) { MappingMethod from = new MappingMethod(this.classRef, mcpName, mcpSignature); MappingMethod to = new MappingMethod(this.classRef, obfName, obfSignature); this.getMappings().addMethodMapping(type, from, to); } /** * Check constraints for the specified annotation based on token values in * the current environment * * @param method Annotated method * @param annotation Annotation to check constraints */ protected final void checkConstraints(ExecutableElement method, AnnotationHandle annotation) { try { Constraint constraint = ConstraintParser.parse(annotation.<String>getValue("constraints")); try { constraint.check(this.ap.getTokenProvider()); } catch (ConstraintViolationException ex) { this.ap.printMessage(Kind.ERROR, ex.getMessage(), method, annotation.asMirror()); } } catch (InvalidConstraintException ex) { this.ap.printMessage(Kind.WARNING, ex.getMessage(), method, annotation.asMirror(), SuppressedBy.CONSTRAINTS); } } protected final void validateTarget(Element element, AnnotationHandle annotation, AliasedElementName name, String type) { if (element instanceof ExecutableElement) { this.validateTargetMethod((ExecutableElement)element, annotation, name, type, false, false); } else if (element instanceof VariableElement) { this.validateTargetField((VariableElement)element, annotation, name, type); } } /** * Checks whether the specified method exists in all targets and raises * warnings where appropriate */ protected final void validateTargetMethod(ExecutableElement method, AnnotationHandle annotation, AliasedElementName name, String type, boolean overwrite, boolean merge) { String signature = TypeUtils.getJavaSignature(method); for (TypeHandle target : this.mixin.getTargets()) { if (target.isImaginary()) { continue; } // Find method as-is MethodHandle targetMethod = target.findMethod(method); // Find method without prefix if (targetMethod == null && name.hasPrefix()) { targetMethod = target.findMethod(name.baseName(), signature); } // Check aliases if (targetMethod == null && name.hasAliases()) { for (String alias : name.getAliases()) { if ((targetMethod = target.findMethod(alias, signature)) != null) { break; } } } if (targetMethod != null) { if (overwrite) { this.validateMethodVisibility(method, annotation, type, target, targetMethod); } } else if (!merge) { this.printMessage(Kind.WARNING, "Cannot find target for " + type + " method in " + target, method, annotation, SuppressedBy.TARGET); } } } private void validateMethodVisibility(ExecutableElement method, AnnotationHandle annotation, String type, TypeHandle target, MethodHandle targetMethod) { Visibility visTarget = targetMethod.getVisibility(); if (visTarget == null) { return; } Visibility visMethod = TypeUtils.getVisibility(method); String visibility = "visibility of " + visTarget + " method in " + target; if (visTarget.ordinal() > visMethod.ordinal()) { this.printMessage(Kind.WARNING, visMethod + " " + type + " method cannot reduce " + visibility, method, annotation, SuppressedBy.VISIBILITY); } else if (visTarget == Visibility.PRIVATE && visMethod.ordinal() > visTarget.ordinal()) { this.printMessage(Kind.WARNING, visMethod + " " + type + " method will upgrade " + visibility, method, annotation, SuppressedBy.VISIBILITY); } } /** * Checks whether the specified field exists in all targets and raises * warnings where appropriate */ protected final void validateTargetField(VariableElement field, AnnotationHandle annotation, AliasedElementName name, String type) { String fieldType = field.asType().toString(); for (TypeHandle target : this.mixin.getTargets()) { if (target.isImaginary()) { continue; } // Search for field FieldHandle targetField = target.findField(field); if (targetField != null) { continue; } // Try search by alias List<String> aliases = name.getAliases(); for (String alias : aliases) { if ((targetField = target.findField(alias, fieldType)) != null) { break; } } if (targetField == null) { this.ap.printMessage(Kind.WARNING, "Cannot find target for " + type + " field in " + target, field, annotation.asMirror(), SuppressedBy.TARGET); } } } /** * Checks whether the referenced method exists in all targets and raises * warnings where appropriate */ protected final void validateReferencedTarget(ExecutableElement method, AnnotationHandle inject, ITargetSelector reference, String type) { if (!(reference instanceof ITargetSelectorByName)) { return; } ITargetSelectorByName nameRef = (ITargetSelectorByName)reference; String signature = nameRef.toDescriptor(); for (TypeHandle target : this.mixin.getTargets()) { if (target.isImaginary()) { continue; } MethodHandle targetMethod = target.findMethod(nameRef.getName(), signature); if (targetMethod == null) { this.ap.printMessage(Kind.WARNING, "Cannot find target method for " + type + " in " + target, method, inject.asMirror(), SuppressedBy.TARGET); } } } private void printMessage(Kind kind, String msg, Element e, AnnotationHandle annotation, SuppressedBy suppressedBy) { if (annotation == null) { this.ap.printMessage(kind, msg, e, suppressedBy); } else { this.ap.printMessage(kind, msg, e, annotation.asMirror(), suppressedBy); } } protected static <T extends IMapping<T>> ObfuscationData<T> stripOwnerData(ObfuscationData<T> data) { ObfuscationData<T> stripped = new ObfuscationData<T>(); for (ObfuscationType type : data) { T mapping = data.get(type); stripped.put(type, mapping.move(null)); } return stripped; } protected static <T extends IMapping<T>> ObfuscationData<T> stripDescriptors(ObfuscationData<T> data) { ObfuscationData<T> stripped = new ObfuscationData<T>(); for (ObfuscationType type : data) { T mapping = data.get(type); stripped.put(type, mapping.transform(null)); } return stripped; } }