/*
* 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.HashMap;
import java.util.Map;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.tools.Diagnostic;
import javax.tools.Diagnostic.Kind;
import org.spongepowered.asm.mixin.injection.struct.InjectionPointData;
import org.spongepowered.asm.mixin.injection.struct.InvalidMemberDescriptorException;
import org.spongepowered.asm.mixin.injection.struct.MemberInfo;
import org.spongepowered.asm.obfuscation.mapping.common.MappingField;
import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod;
import org.spongepowered.asm.util.Constants;
import org.spongepowered.tools.obfuscation.ReferenceManager.ReferenceConflictException;
import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor;
import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor.CompilerEnvironment;
import org.spongepowered.tools.obfuscation.mirror.AnnotationHandle;
import org.spongepowered.tools.obfuscation.mirror.TypeHandle;
import org.spongepowered.tools.obfuscation.struct.InjectorRemap;
/**
* A module for {@link AnnotatedMixin} whic handles injectors
*/
class AnnotatedMixinElementHandlerInjector extends AnnotatedMixinElementHandler {
/**
* Injector element
*/
static class AnnotatedElementInjector extends AnnotatedElement<ExecutableElement> {
private final InjectorRemap state;
public AnnotatedElementInjector(ExecutableElement element, AnnotationHandle annotation, InjectorRemap shouldRemap) {
super(element, annotation);
this.state = shouldRemap;
}
public boolean shouldRemap() {
return this.state.shouldRemap();
}
public void addMessage(Diagnostic.Kind kind, CharSequence msg, Element element, AnnotationHandle annotation) {
this.state.addMessage(kind, msg, element, annotation);
}
@Override
public String toString() {
return this.getAnnotation().toString();
}
}
/**
* Injection point element
*/
static class AnnotatedElementInjectionPoint extends AnnotatedElement<ExecutableElement> {
private final AnnotationHandle at;
private Map<String, String> args;
private final InjectorRemap state;
public AnnotatedElementInjectionPoint(ExecutableElement element, AnnotationHandle inject, AnnotationHandle at, InjectorRemap state) {
super(element, inject);
this.at = at;
this.state = state;
}
public boolean shouldRemap() {
return this.at.getBoolean("remap", this.state.shouldRemap());
}
public AnnotationHandle getAt() {
return this.at;
}
public String getAtArg(String key) {
if (this.args == null) {
this.args = new HashMap<String, String>();
for (String arg : this.at.<String>getList("args")) {
if (arg == null) {
continue;
}
int eqPos = arg.indexOf('=');
if (eqPos > -1) {
this.args.put(arg.substring(0, eqPos), arg.substring(eqPos + 1));
} else {
this.args.put(arg, "");
}
}
}
return this.args.get(key);
}
public void notifyRemapped() {
this.state.notifyRemapped();
}
}
AnnotatedMixinElementHandlerInjector(IMixinAnnotationProcessor ap, AnnotatedMixin mixin) {
super(ap, mixin);
}
public void registerInjector(AnnotatedElementInjector elem) {
if (this.mixin.isInterface()) {
this.ap.printMessage(Kind.ERROR, "Injector in interface is unsupported", elem.getElement());
}
String reference = elem.getAnnotation().<String>getValue("method");
MemberInfo targetMember = MemberInfo.parse(reference);
if (targetMember.name == null) {
return;
}
try {
targetMember.validate();
} catch (InvalidMemberDescriptorException ex) {
elem.printMessage(this.ap, Kind.ERROR, ex.getMessage());
}
if (targetMember.desc != null) {
this.validateReferencedTarget(elem.getElement(), elem.getAnnotation(), targetMember, elem.toString());
}
if (!elem.shouldRemap()) {
return;
}
for (TypeHandle target : this.mixin.getTargets()) {
if (!this.registerInjector(elem, reference, targetMember, target)) {
break;
}
}
}
private boolean registerInjector(AnnotatedElementInjector elem, String reference, MemberInfo targetMember, TypeHandle target) {
String desc = target.findDescriptor(targetMember);
if (desc == null) {
Kind error = this.mixin.isMultiTarget() ? Kind.ERROR : Kind.WARNING;
if (target.isSimulated()) {
elem.printMessage(this.ap, Kind.NOTE, elem + " target '" + reference + "' in @Pseudo mixin will not be obfuscated");
} else if (target.isImaginary()) {
elem.printMessage(this.ap, error, elem + " target requires method signature because enclosing type information for "
+ target + " is unavailable");
} else if (!Constants.CTOR.equals(targetMember.name)) {
elem.printMessage(this.ap, error, "Unable to determine signature for " + elem + " target method");
}
return true;
}
String targetName = elem + " target " + targetMember.name;
MappingMethod targetMethod = target.getMappingMethod(targetMember.name, desc);
ObfuscationData<MappingMethod> obfData = this.obf.getDataProvider().getObfMethod(targetMethod);
if (obfData.isEmpty()) {
if (target.isSimulated()) {
obfData = this.obf.getDataProvider().getRemappedMethod(targetMethod);
} else {
Kind error = Constants.CTOR.equals(targetMember.name) ? Kind.WARNING : Kind.ERROR;
elem.addMessage(error, "No obfuscation mapping for " + targetName, elem.getElement(), elem.getAnnotation());
return false;
}
}
try {
// If the original owner is unspecified, and the mixin is multi-target, we strip the owner from the obf mappings
if ((targetMember.owner == null && this.mixin.isMultiTarget()) || target.isSimulated()) {
obfData = AnnotatedMixinElementHandler.<MappingMethod>stripOwnerData(obfData);
}
this.obf.getReferenceManager().addMethodMapping(this.classRef, reference, obfData);
} catch (ReferenceConflictException ex) {
String conflictType = this.mixin.isMultiTarget() ? "Multi-target" : "Target";
elem.printMessage(this.ap, Kind.ERROR, conflictType + " reference conflict for " + targetName + ": " + reference + " -> "
+ ex.getNew() + " previously defined as " + ex.getOld());
}
return true;
}
/**
* Register a {@link org.spongepowered.asm.mixin.injection.At} annotation
* and process the references
*/
public void registerInjectionPoint(AnnotatedElementInjectionPoint elem, String format) {
if (this.mixin.isInterface()) {
this.ap.printMessage(Kind.ERROR, "Injector in interface is unsupported", elem.getElement());
}
if (!elem.shouldRemap()) {
return;
}
String type = InjectionPointData.parseType(elem.getAt().<String>getValue("value"));
String target = elem.getAt().<String>getValue("target");
if ("NEW".equals(type)) {
this.remapNewTarget(String.format(format, type + ".<target>"), target, elem);
this.remapNewTarget(String.format(format, type + ".args[class]"), elem.getAtArg("class"), elem);
} else {
this.remapReference(String.format(format, type + ".<target>"), target, elem);
}
}
protected final void remapNewTarget(String subject, String reference, AnnotatedElementInjectionPoint elem) {
if (reference == null) {
return;
}
MemberInfo member = MemberInfo.parse(reference);
String target = member.toCtorType();
if (target != null) {
String desc = member.toCtorDesc();
MappingMethod m = new MappingMethod(target, ".", desc != null ? desc : "()V");
ObfuscationData<MappingMethod> remapped = this.obf.getDataProvider().getRemappedMethod(m);
if (remapped.isEmpty()) {
this.ap.printMessage(Kind.WARNING, "Cannot find class mapping for " + subject + " '" + target + "'", elem.getElement(),
elem.getAnnotation().asMirror());
return;
}
ObfuscationData<String> mappings = new ObfuscationData<String>();
for (ObfuscationType type : remapped) {
MappingMethod mapping = remapped.get(type);
if (desc == null) {
mappings.put(type, mapping.getOwner());
} else {
mappings.put(type, mapping.getDesc().replace(")V", ")L" + mapping.getOwner() + ";"));
}
}
this.obf.getReferenceManager().addClassMapping(this.classRef, reference, mappings);
}
elem.notifyRemapped();
}
protected final void remapReference(String subject, String reference, AnnotatedElementInjectionPoint elem) {
if (reference == null) {
return;
}
// JDT supports hanging the error on the @At annotation directly, doing this in javac doesn't work
AnnotationMirror errorsOn = (this.ap.getCompilerEnvironment() == CompilerEnvironment.JDT ? elem.getAt() : elem.getAnnotation()).asMirror();
MemberInfo targetMember = MemberInfo.parse(reference);
if (!targetMember.isFullyQualified()) {
String missing = targetMember.owner == null ? (targetMember.desc == null ? "owner and signature" : "owner") : "signature";
this.ap.printMessage(Kind.ERROR, subject + " is not fully qualified, missing " + missing, elem.getElement(), errorsOn);
return;
}
try {
targetMember.validate();
} catch (InvalidMemberDescriptorException ex) {
this.ap.printMessage(Kind.ERROR, ex.getMessage(), elem.getElement(), errorsOn);
}
try {
if (targetMember.isField()) {
ObfuscationData<MappingField> obfFieldData = this.obf.getDataProvider().getObfFieldRecursive(targetMember);
if (obfFieldData.isEmpty()) {
this.ap.printMessage(Kind.WARNING, "Cannot find field mapping for " + subject + " '" + reference + "'", elem.getElement(),
errorsOn);
return;
}
this.obf.getReferenceManager().addFieldMapping(this.classRef, reference, targetMember, obfFieldData);
} else {
ObfuscationData<MappingMethod> obfMethodData = this.obf.getDataProvider().getObfMethodRecursive(targetMember);
if (obfMethodData.isEmpty()) {
if (targetMember.owner == null || !targetMember.owner.startsWith("java/lang/")) {
this.ap.printMessage(Kind.WARNING, "Cannot find method mapping for " + subject + " '" + reference + "'", elem.getElement(),
errorsOn);
return;
}
}
this.obf.getReferenceManager().addMethodMapping(this.classRef, reference, targetMember, obfMethodData);
}
} catch (ReferenceConflictException ex) {
// Since references are fully-qualified, it shouldn't be possible for there to be multiple mappings, however
// we catch and log the error in case something weird happens in the mapping provider
this.ap.printMessage(Kind.ERROR, "Unexpected reference conflict for " + subject + ": " + reference + " -> "
+ ex.getNew() + " previously defined as " + ex.getOld(), elem.getElement(), errorsOn);
return;
}
elem.notifyRemapped();
}
}