From c0933111843364e97b8ffbc35935518d287fe1e9 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 2 Jun 2024 12:58:25 +0100 Subject: [PATCH] Fix: Overhaul MixinVerifier supertype logic. It previously didn't handle array types correctly among some other issues. --- .../asm/util/asm/MixinVerifier.java | 176 +++++++++++++----- 1 file changed, 127 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java b/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java index 8519887b7..cdbd02ef4 100644 --- a/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java +++ b/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java @@ -24,83 +24,161 @@ */ package org.spongepowered.asm.util.asm; -import java.util.List; - import org.objectweb.asm.Type; +import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.SimpleVerifier; import org.spongepowered.asm.mixin.transformer.ClassInfo; -import org.spongepowered.asm.mixin.transformer.ClassInfo.TypeLookup; + +import java.util.List; /** * Verifier which handles class info lookups via {@link ClassInfo} */ public class MixinVerifier extends SimpleVerifier { - - private Type currentClass; - private Type currentSuperClass; - private List currentClassInterfaces; - private boolean isInterface; + private static final Type OBJECT_TYPE = Type.getType(Object.class); public MixinVerifier(int api, Type currentClass, Type currentSuperClass, List currentClassInterfaces, boolean isInterface) { super(api, currentClass, currentSuperClass, currentClassInterfaces, isInterface); - this.currentClass = currentClass; - this.currentSuperClass = currentSuperClass; - this.currentClassInterfaces = currentClassInterfaces; - this.isInterface = isInterface; } @Override - protected boolean isInterface(final Type type) { - if (this.currentClass != null && type.equals(this.currentClass)) { - return this.isInterface; + protected boolean isInterface(Type type) { + if (type.getSort() != Type.OBJECT) { + return false; } - return ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE).isInterface(); + return ClassInfo.forType(type, ClassInfo.TypeLookup.DECLARED_TYPE).isInterface(); } @Override - protected Type getSuperClass(final Type type) { - if (this.currentClass != null && type.equals(this.currentClass)) { - return this.currentSuperClass; + protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { + Type expectedType = expected.getType(); + Type type = value.getType(); + switch (expectedType.getSort()) { + case Type.INT: + case Type.FLOAT: + case Type.LONG: + case Type.DOUBLE: + return type.equals(expectedType); + case Type.ARRAY: + case Type.OBJECT: + if (type.equals(NULL_TYPE)) { + return true; + } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + if (isAssignableFrom(expectedType, type)) { + return true; + } + if (expectedType.getSort() == Type.ARRAY) { + if (type.getSort() != Type.ARRAY) { + return false; + } + int dim = expectedType.getDimensions(); + expectedType = expectedType.getElementType(); + if (dim > type.getDimensions() || expectedType.getSort() != Type.OBJECT) { + return false; + } + type = Type.getType(type.getDescriptor().substring(dim)); + } + if (isInterface(expectedType)) { + // The merge of class or interface types can only yield class types (because it is not + // possible in general to find an unambiguous common super interface, due to multiple + // inheritance). Because of this limitation, we need to relax the subtyping check here + // if 'value' is an interface. + return type.getSort() >= Type.ARRAY; + } else { + return false; + } + } else { + return false; + } + default: + throw new AssertionError(); } - ClassInfo c = ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE).getSuperClass(); - return c == null ? null : Type.getType("L" + c.getName() + ";"); } @Override - protected boolean isAssignableFrom(final Type type, final Type other) { - if (type.equals(other)) { - return true; + protected boolean isAssignableFrom(Type type1, Type type2) { + return type1.equals(getCommonSupertype(type1, type2)); + } + + @Override + public BasicValue merge(BasicValue value1, BasicValue value2) { + if (value1.equals(value2)) { + return value1; } - if (this.currentClass != null && type.equals(this.currentClass)) { - if (this.getSuperClass(other) == null) { - return false; - } - if (this.isInterface) { - return other.getSort() == Type.OBJECT || other.getSort() == Type.ARRAY; - } - return this.isAssignableFrom(type, this.getSuperClass(other)); + if (value1.equals(BasicValue.UNINITIALIZED_VALUE) || value2.equals(BasicValue.UNINITIALIZED_VALUE)) { + return BasicValue.UNINITIALIZED_VALUE; } - if (this.currentClass != null && other.equals(this.currentClass)) { - if (this.isAssignableFrom(type, this.currentSuperClass)) { - return true; - } - if (this.currentClassInterfaces != null) { - for (int i = 0; i < this.currentClassInterfaces.size(); ++i) { - Type v = this.currentClassInterfaces.get(i); - if (this.isAssignableFrom(type, v)) { - return true; - } + Type supertype = getCommonSupertype(value1.getType(), value2.getType()); + return newValue(supertype); + } + + private static Type getCommonSupertype(Type type1, Type type2) { + if (type1.equals(type2) || type2.equals(NULL_TYPE)) { + return type1; + } + if (type1.equals(NULL_TYPE)) { + return type2; + } + if (type1.getSort() < Type.ARRAY || type2.getSort() < Type.ARRAY) { + // We know they're not the same, so they must be incompatible. + return null; + } + if (type1.getSort() == Type.ARRAY && type2.getSort() == Type.ARRAY) { + int dim1 = type1.getDimensions(); + Type elem1 = type1.getElementType(); + int dim2 = type2.getDimensions(); + Type elem2 = type2.getElementType(); + if (dim1 == dim2) { + Type commonSupertype; + if (elem1.equals(elem2)) { + commonSupertype = elem1; + } else if (elem1.getSort() == Type.OBJECT && elem2.getSort() == Type.OBJECT) { + commonSupertype = getCommonSupertype(elem1, elem2); + } else { + return arrayType(OBJECT_TYPE, dim1 - 1); } + return arrayType(commonSupertype, dim1); } - return false; + Type smaller; + int shared; + if (dim1 < dim2) { + smaller = elem1; + shared = dim1 - 1; + } else { + smaller = elem2; + shared = dim2 - 1; + } + if (smaller.getSort() == Type.OBJECT) { + shared++; + } + return arrayType(OBJECT_TYPE, shared); } - ClassInfo typeInfo = ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE); - if (typeInfo == null) { - return false; + if (type1.getSort() == Type.ARRAY && type2.getSort() == Type.OBJECT || type2.getSort() == Type.ARRAY && type1.getSort() == Type.OBJECT) { + return OBJECT_TYPE; } - if (typeInfo.isInterface()) { - typeInfo = ClassInfo.forName("java/lang/Object"); + return ClassInfo.getCommonSuperClass(type1, type2).getType(); + } + + private static Type arrayType(final Type type, final int dimensions) { + if (dimensions == 0) { + return type; + } else { + StringBuilder descriptor = new StringBuilder(); + for (int i = 0; i < dimensions; ++i) { + descriptor.append('['); + } + descriptor.append(type.getDescriptor()); + return Type.getType(descriptor.toString()); } - return ClassInfo.forType(other, TypeLookup.ELEMENT_TYPE).hasSuperClass(typeInfo); + } + + @Override + protected Class getClass(Type type) { + throw new UnsupportedOperationException( + String.format( + "Live-loading of %s attempted by MixinVerifier! This should never happen!", + type.getClassName() + ) + ); } }