Skip to content

Commit

Permalink
Fix: Overhaul MixinVerifier supertype logic.
Browse files Browse the repository at this point in the history
It previously didn't handle array types correctly among some other issues.
  • Loading branch information
LlamaLad7 authored and modmuss50 committed Jun 2, 2024
1 parent 35c079a commit c093311
Showing 1 changed file with 127 additions and 49 deletions.
176 changes: 127 additions & 49 deletions src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type> currentClassInterfaces;
private boolean isInterface;
private static final Type OBJECT_TYPE = Type.getType(Object.class);

public MixinVerifier(int api, Type currentClass, Type currentSuperClass, List<Type> 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()
)
);
}
}

0 comments on commit c093311

Please sign in to comment.