diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java index f10832b79..3070d1c15 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java @@ -288,7 +288,7 @@ private void printMessage(MessageType type, CharSequence msg, SuppressedBy suppr private void addSoftTarget(TypeHandle type, String reference) { ObfuscationData obfClassData = this.obf.getDataProvider().getObfClass(type); if (!obfClassData.isEmpty()) { - this.obf.getReferenceManager().addClassMapping(this.classRef, reference, obfClassData); + this.obf.getReferenceManager().addMapping(this.classRef, reference, obfClassData); } this.addTarget(type); @@ -498,6 +498,11 @@ public int getPriority() { throw new UnsupportedOperationException("Priority not available at compile time"); } + @Override + public boolean isCompileTime() { + return true; + } + @Override public IAnnotationHandle getAnnotation(Class annotationClass) { return AnnotationHandle.of(this.mixin, annotationClass); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java index f91add4af..40a9d007b 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java @@ -326,7 +326,7 @@ private void registerFactoryForTarget(AnnotatedElementInvoker elem, TypeHandle t } ObfuscationData obfData = this.obf.getDataProvider().getObfClass(elem.getAnnotationValue().replace('.', '/')); - this.obf.getReferenceManager().addClassMapping(this.mixin.getClassRef(), elem.getAnnotationValue(), obfData); + this.obf.getReferenceManager().addMapping(this.mixin.getClassRef(), elem.getAnnotationValue(), obfData); } private String getAccessorTargetName(AnnotatedElementAccessor elem) { diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java index 1328cd9fd..a6888f167 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java @@ -25,7 +25,10 @@ package org.spongepowered.tools.obfuscation; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; @@ -41,9 +44,11 @@ import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; import org.spongepowered.asm.mixin.injection.selectors.TargetSelector; import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; +import org.spongepowered.asm.mixin.injection.struct.MemberInfo; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.obfuscation.mapping.common.MappingField; import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; +import org.spongepowered.asm.util.NameAndDesc; import org.spongepowered.asm.util.asm.IAnnotationHandle; import org.spongepowered.tools.obfuscation.ReferenceManager.ReferenceConflictException; import org.spongepowered.tools.obfuscation.ext.SpecialPackages; @@ -195,6 +200,7 @@ public void registerInjector(AnnotatedElementInjector elem) { private void registerInjectorTarget(AnnotatedElementInjector elem, String reference, ITargetSelector targetSelector, String subject) { try { targetSelector.validate(); + targetSelector.validateNext(); } catch (InvalidSelectorException ex) { elem.printMessage(this.ap, MessageType.TARGET_SELECTOR_VALIDATION, ex.getMessage()); } @@ -204,88 +210,159 @@ private void registerInjectorTarget(AnnotatedElementInjector elem, String refere } ITargetSelectorByName targetMember = (ITargetSelectorByName)targetSelector; - if (targetMember.getName() == null) { - return; - } - - if (targetMember.getDesc() != null) { + + if (targetMember.getName() != null && targetMember.getDesc() != null) { this.validateReferencedTarget(elem, reference, targetMember, subject); } - - if (targetSelector instanceof ITargetSelectorRemappable && elem.shouldRemap()) { - for (TypeHandle target : this.mixin.getTargets()) { - if (!this.registerInjector(elem, reference, (ITargetSelectorRemappable)targetMember, target)) { - break; - } - } + + if (targetSelector instanceof ITargetSelectorRemappable) { + this.registerInjector(elem, reference, (ITargetSelectorRemappable) targetMember); } } - private boolean registerInjector(AnnotatedElementInjector elem, String reference, ITargetSelectorRemappable targetMember, TypeHandle target) { - String desc = target.findDescriptor(targetMember); - if (desc == null) { - MessageType messageType = this.mixin.isMultiTarget() ? MessageType.MISSING_INJECTOR_DESC_MULTITARGET - : MessageType.MISSING_INJECTOR_DESC_SINGLETARGET; - if (target.isSimulated()) { - elem.printMessage(this.ap, MessageType.MISSING_INJECTOR_DESC_SIMULATED, elem + " target '" + reference - + "' in @Pseudo mixin will not be obfuscated"); - } else if (target.isImaginary()) { - elem.printMessage(this.ap, messageType, elem + " target requires method signature because enclosing type information for " - + target + " is unavailable"); - } else if (!targetMember.isInitialiser()) { - elem.printMessage(this.ap, messageType, "Unable to determine descriptor for " + elem + " target method"); - } - return true; - } - + private void registerInjector(AnnotatedElementInjector elem, String reference, ITargetSelectorRemappable targetMember) { String targetName = elem + " target " + targetMember.getName(); - MappingMethod targetMethod = target.getMappingMethod(targetMember.getName(), desc); - ObfuscationData obfData = this.obf.getDataProvider().getObfMethod(targetMethod); - if (obfData.isEmpty()) { - if (target.isSimulated()) { - obfData = this.obf.getDataProvider().getRemappedMethod(targetMethod); - } else if (targetMember.isClassInitialiser()) { - return true; - } else { - elem.addMessage(targetMember.isConstructor() ? MessageType.NO_OBFDATA_FOR_CTOR : MessageType.NO_OBFDATA_FOR_TARGET, - "Unable to locate obfuscation mapping for " + targetName, elem.getElement(), elem.getAnnotation()); - return false; + ObfuscationData remapped; + if (elem.shouldRemap() && targetMember.getName() != null) { + remapped = this.remapTarget(elem, reference, targetMember, targetName); + if (remapped == null) { + return; } + } else { + remapped = new ObfuscationData<>(); } - + + if (targetMember instanceof MemberInfo) { + // Need to try remapping the nested selectors, even if remap is false + remapped = this.remapMemberInfo((MemberInfo) targetMember, remapped); + } else if (!elem.shouldRemap()) { + return; + } + IReferenceManager refMaps = this.obf.getReferenceManager(); try { - // If the original owner is unspecified, and the mixin is multi-target, we strip the owner from the obf mappings - if ((targetMember.getOwner() == null && this.mixin.isMultiTarget()) || target.isSimulated()) { - obfData = AnnotatedMixinElementHandler.stripOwnerData(obfData); - } - refMaps.addMethodMapping(this.classRef, reference, obfData); + refMaps.addMapping(this.classRef, reference, remapped); } catch (ReferenceConflictException ex) { String conflictType = this.mixin.isMultiTarget() ? "Multi-target" : "Target"; - + + elem.printMessage(this.ap, MessageType.INJECTOR_MAPPING_CONFLICT, conflictType + " reference conflict for " + targetName + ": " + + reference + " -> " + ex.getNew() + " previously defined as " + ex.getOld()); + } + } + + private ObfuscationData remapTarget(AnnotatedElementInjector elem, String reference, ITargetSelectorRemappable targetMember, String targetName) { + ObfuscationData> remapped = new ObfuscationData<>(); + for (ObfuscationEnvironment env : this.obf.getEnvironments()) { + remapped.put(env.getType(), new HashSet<>()); + } + + for (TypeHandle target : this.mixin.getTargets()) { + String desc = target.findDescriptor(targetMember); + if (desc == null) { + MessageType messageType = this.mixin.isMultiTarget() ? MessageType.MISSING_INJECTOR_DESC_MULTITARGET + : MessageType.MISSING_INJECTOR_DESC_SINGLETARGET; + if (target.isSimulated()) { + elem.printMessage(this.ap, MessageType.MISSING_INJECTOR_DESC_SIMULATED, elem + " target '" + reference + + "' in @Pseudo mixin will not be obfuscated"); + } else if (target.isImaginary()) { + elem.printMessage(this.ap, messageType, elem + " target requires method signature because enclosing type information for " + + target + " is unavailable"); + } else if (!targetMember.isInitialiser()) { + elem.printMessage(this.ap, messageType, "Unable to determine descriptor for " + elem + " target method"); + } + continue; + } + + MappingMethod targetMethod = target.getMappingMethod(targetMember.getName(), desc); + ObfuscationData obfData = this.obf.getDataProvider().getObfMethod(targetMethod); + if (obfData.isEmpty()) { + if (target.isSimulated()) { + obfData = this.obf.getDataProvider().getRemappedMethod(targetMethod); + } else if (targetMember.isClassInitialiser()) { + continue; + } else { + elem.addMessage(targetMember.isConstructor() ? MessageType.NO_OBFDATA_FOR_CTOR : MessageType.NO_OBFDATA_FOR_TARGET, + "Unable to locate obfuscation mapping for " + targetName, elem.getElement(), elem.getAnnotation()); + return null; + } + } + + if ((targetMember.getOwner() == null && this.mixin.isMultiTarget()) || target.isSimulated()) { + obfData = AnnotatedMixinElementHandler.stripOwnerData(obfData); + } + + for (ObfuscationEnvironment env : this.obf.getEnvironments()) { + MappingMethod mapping = obfData.get(env.getType()); + String name = mapping.getSimpleName(); + if (mapping.getOwner() != null) { + name = 'L' + mapping.getOwner() + ';' + name; + } + remapped.get(env.getType()).add(new NameAndDesc(name, mapping.getDesc())); + } + } + + ObfuscationData result = new ObfuscationData<>(); + for (ObfuscationEnvironment env : this.obf.getEnvironments()) { + Set members = remapped.get(env.getType()); + if (members.size() == 1) { + NameAndDesc member = members.iterator().next(); + result.put(env.getType(), member.name + member.desc); + continue; + } + if (elem.hasCoerceArgument() && targetMember.getOwner() == null && targetMember.getDesc() == null) { - ITargetSelector oldMember = TargetSelector.parse(ex.getOld(), elem); - ITargetSelector newMember = TargetSelector.parse(ex.getNew(), elem); - String oldName = oldMember instanceof ITargetSelectorByName ? ((ITargetSelectorByName)oldMember).getName() : oldMember.toString(); - String newName = newMember instanceof ITargetSelectorByName ? ((ITargetSelectorByName)newMember).getName() : newMember.toString(); - if (oldName != null && oldName.equals(newName)) { - obfData = AnnotatedMixinElementHandler.stripDescriptors(obfData); - refMaps.setAllowConflicts(true); - refMaps.addMethodMapping(this.classRef, reference, obfData); - refMaps.setAllowConflicts(false); + Set names = members.stream().map(it -> it.name).collect(Collectors.toSet()); + if (names.size() == 1) { + String name = names.iterator().next(); + result.put(env.getType(), name); // This is bad because in notch mappings, using the bare target name might cause everything to explode - elem.printMessage(this.ap, MessageType.BARE_REFERENCE, "Coerced " + conflictType + " reference has conflicting descriptors for " - + targetName + ": Storing bare references " + obfData.values() + " in refMap"); - return true; + elem.printMessage(this.ap, MessageType.BARE_REFERENCE, "Coerced Multi-target reference has conflicting descriptors for " + + targetName + ": Storing bare reference " + name + " in refMap"); } + continue; } - - elem.printMessage(this.ap, MessageType.INJECTOR_MAPPING_CONFLICT, conflictType + " reference conflict for " + targetName + ": " - + reference + " -> " + ex.getNew() + " previously defined as " + ex.getOld()); + + elem.printMessage( + this.ap, MessageType.INJECTOR_MAPPING_CONFLICT, + "Multi-target reference conflict for " + targetName + ": " + members + ); } - - return true; + + return result; + } + + private ObfuscationData remapMemberInfo(MemberInfo targetMember, ObfuscationData rootNames) { + MemberInfo next = (MemberInfo) targetMember.next(); + if (next == null) { + return rootNames; + } + + ObfuscationData result = new ObfuscationData<>(); + for (ObfuscationEnvironment env : this.obf.getEnvironments()) { + boolean needsMapping = false; + String rootName = rootNames.get(env.getType()); + if (rootName == null) { + rootName = targetMember.headToString(); + } else { + needsMapping = true; + } + + MemberInfo remappedNext = next.remapNestedUsing(new ObfuscationEnvironmentRemapper(env, this.obf.getDataProvider())); + if (remappedNext == null) { + // No change + remappedNext = next; + } else { + needsMapping = true; + } + + if (needsMapping) { + String remapped = rootName + " ->" + targetMember.recurseDepthToString() + ' ' + remappedNext; + result.put(env.getType(), remapped); + } + } + + return result; } /** @@ -357,7 +434,7 @@ protected final void remapNewTarget(String subject, String reference, ITargetSel } } - this.obf.getReferenceManager().addClassMapping(this.classRef, reference, mappings); + this.obf.getReferenceManager().addMapping(this.classRef, reference, mappings); } elem.notifyRemapped(); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java index 6ee81ef7f..6040bde51 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java @@ -257,6 +257,10 @@ public MappingMethod getObfMethod(MappingMethod method, boolean lazyRemap) { */ @Override public ITargetSelectorRemappable remapDescriptor(ITargetSelectorRemappable method) { + if (!this.initMappings()) { + return null; + } + boolean transformed = false; String owner = method.getOwner(); @@ -289,6 +293,10 @@ public ITargetSelectorRemappable remapDescriptor(ITargetSelectorRemappable metho */ @Override public String remapDescriptor(String desc) { + if (!this.initMappings()) { + return desc; + } + String newDesc = ObfuscationUtil.mapDescriptor(desc, this.remapper); return newDesc != null ? newDesc : desc; } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironmentRemapper.java b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironmentRemapper.java new file mode 100644 index 000000000..b6c16e030 --- /dev/null +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironmentRemapper.java @@ -0,0 +1,84 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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 org.spongepowered.asm.mixin.extensibility.IRemapper; +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.tools.obfuscation.interfaces.IObfuscationDataProvider; + +class ObfuscationEnvironmentRemapper implements IRemapper { + private final ObfuscationEnvironment env; + private final IObfuscationDataProvider dataProvider; + + public ObfuscationEnvironmentRemapper(ObfuscationEnvironment env, IObfuscationDataProvider dataProvider) { + this.env = env; + this.dataProvider = dataProvider; + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + MappingMethod remapped = this.dataProvider.getObfMethodRecursive(new MemberInfo(name, owner, desc)).get(this.env.getType()); + if (remapped != null) { + return remapped.getSimpleName(); + } + return name; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + MappingField remapped = this.dataProvider.getObfFieldRecursive(new MemberInfo(name, owner, desc)).get(this.env.getType()); + if (remapped != null) { + return remapped.getSimpleName(); + } + return name; + } + + @Override + public String map(String typeName) { + String remapped = this.dataProvider.getObfClass(typeName).get(this.env.getType()); + if (remapped != null) { + return remapped; + } + return typeName; + } + + @Override + public String unmap(String typeName) { + throw new UnsupportedOperationException(); + } + + @Override + public String mapDesc(String desc) { + return this.env.remapDescriptor(desc); + } + + @Override + public String unmapDesc(String desc) { + throw new UnsupportedOperationException(); + } +} diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java index ec1ad5544..6eca0a93b 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java @@ -246,9 +246,9 @@ public void addFieldMapping(String className, String reference, ITargetSelectorR * org.spongepowered.tools.obfuscation.ObfuscationData) */ @Override - public void addClassMapping(String className, String reference, ObfuscationData obfClassData) { + public void addMapping(String className, String reference, ObfuscationData obfData) { for (ObfuscationEnvironment env : this.environments) { - String remapped = obfClassData.get(env.getType()); + String remapped = obfData.get(env.getType()); if (remapped != null) { this.addMapping(env.getType(), className, reference, remapped); } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IReferenceManager.java b/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IReferenceManager.java index 74312a84f..0cd493698 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IReferenceManager.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IReferenceManager.java @@ -100,12 +100,12 @@ public abstract void addFieldMapping(String className, String reference, ITarget ObfuscationData obfFieldData); /** - * Adds a class mapping to the internal refmap + * Adds an arbitrary mapping to the internal refmap * * @param className Mixin class name which owns the refmap entry * @param reference Original reference, as it appears in the annotation - * @param obfClassData Class obf names + * @param obfData Obf strings */ - public abstract void addClassMapping(String className, String reference, ObfuscationData obfClassData); + public abstract void addMapping(String className, String reference, ObfuscationData obfData); } diff --git a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java index ae057f32b..f75ad6084 100644 --- a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java @@ -71,10 +71,15 @@ public final class FabricUtil { */ public static final int COMPATIBILITY_0_17_4 = 17004; // 0.17.4+mixin.0.8.7 + /** + * Fabric compatibility version 0.17.5 + */ + public static final int COMPATIBILITY_0_17_5 = 17005; // 0.17.5+mixin.0.8.7 + /** * Latest compatibility version */ - public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_17_4; + public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_17_5; public static String getModId(IMixinConfig config) { return getModId(config, "(unknown)"); @@ -89,10 +94,13 @@ public static String getModId(ISelectorContext context) { } public static int getCompatibility(ISelectorContext context) { - return getCompatibility(getConfig(context)); + return getCompatibility(context.getMixin()); } public static int getCompatibility(IMixinContext context) { + if (context.isCompileTime()) { + return COMPATIBILITY_LATEST; + } return getCompatibility(context.getMixin().getConfig()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ElementNode.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ElementNode.java index e72ea2854..2497a50b7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ElementNode.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ElementNode.java @@ -27,6 +27,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; +import java.util.Objects; import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; @@ -166,16 +167,6 @@ public MethodNode get() { return this.method; } - @Override - public boolean equals(Object obj) { - return this.method.equals(obj); - } - - @Override - public int hashCode() { - return this.method.hashCode(); - } - } /** @@ -231,16 +222,6 @@ public String getSignature() { public FieldNode get() { return this.field; } - - @Override - public boolean equals(Object obj) { - return this.field.equals(obj); - } - - @Override - public int hashCode() { - return this.field.hashCode(); - } } @@ -289,45 +270,27 @@ public String getSignature() { public MethodInsnNode get() { return this.insn; } - - @Override - public boolean equals(Object obj) { - return this.insn.equals(obj); - } - - @Override - public int hashCode() { - return this.insn.hashCode(); - } } /** - * ElementNode for InvokeDynamicInsnNode + * ElementNode for SAM instantiations using INVOKEDYNAMIC */ - static class ElementNodeInvokeDynamicInsn extends ElementNode { + static class ElementNodeLmfInsn extends ElementNode { - private InvokeDynamicInsnNode insn; + private final InvokeDynamicInsnNode insn; - private Type samMethodType; + private final Type samMethodType; - private Handle implMethod; + private final Handle implMethod; - private Type instantiatedMethodType; + private final Type instantiatedMethodType; - ElementNodeInvokeDynamicInsn(InvokeDynamicInsnNode invokeDynamic) { + ElementNodeLmfInsn(InvokeDynamicInsnNode invokeDynamic) { this.insn = invokeDynamic; - - if (invokeDynamic.bsmArgs != null && invokeDynamic.bsmArgs.length > 1) { - Object samMethodType = invokeDynamic.bsmArgs[0]; - Object implMethod = invokeDynamic.bsmArgs[1]; - Object instantiatedMethodType = invokeDynamic.bsmArgs[2]; - if (samMethodType instanceof Type && implMethod instanceof Handle && instantiatedMethodType instanceof Type) { - this.samMethodType = (Type)samMethodType; - this.implMethod = (Handle)implMethod; - this.instantiatedMethodType = (Type)instantiatedMethodType; - } - } + this.samMethodType = (Type) invokeDynamic.bsmArgs[0]; + this.implMethod = (Handle) invokeDynamic.bsmArgs[1]; + this.instantiatedMethodType = (Type) invokeDynamic.bsmArgs[2]; } @Override @@ -337,7 +300,7 @@ public NodeType getType() { @Override public boolean isField() { - return this.implMethod != null && Handles.isField(this.implMethod); + return Handles.isField(this.implMethod); } @Override @@ -347,7 +310,12 @@ public AbstractInsnNode getInsn() { @Override public String getOwner() { - return this.implMethod != null ? this.implMethod.getOwner() : this.insn.name; + return Type.getReturnType(this.insn.desc).getInternalName(); + } + + @Override + public String getImplOwner() { + return this.implMethod.getOwner(); } @Override @@ -356,23 +324,18 @@ public String getName() { } @Override - public String getSyntheticName() { - return this.implMethod != null ? this.implMethod.getName() : this.insn.name; + public String getImplName() { + return this.implMethod.getName(); } @Override public String getDesc() { - return this.implMethod != null ? this.implMethod.getDesc() : this.insn.desc; - } - - @Override - public String getDelegateDesc() { - return this.samMethodType != null ? this.samMethodType.getDescriptor() : this.getDesc(); + return this.instantiatedMethodType.getDescriptor(); } @Override public String getImplDesc() { - return this.instantiatedMethodType != null ? this.instantiatedMethodType.getDescriptor() : this.getDesc(); + return this.implMethod.getDesc(); } @Override @@ -384,17 +347,13 @@ public String getSignature() { public InvokeDynamicInsnNode get() { return this.insn; } - - @Override - public boolean equals(Object obj) { - return this.insn.equals(obj); - } - - @Override - public int hashCode() { - return this.insn.hashCode(); - } + static ElementNodeLmfInsn of(InvokeDynamicInsnNode insn) { + if (!insn.bsm.equals(Handles.LMF_HANDLE) && !insn.bsm.equals(Handles.ALT_LMF_HANDLE)) { + return null; + } + return new ElementNodeLmfInsn(insn); + } } /** @@ -447,16 +406,6 @@ public String getSignature() { public FieldInsnNode get() { return this.insn; } - - @Override - public boolean equals(Object obj) { - return this.insn.equals(obj); - } - - @Override - public int hashCode() { - return this.insn.hashCode(); - } } @@ -467,11 +416,11 @@ static class ElementNodeIterator implements Iterator iter; - private final boolean filterDynamic; + private final boolean filterLmf; - ElementNodeIterator(Iterator iter, boolean filterDynamic) { + ElementNodeIterator(Iterator iter, boolean filterLmf) { this.iter = iter; - this.filterDynamic = filterDynamic; + this.filterLmf = filterLmf; } @Override @@ -482,7 +431,7 @@ public boolean hasNext() { @Override public ElementNode next() { AbstractInsnNode elem = this.iter.next(); - return !this.filterDynamic || (elem != null && elem.getOpcode() == Opcodes.INVOKEDYNAMIC) ? ElementNode.of(elem) : null; + return !this.filterLmf || (elem != null && elem.getOpcode() == Opcodes.INVOKEDYNAMIC) ? ElementNode.of(elem) : null; } } @@ -494,16 +443,16 @@ static class ElementNodeIterable implements Iterable iterable; - private final boolean filterDynamic; + private final boolean filterLmf; - public ElementNodeIterable(Iterable iterable, boolean filterDynamic) { + public ElementNodeIterable(Iterable iterable, boolean filterLmf) { this.iterable = iterable; - this.filterDynamic = filterDynamic; + this.filterLmf = filterLmf; } @Override public Iterator> iterator() { - return new ElementNodeIterator(this.iterable.iterator(), this.filterDynamic); + return new ElementNodeIterator(this.iterable.iterator(), this.filterLmf); } } @@ -549,9 +498,17 @@ public AbstractInsnNode getInsn() { /** * Get the element owner's name, if this element has an owner, otherwise - * returns null + * returns null. For LMF elements this is the SAM type + * being implemented. */ public abstract String getOwner(); + + /** + * For LMF elements, returns the owner of the implementing method. + */ + public String getImplOwner() { + return this.getOwner(); + } /** * Get the element name @@ -559,30 +516,21 @@ public AbstractInsnNode getInsn() { public abstract String getName(); /** - * Get the synthetic element name. For INVOKEDYNAMIC elements this is the - * real name of the lambda method implementing the delegate. + * For LMF elements, returns the real name of the implementing method. */ - public String getSyntheticName() { + public String getImplName() { return this.getName(); } /** - * Get the element descriptor. For INVOKEDYNAMIC this is the full descriptor - * of the lambda (including prepended captures). + * Get the element descriptor. For LMF elements this is the specialised + * descriptor of the SAM. */ public abstract String getDesc(); /** - * For INVOKEDYNAMIC elements, returns original descriptor of the delegate. - */ - public String getDelegateDesc() { - return this.getDesc(); - } - - /** - * For INVOKEDYNAMIC elements, returns specialised descriptor of the - * delegate (lambda descriptor without prepended captures), can be the same - * as the delegate descriptor or more specialised. + * For LMF elements, returns the full descriptor of the implementing + * method (including any leading parameters bound to it). */ public String getImplDesc() { return this.getDesc(); @@ -611,6 +559,16 @@ public String toString() { return String.format("%s%s%s", owner, Strings.nullToEmpty(this.getName()), desc); } + @Override + public final boolean equals(Object obj) { + return this.getClass() == obj.getClass() && Objects.equals(this.get(), ((ElementNode) obj).get()); + } + + @Override + public final int hashCode() { + return Objects.hashCode(this.get()); + } + /** * Create an ElementNode wrapper for the supplied method node * @@ -654,7 +612,7 @@ public static ElementNode of(ClassNode owner, TNode node) { } else if (node instanceof MethodInsnNode) { return (ElementNode)new ElementNodeMethodInsn((MethodInsnNode)node); } else if (node instanceof InvokeDynamicInsnNode) { - return (ElementNode)new ElementNodeInvokeDynamicInsn((InvokeDynamicInsnNode)node); + return (ElementNode) ElementNodeLmfInsn.of((InvokeDynamicInsnNode)node); } else if (node instanceof FieldInsnNode) { return (ElementNode)new ElementNodeFieldInsn((FieldInsnNode)node); } @@ -675,7 +633,7 @@ public static ElementNode of(TNode node) if (node instanceof MethodInsnNode) { return (ElementNode)new ElementNodeMethodInsn((MethodInsnNode)node); } else if (node instanceof InvokeDynamicInsnNode) { - return (ElementNode)new ElementNodeInvokeDynamicInsn((InvokeDynamicInsnNode)node); + return (ElementNode) ElementNodeLmfInsn.of((InvokeDynamicInsnNode)node); } else if (node instanceof FieldInsnNode) { return (ElementNode)new ElementNodeFieldInsn((FieldInsnNode)node); } @@ -742,12 +700,12 @@ public static Iterable> insnList(InsnList insns) { /** * Get a wrapped version of the supplied insn list which returns element - * nodes for every INVOKEDYNAMIC instruction only + * nodes for every LMF instruction only * * @param insns Insn list to wrap * @return Wrapper for insn list */ - public static Iterable> dynamicInsnList(InsnList insns) { + public static Iterable> lmfInsnList(InsnList insns) { return new ElementNodeIterable(insns, true); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java index 20212a7e3..deccceef6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java @@ -113,6 +113,12 @@ public enum Configure { * to set defaults for match limits based on role. */ SELECT_MEMBER(0), + + /** + * Configure this selector for matching lambdas in a method. Usually used + * to set defaults for match limits based on role. + */ + SELECT_LAMBDA(0), /** * Configure this selector for matching field and method instructions in @@ -172,6 +178,22 @@ public void checkArgs(String... args) throws IllegalArgumentException { *

Can return null

*/ public abstract ITargetSelector next(); + + /** + * Minimum relative nesting level at which to search for the {@link next} + * selector. + */ + public default int getMinRecurseDepth() { + return 1; + } + + /** + * Maximum relative nesting level at which to search for the {@link next} + * selector. + */ + public default int getMaxRecurseDepth() { + return 1; + } /** * Configure and return a modified version of this selector by consuming the @@ -205,6 +227,18 @@ public void checkArgs(String... args) throws IllegalArgumentException { */ public abstract ITargetSelector validate() throws InvalidSelectorException; + /** + * Validates the {@link next} selector recursively. + * + * @throws InvalidSelectorException if any checks fail + */ + public default void validateNext() throws InvalidSelectorException { + ITargetSelector next = this.next(); + if (next != null) { + next.validate().validateNext(); + } + } + /** * Attach this selector to the specified context. Should return this * selector unmodified if all is well, or a new selector to be used for diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/MemberMatcher.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/MemberMatcher.java index 2ab8dd4fa..b5daf9e43 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/MemberMatcher.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/MemberMatcher.java @@ -179,7 +179,7 @@ public String toString() { */ @Override public ITargetSelector next() { - return this; // Regex matcher flows into targets + return null; } /* (non-Javadoc) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java index 224570103..00dcdaadb 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java @@ -24,23 +24,19 @@ */ package org.spongepowered.asm.mixin.injection.selectors; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.ListIterator; -import java.util.Set; +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.mixin.FabricUtil; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector.Configure; -import org.spongepowered.asm.mixin.injection.selectors.TargetSelector.Result; import org.spongepowered.asm.mixin.injection.selectors.throwables.SelectorConstraintException; import org.spongepowered.asm.mixin.injection.struct.InvalidMemberDescriptorException; import org.spongepowered.asm.mixin.injection.struct.TargetNotSupportedException; @@ -48,55 +44,35 @@ import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; +import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; +import org.spongepowered.asm.util.NameAndDesc; + +import com.google.common.collect.Lists; public class TargetSelectors implements Iterable { + + private static final ILogger logger = MixinService.getService().getLogger("mixin"); /** - * Selected target method, paired with the selector which identified it + * Selected target method */ public static class SelectedMethod { - - /** - * The parent target of this target. If this target is a lambda then - * this will be the selector for the enclosing method. Parent is null - * for the outermost method. - */ - private final SelectedMethod parent; - - /** - * The target selector which selected this target - */ - private final ITargetSelector selector; - + /** * The selected target method */ private final MethodNode method; - SelectedMethod(SelectedMethod parent, ITargetSelector selector, MethodNode method) { - this.parent = parent; - this.selector = selector; + SelectedMethod(MethodNode method) { this.method = method; } - SelectedMethod(ITargetSelector selector, MethodNode method) { - this(null, selector, method); - } - @Override public String toString() { return this.method.name + this.method.desc; } - - public SelectedMethod getParent() { - return this.parent; - } - - public ITargetSelector next() { - return this.selector.next(); - } public MethodNode getMethod() { return this.method; @@ -130,7 +106,27 @@ public MethodNode getMethod() { * Whether the annotated method is static */ private final boolean isStatic; - + + /** + * An index of methods within the target class + */ + private final Map methodIndex; + + /** + * Whether we have already scraped lambda information + */ + private boolean scrapedLambdas = false; + + /** + * An index of the lambdas contained within each method, populated lazily + */ + private final Map>> containedLambdas = new IdentityHashMap<>(); + + /** + * All the methods which are lambdas, populated lazily + */ + private final Set lambdaMethods = Collections.newSetFromMap(new IdentityHashMap<>()); + /** * Root selectors */ @@ -147,13 +143,16 @@ public TargetSelectors(ISelectorContext context, ClassNode classNode) { this.mixin = context.getMixin(); this.method = context.getMethod(); this.isStatic = this.method instanceof MethodNode && Bytecode.isStatic((MethodNode)this.method); + this.methodIndex = classNode.methods.stream() + .collect(Collectors.toMap(NameAndDesc::new, Function.identity(), (a, b) -> a)); } public void parse(Set selectors) { // Validate and attach the parsed selectors for (ITargetSelector selector : selectors) { try { - this.addSelector(selector.validate().attach(this.context)); + this.validateSelector(selector); + this.addSelector(selector.attach(this.context)); } catch (InvalidMemberDescriptorException ex) { throw new InvalidInjectionException(this.context, String.format("%s, has invalid target descriptor: %s. %s", this.context.getElementDescription(), ex.getMessage(), this.mixin.getReferenceMapper().getStatus())); @@ -167,6 +166,13 @@ public void parse(Set selectors) { } } + private void validateSelector(ITargetSelector selector) { + selector.validate(); + if (FabricUtil.getCompatibility(this.context) >= FabricUtil.COMPATIBILITY_0_17_5) { + selector.validateNext(); + } + } + public TargetSelectors addSelector(ITargetSelector selector) { this.selectors.add(selector); return this; @@ -193,87 +199,141 @@ public void remove(SelectedMethod target) { * Find methods in the target class which match the parsed selectors */ public void find() { - this.findRootTargets(); - // this.findNestedTargets(); + LinkedHashSet selected = new LinkedHashSet<>(); + + for (ITargetSelector selector : this.selectors) { + List roots = this.findRootTargets(selector); + if (roots.isEmpty()) { + continue; + } + if (selector.next() == null || FabricUtil.getCompatibility(this.context) < FabricUtil.COMPATIBILITY_0_17_5) { + // Must ignore nested selectors to match prior behaviour + if (selector.next() != null) { + String advice = MixinService.getService().getAdviceProvider().higherCompatibilityNeeded( + FabricUtil.COMPATIBILITY_0_17_5, + "0.17.5" + ); + TargetSelectors.logger.warn( + "{} specifies a nested target selector which is being ignored at the current " + + "compatibility version. Advice for the author: {}", + this.context, advice + ); + } + selected.addAll(roots); + continue; + } + this.scrapeLambdas(); + + LinkedHashSet> working = roots.stream() + .filter(root -> !this.lambdaMethods.contains(root)) + .map(root -> ElementNode.of(this.targetClassNode, root)) + .collect(Collectors.toCollection(LinkedHashSet::new)); + + while (selector.next() != null) { + int minDepth = selector.getMinRecurseDepth(); + int maxDepth = selector.getMaxRecurseDepth(); + selector = selector.next().configure(Configure.SELECT_LAMBDA); + + LinkedHashSet> children = this.findNested(selector, working, minDepth, maxDepth); + this.checkMinMatches(selector, children.size()); + working = children; + } + + for (ElementNode node : working) { + selected.add(this.findMethod(node)); + } + } + + this.submitTargets(selected); } /** - * Evaluate the root selectors parsed from this injector, find the root - * targets and store them in the {@link #targets} collection. + * Recursively finds nested matches for the given selector in the given root + * methods, depth-first. + * + * @param selector selector to be matched against the nested elements + * @param parents root methods in which to search for lambdas + * @param minDepth minimum relative nesting level to be matched + * @param maxDepth maximum relative nesting level to be matched + * @return matched elements */ - private void findRootTargets() { - for (ITargetSelector selector : this.selectors) { - selector = selector.configure(Configure.SELECT_MEMBER); - - int matchCount = 0; - int maxCount = selector.getMaxMatchCount(); - - for (MethodNode target : this.targetClassNode.methods) { - if (selector.match(ElementNode.of(this.targetClassNode, target)).isExactMatch()) { - matchCount++; - - boolean isMixinMethod = Annotations.getVisible(target, MixinMerged.class) != null; - if (maxCount <= 1 || ((this.isStatic || !Bytecode.isStatic(target)) && target != this.method && !isMixinMethod)) { - this.checkTarget(target); - this.targets.add(new SelectedMethod(selector, target)); - } + private LinkedHashSet> findNested( + ITargetSelector selector, Collection> parents, int minDepth, int maxDepth + ) { + LinkedHashSet> children = new LinkedHashSet<>(); + if (selector.getMaxMatchCount() <= 0) { + return children; + } + + for (ElementNode parent : parents) { + List>> stack = new ArrayList<>(); + stack.add(new WithDepth<>(parent, 0)); - if (matchCount >= maxCount) { - break; + while (!stack.isEmpty()) { + WithDepth> current = stack.remove(stack.size() - 1); + + if (current.depth >= minDepth && selector.match(current.value).isExactMatch()) { + children.add(current.value); + if (children.size() == selector.getMaxMatchCount()) { + return children; } } - } - if (matchCount < selector.getMinMatchCount()) { - throw new InvalidInjectionException(this.context, new SelectorConstraintException(selector, String.format( - "Injection validation failed: %s for %s did not match the required number of targets (required=%d, matched=%d). %s%s", - selector, this.context.getElementDescription(), selector.getMinMatchCount(), matchCount, - this.mixin.getReferenceMapper().getStatus(), AnnotatedMethodInfo.getDynamicInfo(this.method)))); + if (current.depth >= maxDepth) { + // Stop looking + continue; + } + + for (ElementNode lambda : Lists.reverse(this.containedLambdas.get(this.findMethod(current.value)))) { + stack.add(new WithDepth<>(lambda, current.depth + 1)); + } } } + return children; } /** - * For each root target, resolve the nested targets from the target - * descriptor + * Finds the root matches for the given selector in the target class + * (ignores nested selectors). */ - protected void findNestedTargets() { - boolean recursed = false; - do { - recursed = false; - for (ListIterator iter = this.targets.listIterator(); iter.hasNext();) { - SelectedMethod target = iter.next(); - ITargetSelector next = target.next(); - if (next == null) { - continue; + private List findRootTargets(ITargetSelector selector) { + selector = selector.configure(Configure.SELECT_MEMBER); + + List result = new ArrayList<>(); + int maxCount = selector.getMaxMatchCount(); + + for (MethodNode target : this.targetClassNode.methods) { + if (selector.match(ElementNode.of(this.targetClassNode, target)).isExactMatch()) { + boolean isMixinMethod = Annotations.getVisible(target, MixinMerged.class) != null; + if (maxCount <= 1 || ((this.isStatic || !Bytecode.isStatic(target)) && target != this.method && !isMixinMethod)) { + result.add(target); } - - recursed = true; - Result result = TargetSelector.run(next, ElementNode.dynamicInsnList(target.getMethod().instructions)); - iter.remove(); - for (ElementNode candidate : result.candidates) { - if (candidate.getInsn().getOpcode() != Opcodes.INVOKEDYNAMIC) { - continue; - } - - if (!candidate.getOwner().equals(this.mixin.getTargetClassRef())) { - throw new InvalidInjectionException(this.context, String.format( - "%s, failed to select into child. Cannot select foreign method: %s. %s", - this.context.getElementDescription(), candidate, this.mixin.getReferenceMapper().getStatus())); - } - - MethodNode method = this.findMethod(candidate); - if (method == null) { - throw new InvalidInjectionException(this.context, String.format( - "%s, failed to select into child. %s%s was not found in the target class.", - this.context.getElementDescription(), candidate.getName(), candidate.getDesc())); - } - iter.add(new SelectedMethod(target, next, method)); + if (result.size() >= maxCount) { + break; } } } - while (recursed); + + this.checkMinMatches(selector, result.size()); + + return result; + } + + private void checkMinMatches(ITargetSelector selector, int matchCount) { + if (matchCount < selector.getMinMatchCount()) { + throw new InvalidInjectionException(this.context, new SelectorConstraintException(selector, String.format( + "Injection validation failed: %s for %s did not match the required number of targets (required=%d, matched=%d). %s%s", + selector, this.context.getElementDescription(), selector.getMinMatchCount(), matchCount, + this.mixin.getReferenceMapper().getStatus(), AnnotatedMethodInfo.getDynamicInfo(this.method)))); + } + } + + private void submitTargets(Iterable targets) { + for (MethodNode target : targets) { + this.checkTarget(target); + this.targets.add(new SelectedMethod(target)); + } } private void checkTarget(MethodNode target) { @@ -289,17 +349,54 @@ private void checkTarget(MethodNode target) { } /** - * Finds a method in the target class - * - * @return Target method matching searchFor, or null if not found + * If not done already, scrapes lambda information from all methods in the + * target class, populating {@link containedLambdas} and + * {@link lambdaMethods}. */ - private MethodNode findMethod(ElementNode searchFor) { - for (MethodNode target : this.targetClassNode.methods) { - if (target.name.equals(searchFor.getSyntheticName()) && target.desc.equals(searchFor.getDesc())) { - return target; + private void scrapeLambdas() { + if (this.scrapedLambdas) { + return; + } + this.scrapedLambdas = true; + + for (MethodNode parent : this.targetClassNode.methods) { + List> lambdas = this.findContainedLambdas(parent); + this.containedLambdas.put(parent, lambdas); + for (ElementNode lambda : lambdas) { + this.lambdaMethods.add(this.findMethod(lambda)); } } - return null; + } + + private List> findContainedLambdas(MethodNode parent) { + List> result = new ArrayList<>(); + for (ElementNode candidate : ElementNode.lmfInsnList(parent.instructions)) { + if (candidate == null) { + continue; + } + if (!candidate.getImplOwner().equals(this.mixin.getTargetClassRef())) { + // Reference to a foreign method + continue; + } + + MethodNode method = this.findMethod(candidate); + + if (Bytecode.hasFlag(method, Opcodes.ACC_SYNTHETIC) && !Bytecode.hasFlag(method, Opcodes.ACC_BRIDGE)) { + result.add(candidate); + } + } + return result; + } + + private MethodNode findMethod(ElementNode node) { + NameAndDesc candidate = new NameAndDesc(node.getImplName(), node.getImplDesc()); + MethodNode method = this.methodIndex.get(candidate); + if (method == null) { + throw new InvalidInjectionException(this.context, String.format( + "%s failed to find %s in target class %s", + this.context.getElementDescription(), candidate, this.mixin.getTargetClassRef())); + } + return method; } /** @@ -352,4 +449,17 @@ private static String namesOf(Collection selectors) { return sb.toString(); } + /** + * DFS helper. + */ + private static final class WithDepth { + public final T value; + public final int depth; + + public WithDepth(T value, int depth) { + this.value = value; + this.depth = depth; + } + } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java index f0b53d444..4d646eb1e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java @@ -456,6 +456,7 @@ public ITargetSelector configure(Configure request, String... args) { return new DynamicSelectorDesc(this, Quantifier.SINGLE); } break; + case SELECT_LAMBDA: case SELECT_INSTRUCTION: if (this.matches.isDefault()) { return new DynamicSelectorDesc(this, Quantifier.ANY); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java index 70545d4c0..954ee7c0a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java @@ -24,18 +24,16 @@ */ package org.spongepowered.asm.mixin.injection.struct; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.spongepowered.asm.mixin.injection.selectors.ElementNode; -import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorConstructor; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorRemappable; -import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; -import org.spongepowered.asm.mixin.injection.selectors.MatchResult; +import org.spongepowered.asm.mixin.FabricUtil; +import org.spongepowered.asm.mixin.extensibility.IRemapper; +import org.spongepowered.asm.mixin.injection.selectors.*; import org.spongepowered.asm.mixin.throwables.MixinException; import org.spongepowered.asm.obfuscation.mapping.IMapping; import org.spongepowered.asm.obfuscation.mapping.common.MappingField; @@ -216,7 +214,19 @@ * */ public final class MemberInfo implements ITargetSelectorRemappable, ITargetSelectorConstructor { - + + /** + * Shallow parsing pattern to extract nesting + */ + private static final Pattern PARSER = Pattern.compile( + "(?.*?\\S)\\s+->(?\\{.*?}|\\S*)\\s+(?\\S.*)" + ); + + /** + * Default nesting level quantifier + */ + private static final Quantifier DEFAULT_DEPTH = new Quantifier(1, 1); + /** * Separator for elements in the path */ @@ -251,11 +261,18 @@ public final class MemberInfo implements ITargetSelectorRemappable, ITargetSelec * The actual String value passed into the {@link #parse} method */ private final String input; - + /** - * The actual String value passed into the {@link #parse} method + * The nested selector, or {@code null} if this is the last selector in the + * chain. + */ + private final MemberInfo next; + + /** + * The range of relative nesting levels at which to search for the + * {@link next} selector. */ - private final String tail; + private Quantifier nextDepth = MemberInfo.DEFAULT_DEPTH; /** * ctor @@ -264,7 +281,7 @@ public final class MemberInfo implements ITargetSelectorRemappable, ITargetSelec * @param matches Quantifier specifying the number of matches required */ public MemberInfo(String name, Quantifier matches) { - this(name, null, null, matches, null, null); + this(name, null, matches); } /** @@ -276,7 +293,7 @@ public MemberInfo(String name, Quantifier matches) { * @param matches Quantifier specifying the number of matches required */ public MemberInfo(String name, String owner, Quantifier matches) { - this(name, owner, null, matches, null, null); + this(name, owner, null, matches); } /** @@ -288,7 +305,7 @@ public MemberInfo(String name, String owner, Quantifier matches) { * @param desc Member descriptor, can be null */ public MemberInfo(String name, String owner, String desc) { - this(name, owner, desc, Quantifier.DEFAULT, null, null); + this(name, owner, desc, Quantifier.DEFAULT); } /** @@ -301,43 +318,7 @@ public MemberInfo(String name, String owner, String desc) { * @param matches Quantifier specifying the number of matches required */ public MemberInfo(String name, String owner, String desc, Quantifier matches) { - this(name, owner, desc, matches, null, null); - } - - /** - * ctor - * - * @param name Member name, must not be null - * @param owner Member owner, can be null otherwise must be in internal form - * without L; - * @param desc Member descriptor, can be null - * @param matches Quantifier specifying the number of matches required - */ - public MemberInfo(String name, String owner, String desc, Quantifier matches, String tail) { - this(name, owner, desc, matches, tail, null); - } - - /** - * ctor - * - * @param name Member name, must not be null - * @param owner Member owner, can be null otherwise must be in internal form - * without L; - * @param desc Member descriptor, can be null - * @param matches Quantifier specifying the number of matches required - */ - public MemberInfo(String name, String owner, String desc, Quantifier matches, String tail, String input) { - if (owner != null && owner.contains(".")) { - throw new IllegalArgumentException("Attempt to instance a MemberInfo with an invalid owner format"); - } - - this.owner = owner; - this.name = name; - this.desc = desc; - this.matches = matches; - this.forceField = false; - this.tail = tail; - this.input = input; + this(name, owner, desc, matches, null, MemberInfo.DEFAULT_DEPTH, null); } /** @@ -350,8 +331,8 @@ public MemberInfo(AbstractInsnNode insn) { this.matches = Quantifier.DEFAULT; this.forceField = false; this.input = null; - this.tail = null; - + this.next = null; + if (insn instanceof MethodInsnNode) { MethodInsnNode methodNode = (MethodInsnNode) insn; this.owner = methodNode.owner; @@ -378,8 +359,8 @@ public MemberInfo(IMapping mapping) { this.desc = mapping.getDesc(); this.matches = Quantifier.SINGLE; this.forceField = mapping.getType() == IMapping.Type.FIELD; - this.tail = null; this.input = null; + this.next = null; } /** @@ -393,8 +374,9 @@ private MemberInfo(MemberInfo remapped, MappingMethod method, boolean setOwner) this.desc = method.getDesc(); this.matches = remapped.matches; this.forceField = false; - this.tail = null; this.input = null; + this.next = remapped.next; + this.nextDepth = remapped.nextDepth; } /** @@ -409,15 +391,41 @@ private MemberInfo(MemberInfo original, String owner) { this.desc = original.desc; this.matches = original.matches; this.forceField = original.forceField; - this.tail = original.tail; this.input = null; + this.next = original.next; + this.nextDepth = original.nextDepth; } - + + private MemberInfo(String name, String owner, String desc, Quantifier matches, MemberInfo next, Quantifier nextDepth, String input) { + this.owner = owner; + this.name = name; + this.desc = desc; + this.matches = matches; + this.forceField = false; + this.input = input; + this.next = next; + this.nextDepth = nextDepth; + } + + private MemberInfo(MemberInfo original, Quantifier newMatches) { + this(original.name, original.owner, original.desc, newMatches, original.next, original.nextDepth, null); + } + @Override public ITargetSelector next() { - return Strings.isNullOrEmpty(this.tail) ? null : MemberInfo.parse(this.tail, null); + return this.next; } - + + @Override + public int getMinRecurseDepth() { + return this.nextDepth.getClampedMin(); + } + + @Override + public int getMaxRecurseDepth() { + return this.nextDepth.getClampedMax(); + } + @Override public String getOwner() { return this.owner; @@ -448,13 +456,33 @@ public int getMaxMatchCount() { */ @Override public String toString() { + String tail = ""; + if (this.next != null) { + tail = ' ' + MemberInfo.ARROW + this.recurseDepthToString() + ' ' + this.next; + } + return this.headToString() + tail; + } + + /** + * Renders the root selector as a string, ignoring nested selectors. + */ + public String headToString() { String owner = this.owner != null ? "L" + this.owner + ";" : ""; String name = this.name != null ? this.name : ""; String quantifier = this.matches.toString(); String desc = this.desc != null ? this.desc : ""; String separator = desc.startsWith("(") ? "" : (this.desc != null ? ":" : ""); - String tail = this.tail != null ? " " + MemberInfo.ARROW + " " + this.tail : ""; - return owner + name + quantifier + separator + desc + tail; + return owner + name + quantifier + separator + desc; + } + + /** + * Renders the nesting level quantifier as a canonical string. + */ + public String recurseDepthToString() { + if (this.getMinRecurseDepth() == 1 && this.getMaxRecurseDepth() == 1) { + return ""; + } + return this.nextDepth.toString(); } /** @@ -633,8 +661,7 @@ public boolean isInitialiser() { */ @Override public MemberInfo validate() throws InvalidSelectorException { - // Parse emits a match count of 0 if the quantifier is incorrectly specified - if (this.getMaxMatchCount() == 0) { + if (this.matches.isInvalid() || this.matches.getClampedMax() == 0 || this.nextDepth.isInvalid()) { throw new InvalidMemberDescriptorException(this.input, "Malformed quantifier in selector: " + this.input); } @@ -736,43 +763,6 @@ public MatchResult matches(String owner, String name, String desc) { } return MatchResult.EXACT_MATCH; } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (obj == null || !(obj instanceof ITargetSelectorByName)) { - return false; - } - - ITargetSelectorByName other = (ITargetSelectorByName)obj; - boolean otherForceField = other instanceof MemberInfo ? ((MemberInfo)other).forceField - : other instanceof ITargetSelectorRemappable ? ((ITargetSelectorRemappable)other).isField() : false; - - return this.compareMatches(other) && this.forceField == otherForceField - && Objects.equal(this.owner, other.getOwner()) - && Objects.equal(this.name, other.getName()) - && Objects.equal(this.desc, other.getDesc()); - } - - /** - * Compare local match count with match count of other selector - */ - private boolean compareMatches(ITargetSelectorByName other) { - if (other instanceof MemberInfo) { - return ((MemberInfo)other).matches.equals(this.matches); - } - return this.getMinMatchCount() == other.getMinMatchCount() && this.getMaxMatchCount() == other.getMaxMatchCount(); - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return Objects.hashCode(this.matches, this.owner, this.name, this.desc); - } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.selectors.ITargetSelector @@ -784,12 +774,13 @@ public ITargetSelector configure(Configure request, String... args) { switch (request) { case SELECT_MEMBER: if (this.matches.isDefault()) { - return new MemberInfo(this.name, this.owner, this.desc, Quantifier.SINGLE, this.tail); + return new MemberInfo(this, Quantifier.SINGLE); } break; + case SELECT_LAMBDA: case SELECT_INSTRUCTION: if (this.matches.isDefault()) { - return new MemberInfo(this.name, this.owner, this.desc, Quantifier.ANY, this.tail); + return new MemberInfo(this, Quantifier.ANY); } break; case MOVE: @@ -802,7 +793,7 @@ public ITargetSelector configure(Configure request, String... args) { return this.transform(null); case CLEAR_LIMITS: if (this.matches.getMin() != 0 || this.matches.getMax() < Integer.MAX_VALUE) { - return new MemberInfo(this.name, this.owner, this.desc, Quantifier.ANY, this.tail); + return new MemberInfo(this, Quantifier.ANY); } break; default: @@ -860,7 +851,95 @@ public ITargetSelectorRemappable transform(String newDesc) { public ITargetSelectorRemappable remapUsing(MappingMethod srgMethod, boolean setOwner) { return new MemberInfo(this, srgMethod, setOwner); } - + + /** + * Remaps this selector using the given remapper, assuming that this is a + * top-level selector. + * @param remapper the remapper to use + * @return the remapped selector, or {@code null} if no change. + */ + public MemberInfo remapUsing(IRemapper remapper) { + boolean changed = false; + + String name = this.name; + if (name != null) { + if (this.isField()) { + name = remapper.mapFieldName(this.owner, name, this.desc); + } else { + name = remapper.mapMethodName(this.owner, name, this.desc); + } + changed = changed || !this.name.equals(name); + } + + String owner = this.owner; + if (owner != null) { + owner = remapper.map(owner); + changed = changed || !this.owner.equals(owner); + } + + String desc = this.desc; + if (desc != null) { + desc = remapper.mapDesc(desc); + changed = changed || !this.desc.equals(desc); + } + + MemberInfo next = this.next; + if (next != null) { + MemberInfo remappedNext = next.remapNestedUsing(remapper); + if (remappedNext != null) { + changed = true; + next = remappedNext; + } + } + + if (!changed) { + return null; + } + + return new MemberInfo(name, owner, desc, this.matches, next, this.nextDepth, null); + } + + /** + * Remaps this selector using the given remapper, assuming that this is a + * nested selector. + * @param remapper the remapper to use + * @return the remapped selector, or {@code null} if no change. + */ + public MemberInfo remapNestedUsing(IRemapper remapper) { + boolean changed = false; + + String desc = this.desc; + if (desc != null) { + desc = remapper.mapDesc(desc); + changed = changed || !this.desc.equals(desc); + } + + String owner = this.owner; + String name = null; + if (owner != null) { + // The owner is enough to uniquely identify the SAM, strip the name + owner = remapper.map(owner); + changed = changed || this.name != null || !this.owner.equals(owner); + } else { + name = this.name; + } + + MemberInfo next = this.next; + if (next != null) { + MemberInfo remappedNext = next.remapNestedUsing(remapper); + if (remappedNext != null) { + changed = true; + next = remappedNext; + } + } + + if (!changed) { + return null; + } + + return new MemberInfo(name, owner, desc, this.matches, next, this.nextDepth, null); + } + /** * Parse a MemberInfo from a string * @@ -869,39 +948,59 @@ public ITargetSelectorRemappable remapUsing(MappingMethod srgMethod, boolean set * @return parsed MemberInfo */ public static MemberInfo parse(final String input, final ISelectorContext context) { + boolean isModern = context == null || FabricUtil.getCompatibility(context) >= FabricUtil.COMPATIBILITY_0_17_5; + String desc = null; String owner = null; - String name = Strings.nullToEmpty(input).replaceAll("\\s", ""); - String tail = null; - - int arrowPos = name.indexOf(MemberInfo.ARROW); - if (arrowPos > -1) { - tail = name.substring(arrowPos + 2); - name = name.substring(0, arrowPos); - } + String name = Strings.nullToEmpty(input); + MemberInfo next = null; + Quantifier nextDepth = MemberInfo.DEFAULT_DEPTH; + + if (isModern) { + if (context != null) { + name = context.remap(name); + } + name = name.trim(); + + Matcher nestedMatcher = MemberInfo.PARSER.matcher(name); + if (nestedMatcher.matches()) { + name = nestedMatcher.group("root").trim(); + Quantifier parsedDepth = Quantifier.parse(nestedMatcher.group("nextDepth")); + if (!parsedDepth.isDefault()) { + nextDepth = parsedDepth; + } + next = MemberInfo.parse(nestedMatcher.group("next"), null); + } + } else { + name = name.replaceAll("\\s", ""); + + int arrowPos = name.indexOf(MemberInfo.ARROW); + if (arrowPos > -1) { + next = MemberInfo.parse(name.substring(arrowPos + 2), context); + name = name.substring(0, arrowPos); + } - if (context != null) { name = context.remap(name); } int parenPos = name.indexOf('('); int colonPos = name.indexOf(':'); if (parenPos > -1) { - desc = name.substring(parenPos); - name = name.substring(0, parenPos); + desc = name.substring(parenPos).trim(); + name = name.substring(0, parenPos).trim(); } else if (colonPos > -1) { - desc = name.substring(colonPos + 1); - name = name.substring(0, colonPos); + desc = name.substring(colonPos + 1).trim(); + name = name.substring(0, colonPos).trim(); } int lastDotPos = name.lastIndexOf('.'); int semiColonPos = name.indexOf(';'); if (lastDotPos > -1) { - owner = name.substring(0, lastDotPos).replace('.', '/'); - name = name.substring(lastDotPos + 1); + owner = name.substring(0, lastDotPos).replace('.', '/').trim(); + name = name.substring(lastDotPos + 1).trim(); } else if (semiColonPos > -1 && name.startsWith("L")) { - owner = name.substring(1, semiColonPos).replace('.', '/'); - name = name.substring(semiColonPos + 1); + owner = name.substring(1, semiColonPos).replace('.', '/').trim(); + name = name.substring(semiColonPos + 1).trim(); } if ((name.indexOf('/') > -1 || name.indexOf('.') > -1) && owner == null) { @@ -919,22 +1018,22 @@ public static MemberInfo parse(final String input, final ISelectorContext contex Quantifier quantifier = Quantifier.DEFAULT; if (name.endsWith("*")) { quantifier = Quantifier.ANY; - name = name.substring(0, name.length() - 1); + name = name.substring(0, name.length() - 1).trim(); } else if (name.endsWith("+")) { quantifier = Quantifier.PLUS; - name = name.substring(0, name.length() - 1); + name = name.substring(0, name.length() - 1).trim(); } else if (name.endsWith("}")) { quantifier = Quantifier.NONE; // Assume invalid until quantifier is parsed int bracePos = name.indexOf("{"); if (bracePos >= 0) { try { quantifier = Quantifier.parse(name.substring(bracePos, name.length())); - name = name.substring(0, bracePos); + name = name.substring(0, bracePos).trim(); } catch (Exception ex) { // Handled later in validate since matchCount will be 0 } } - } else if (name.indexOf("{") >= 0) { + } else if (name.contains("{")) { quantifier = Quantifier.NONE; // Probably incomplete quantifier } @@ -942,7 +1041,7 @@ public static MemberInfo parse(final String input, final ISelectorContext contex name = null; } - return new MemberInfo(name, owner, desc, quantifier, tail, input); + return new MemberInfo(name, owner, desc, quantifier, next, nextDepth, input); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java b/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java index 0ab2e95e5..9c1ba547b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java @@ -92,4 +92,9 @@ public interface IMixinContext { */ public abstract int getPriority(); + /** + * Gets whether this is a compile-time context. + */ + public abstract boolean isCompileTime(); + } diff --git a/src/main/java/org/spongepowered/asm/mixin/refmap/ReferenceMapper.java b/src/main/java/org/spongepowered/asm/mixin/refmap/ReferenceMapper.java index d32789bb0..1a1182710 100644 --- a/src/main/java/org/spongepowered/asm/mixin/refmap/ReferenceMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/refmap/ReferenceMapper.java @@ -220,7 +220,6 @@ public String addMapping(String context, String className, String reference, Str if (this.readOnly || reference == null || newReference == null) { return null; } - String conformedReference = reference.replaceAll("\\s", ""); Map> mappings = this.mappings; if (context != null) { mappings = this.data.get(context); @@ -234,7 +233,7 @@ public String addMapping(String context, String className, String reference, Str classMappings = new TreeMap(); mappings.put(className, classMappings); } - return classMappings.put(conformedReference, newReference); + return classMappings.put(reference, newReference); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/refmap/RemappingReferenceMapper.java b/src/main/java/org/spongepowered/asm/mixin/refmap/RemappingReferenceMapper.java index 309227786..d9abbeb07 100644 --- a/src/main/java/org/spongepowered/asm/mixin/refmap/RemappingReferenceMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/refmap/RemappingReferenceMapper.java @@ -148,26 +148,8 @@ public String remapWithContext(String context, String className, String referenc if (remappedCached != null) { return remappedCached; } else { - String remapped = origInfoString; - - // To handle propagation, find super/itf-class (for IRemapper) - // but pass the requested class in the MemberInfo - MemberInfo info = MemberInfo.parse(remapped, null); - if (info.getName() == null && info.getDesc() == null) { - return info.getOwner() != null ? new MemberInfo(remapper.map(info.getOwner()), Quantifier.DEFAULT).toString() : info.toString(); - } else if (info.isField()) { - remapped = new MemberInfo( - remapper.mapFieldName(info.getOwner(), info.getName(), info.getDesc()), - info.getOwner() == null ? null : remapper.map(info.getOwner()), - info.getDesc() == null ? null : remapper.mapDesc(info.getDesc()) - ).toString(); - } else { - remapped = new MemberInfo( - remapper.mapMethodName(info.getOwner(), info.getName(), info.getDesc()), - info.getOwner() == null ? null : remapper.map(info.getOwner()), - info.getDesc() == null ? null : remapMethodDescriptor(remapper, info.getDesc()) - ).toString(); - } + MemberInfo info = MemberInfo.parse(origInfoString, null).remapUsing(this.remapper); + String remapped = info != null ? info.toString() : origInfoString; mappedReferenceCache.put(origInfoString, remapped); return remapped; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 4ca8bcab7..7fab4cd31 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -1203,6 +1203,11 @@ public int getPriority() { return this.mixin.getPriority(); } + @Override + public boolean isCompileTime() { + return false; + } + /** * Get all interfaces for this mixin * diff --git a/src/main/java/org/spongepowered/asm/util/Handles.java b/src/main/java/org/spongepowered/asm/util/Handles.java index 55709f2b0..7aabae4e7 100644 --- a/src/main/java/org/spongepowered/asm/util/Handles.java +++ b/src/main/java/org/spongepowered/asm/util/Handles.java @@ -24,6 +24,11 @@ */ package org.spongepowered.asm.util; +import java.lang.invoke.CallSite; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + import org.objectweb.asm.Handle; import org.objectweb.asm.Opcodes; @@ -31,6 +36,22 @@ * Utility class for working with method and field handles */ public final class Handles { + + public static final Handle LMF_HANDLE = new Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + "metafactory", + Bytecode.generateDescriptor(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, MethodType.class), + false + ); + + public static final Handle ALT_LMF_HANDLE = new Handle( + Opcodes.H_INVOKESTATIC, + "java/lang/invoke/LambdaMetafactory", + "altMetafactory", + Bytecode.generateDescriptor(CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class), + false + ); private static final int[] H_OPCODES = { 0, // invalid diff --git a/src/main/java/org/spongepowered/asm/util/NameAndDesc.java b/src/main/java/org/spongepowered/asm/util/NameAndDesc.java new file mode 100644 index 000000000..dd8f93954 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/util/NameAndDesc.java @@ -0,0 +1,74 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * 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.asm.util; + +import org.objectweb.asm.tree.MethodNode; + +import java.util.Objects; + +/** + * Represents a method or field, intended for use as a map key + */ +public final class NameAndDesc { + + /** + * The name of the element + */ + public final String name; + + /** + * The bytecode descriptor of the element + */ + public final String desc; + + public NameAndDesc(String name, String desc) { + this.name = name; + this.desc = desc; + } + + public NameAndDesc(MethodNode method) { + this(method.name, method.desc); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof NameAndDesc)) { + return false; + } + NameAndDesc that = (NameAndDesc) o; + return Objects.equals(name, that.name) && Objects.equals(desc, that.desc); + } + + @Override + public int hashCode() { + return name.hashCode() * 31 + desc.hashCode(); + } + + @Override + public String toString() { + return name + ' ' + desc; + } +} diff --git a/src/main/java/org/spongepowered/asm/util/Quantifier.java b/src/main/java/org/spongepowered/asm/util/Quantifier.java index 12141ec75..fe7778ec5 100644 --- a/src/main/java/org/spongepowered/asm/util/Quantifier.java +++ b/src/main/java/org/spongepowered/asm/util/Quantifier.java @@ -38,7 +38,7 @@ public final class Quantifier { /** * Invalid (matches none) */ - public static Quantifier NONE = new Quantifier(0, 0); + public static Quantifier NONE = new Quantifier(0, -2); /** * Single (matches zero or 1) @@ -74,7 +74,11 @@ public Quantifier(int min, int max) { * Check whether this is a defaulted qualifier */ public boolean isDefault() { - return this.min == 0 && this.max < 0; + return this.min == 0 && this.max == -1; + } + + public boolean isInvalid() { + return this.max == -2; } /** @@ -102,7 +106,7 @@ public int getClampedMin() { * Get the clamped max value */ public int getClampedMax() { - return this.max < 0 ? 1 : Math.max(this.min, this.max); + return this.max == -1 ? 1 : Math.max(this.min, this.max); } /* (non-Javadoc) @@ -116,12 +120,8 @@ public String toString() { } else if (this.max < this.min) { return ""; } else { - if (this.min == 0) { - if (this.max == 1) { - return ""; - } else if (this.max == Integer.MAX_VALUE) { - return "*"; - } + if (this.min == 0 && this.max == Integer.MAX_VALUE) { + return "*"; } if (this.min == 1 && this.max == Integer.MAX_VALUE) { return "+"; @@ -184,31 +184,5 @@ public static Quantifier parse(String string) { return Quantifier.NONE; } } - - /* (non-Javadoc) - * @see java.lang.Object#equals(java.lang.Object) - */ - @Override - public boolean equals(Object obj) { - if (obj instanceof Quantifier) { - Quantifier other = (Quantifier)obj; - return other.min == this.min && other.max == this.max; - } - - if (obj instanceof Number) { - int intValue = ((Number)obj).intValue(); - return (intValue == this.min) && (intValue == this.max); - } - - return false; - } - - /* (non-Javadoc) - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - return 31 * this.min * this.max; - } }