Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import org.objectweb.asm.tree.ClassNode;
import org.sinytra.adapter.util.provider.ClassLookup;

import java.util.concurrent.ConcurrentHashMap;
import java.util.*;

public class InheritanceHandler {
private final ClassLookup classProvider;
private final Map<String, Collection<String>> parentCache = new HashMap<>();
// ConcurrentHashMap: one handler instance is shared by every mixin using the same class lookup, and the
// transformation is driven from a ForkJoinPool.
private final Map<String, Collection<String>> parentCache = new ConcurrentHashMap<>();

public InheritanceHandler(ClassLookup classProvider) {
this.classProvider = classProvider;
Expand All @@ -24,11 +27,13 @@ public boolean isClassInherited(String child, String parent) {

public Collection<String> getClassParents(String name) {
Collection<String> parents = this.parentCache.get(name);
if (parents == null) {
parents = computeClassParents(name);
this.parentCache.put(name, parents);
if (parents != null) {
return parents;
}
return parents;
// Not computeIfAbsent: computeClassParents recurses into getClassParents, which it rejects.
Collection<String> computed = computeClassParents(name);
Collection<String> prev = this.parentCache.putIfAbsent(name, computed);
return prev != null ? prev : computed;
}

private Collection<String> computeClassParents(String name) {
Expand All @@ -51,3 +56,4 @@ private Collection<String> computeClassParents(String name) {
return parents;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.objectweb.asm.tree.*;
import org.sinytra.adapter.analysis.params.EnhancedParamsDiff;
import org.sinytra.adapter.analysis.params.ParamsDiffSnapshot;
import org.sinytra.adapter.env.ann.AtData;
import org.sinytra.adapter.env.ctx.LocalVariable;
import org.sinytra.adapter.env.ctx.MixinContext;
import org.sinytra.adapter.env.ctx.TargetPair;
Expand All @@ -26,11 +27,20 @@ public record CapturedLocalsInfo(AdapterUtil.CapturedLocals capturedLocals, Para

@Nullable
public static CapturedLocalsInfo getCapturedLocals(MixinContext context, TargetPair dirtyTarget) {
return getCapturedLocals(context, dirtyTarget, null);
}

/**
* @param atData the effective {@code @At} data, or {@code null} to fall back to the annotation as written
* (adaptation may have moved the injection site)
*/
@Nullable
public static CapturedLocalsInfo getCapturedLocals(MixinContext context, TargetPair dirtyTarget, @Nullable AtData atData) {
AdapterUtil.CapturedLocals capturedLocals = AdapterUtil.getCapturedLocals(context, dirtyTarget);
if (capturedLocals == null) return null;

// Get available local variables at the injection point in the target method
List<LocalVariable> available = context.methods().getTargetMethodLocals(capturedLocals.target());
List<LocalVariable> available = context.methods().getTargetMethodLocals(capturedLocals.target(), atData);
if (available == null) return null;

List<Type> availableTypes = available.stream().map(LocalVariable::type).toList();
Expand Down
10 changes: 6 additions & 4 deletions core/src/main/java/org/sinytra/adapter/env/MockMixinRuntime.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ public static IMixinContext forClass(String className, String targetClass, Patch
return new ClassMixinContext(className, targetClass, environment);
}

public static ISliceContext forSlice(IMixinContext context, MethodNode methodNode) {
return new MethodSliceContext(context, methodNode);
public static ISliceContext forSlice(IMixinContext context, MethodNode methodNode, AnnotationNode annotation) {
return new MethodSliceContext(context, methodNode, annotation);
}

public static InjectionInfo forInjectionInfo(String className, String targetClass, PatchEnvironment environment) {
Expand Down Expand Up @@ -102,7 +102,7 @@ public static InjectionInfo createModifyVariableInjectionInfo(IMixinContext mixi
return info;
}

private record MethodSliceContext(IMixinContext context, MethodNode methodNode) implements ISliceContext {
private record MethodSliceContext(IMixinContext context, MethodNode methodNode, AnnotationNode annotation) implements ISliceContext {
@Override
public IMixinContext getMixin() {
return this.context;
Expand All @@ -117,7 +117,9 @@ public String remap(String reference) {
@Override public String getElementDescription() {return "dummy";}
@Override public MethodSlice getSlice(String id) {throw new UnsupportedOperationException();}
@Override public MethodNode getMethod() {return this.methodNode;}
@Override public AnnotationNode getAnnotationNode() {throw new UnsupportedOperationException();}
// NOTE: the remaining stubs below deliberately still throw. A loud failure is preferable to a
// silent wrong answer: if Mixin reaches another of these entry points we want to see it.
@Override public AnnotationNode getAnnotationNode() {return this.annotation;}
@Override public ISelectorContext getParent() {throw new UnsupportedOperationException();}
@Override public IAnnotationHandle getAnnotation() {throw new UnsupportedOperationException();}
@Override public IAnnotationHandle getSelectorAnnotation() {throw new UnsupportedOperationException();}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public static ClassTarget parse(AnnotationHandle handle, RefMapper mapper) {
.or(() -> handle.<List<String>>getValue(MIXIN_TARGETS).map(v -> {
List<Type> types = v.get().stream()
.map(mapper::remap)
// "targets" holds dotted binary names while Type.getObjectType expects an internal name;
// only '.' becomes '/', the '$' of an inner class stays.
.map(name -> name.replace('.', '/'))
.map(Type::getObjectType)
.toList();
return new ClassTarget(types, Either.right(v));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

public class ConstantData {
private static final PropertyContainerTemplate TEMPLATE = PropertyContainerTemplate.builder()
.requireOne(Keys.DOUBLE_VALUE, Keys.CLASS_VALUE)
.requireOne(Keys.DOUBLE_VALUE, Keys.CLASS_VALUE, Keys.INT_VALUE, Keys.FLOAT_VALUE, Keys.LONG_VALUE, Keys.BOOLEAN_VALUE)
.build();

private final PropertyContainer properties;
Expand Down Expand Up @@ -49,5 +49,11 @@ public static Optional<ConstantData> parse(AnnotationHandle handle) {
public static class Keys {
public static final PropertyKey<Double> DOUBLE_VALUE = PropertyKey.create("doubleValue", Double.class);
public static final PropertyKey<Type> CLASS_VALUE = PropertyKey.create("classValue", Type.class);
public static final PropertyKey<Integer> INT_VALUE = PropertyKey.create("intValue", Integer.class);
public static final PropertyKey<Float> FLOAT_VALUE = PropertyKey.create("floatValue", Float.class);
public static final PropertyKey<Long> LONG_VALUE = PropertyKey.create("longValue", Long.class);
public static final PropertyKey<Boolean> BOOLEAN_VALUE = PropertyKey.create("booleanValue", Boolean.class);
}
}


28 changes: 27 additions & 1 deletion core/src/main/java/org/sinytra/adapter/env/ctx/MethodFinder.java
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,31 @@ public Pair<ClassNode, List<MethodNode>> findMethods(ClassLookup lookup, MethodQ
}

if (candidates.isEmpty() && targetClass.superName != null) {
return findMethods(lookup, new MethodQualifier(Type.getObjectType(targetClass.superName).getDescriptor(), qualifier.name(), qualifier.desc()), flags);
// Returns unconditionally: falling through would make the interface traversal below reachable,
// and interface declarations returned as owners break startup.
return findMethods(lookup,
new MethodQualifier(Type.getObjectType(targetClass.superName).getDescriptor(), qualifier.name(), qualifier.desc()), flags);
}

if (candidates.isEmpty()) {
// superName reaches superclasses only; a target can equally be a default method on an interface,
// which is how NeoForge attaches helpers (BlockState#canStickTo is declared on
// IBlockStateExtension). Declines when more than one interface matches.
Pair<ClassNode, List<MethodNode>> viaInterface = null;
for (String itf : targetClass.interfaces) {
Pair<ClassNode, List<MethodNode>> found = findMethods(lookup,
new MethodQualifier(Type.getObjectType(itf).getDescriptor(), qualifier.name(), qualifier.desc()), flags);
if (found == null || found.getSecond().isEmpty()) {
continue;
}
if (viaInterface != null) {
return Pair.of(targetClass, List.of());
}
viaInterface = found;
}
if (viaInterface != null) {
return viaInterface;
}
}

return Pair.of(targetClass, candidates);
Expand All @@ -109,3 +133,5 @@ private static boolean hasFlag(int flags, int flag) {
return (flags & flag) != 0;
}
}


39 changes: 37 additions & 2 deletions core/src/main/java/org/sinytra/adapter/env/ctx/MethodHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ private InsnList getSlicedInsns(AnnotationHandle parentAnnotation, ClassNode cla
})
.map(sliceAnn -> {
IMixinContext mixinContext = MockMixinRuntime.forClass(classNode.name, targetClass.name, context.environment());
ISliceContext sliceContext = MockMixinRuntime.forSlice(mixinContext, injectorMethod);
ISliceContext sliceContext = MockMixinRuntime.forSlice(mixinContext, injectorMethod, sliceAnn);
return computeSlicedInsns(sliceContext, sliceAnn, mixinTarget);
})
.orElse(targetMethod.instructions);
Expand Down Expand Up @@ -244,7 +244,42 @@ public List<LocalVariable> getTargetMethodLocals(TargetPair target, int startPos

@Nullable
public List<LocalVariable> getTargetMethodLocals(TargetPair target, int startPos, int lvtCompatLevel) {
List<AbstractInsnNode> targetInsns = findInjectionTargetInsns(target);
return getTargetMethodLocals(target, findInjectionTargetInsns(target), startPos, lvtCompatLevel);
}

/** Like {@link #findInjectionTargetInsns(TargetPair)}, but from the given {@code @At} data. */
public List<AbstractInsnNode> findInjectionTargetInsns(@Nullable TargetPair target, @Nullable AtData atData) {
if (target == null) return List.of();
if (atData == null) return findInjectionTargetInsns(target);

return computeInjectionTargetInsns(
target,
this.context::injectionPointAnnotation,
(ctx, h) -> {
AnnotationNode atCopy = atData.toAnnotationNode();
AnnotationHandle annCopy = this.context.methodAnnotation().copy();
annCopy.setOrAppendNonNull(PROPERTY_AT, atCopy);

return InjectionPoint.parse(ctx, this.context.methodNode(), annCopy.unwrap(), atCopy);
},
true
);
}

/** Variant of {@link #getTargetMethodLocals(TargetPair)} using the effective {@code @At} data. */
@Nullable
public List<LocalVariable> getTargetMethodLocals(TargetPair target, @Nullable AtData atData) {
Type[] targetParams = Type.getArgumentTypes(target.methodNode().desc);
boolean isStatic = isStatic(this.context.methodNode());
int lvtOffset = isStatic ? 0 : 1;
// The starting LVT index is of the first var after all method parameters. Offset by 1 for instance methods to skip 'this'
int targetLocalPos = targetParams.length + lvtOffset;
return getTargetMethodLocals(target, findInjectionTargetInsns(target, atData), targetLocalPos,
this.context.environment().fabricLVTCompatibility());
}

@Nullable
private List<LocalVariable> getTargetMethodLocals(TargetPair target, List<AbstractInsnNode> targetInsns, int startPos, int lvtCompatLevel) {
if (targetInsns.isEmpty()) {
LOGGER.debug("Skipping LVT patch, no target instructions found");
return null;
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/org/sinytra/adapter/env/ctx/MixinContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.sinytra.adapter.analysis.selector.AnnotationHandle;
import org.sinytra.adapter.env.ann.AtData;
import org.sinytra.adapter.env.ann.ClassTarget;
import org.sinytra.adapter.patch.mixin.MixinFlag;
import org.sinytra.adapter.patch.mixin.MixinType;
import org.sinytra.adapter.patch.processor.Processors;
import org.sinytra.adapter.patch.resolver.Resolvers;
import org.sinytra.adapter.types.TypeAdapter;
import org.sinytra.adapter.util.AdapterUtil;
import org.sinytra.adapter.util.MethodQualifier;
import org.sinytra.adapter.util.provider.ClassLookup;

import java.util.ArrayList;
Expand All @@ -36,6 +38,32 @@ public class MixinContext implements RefMapper, Auditor {
private final Resolvers resolvers = new Resolvers();
private final Processors processors = new Processors();

/**
* Where the injection point ended up after adaptation, published by PipelineMethodTransformer.
*/
@Nullable
private AtData effectiveAtData;
@Nullable
private MethodQualifier effectiveTargetMethod;

public void setEffectiveInjectionPoint(@Nullable AtData atData, @Nullable MethodQualifier targetMethod) {
this.effectiveAtData = atData;
this.effectiveTargetMethod = targetMethod;
}

/**
* The {@code @At} data that applies to this handler, or {@code null} when adaptation did not run.
*/
@Nullable
public AtData effectiveAtData() {
return this.effectiveAtData;
}

@Nullable
public MethodQualifier effectiveTargetMethod() {
return this.effectiveTargetMethod;
}

public MixinContext(MixinType mixinType, PatchContext patchContext, ClassTarget classTarget, ClassNode classNode, MethodNode methodNode, AnnotationHandle methodAnnotation, AnnotationHandle injectionPointAnnotation, Set<MixinFlag> flags) {
this.mixinType = mixinType;
this.patchContext = patchContext;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,13 @@ static PatchEnvironment create(RefmapHolder refmapHolder, ClassLookup cleanClass
Collection<String> getKnownNamespaces();

boolean isKnownPackage(String pkg);

/**
* When enabled, mixin handlers that could not be adapted are removed from their class rather than left in
* the jar, so the mod loads with those features disabled. Read from the
* {@code connector.stripFailingMixins} system property.
*/
default boolean stripFailingMixins() {
return Boolean.getBoolean("connector.stripFailingMixins");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import org.sinytra.adapter.util.provider.MixinClassLookup;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public final class PatchEnvironmentImpl implements PatchEnvironment {
private final RefmapHolder refmapHolder;
Expand All @@ -21,7 +21,8 @@ public final class PatchEnvironmentImpl implements PatchEnvironment {
private final AuditTrail auditTrail;
private final @Nullable Collection<String> pkgNamespaces;

private final Map<ClassLookup, InheritanceHandler> inheritanceHandlers = new HashMap<>();
// Classes are processed in parallel, so the cache is shared across threads.
private final Map<ClassLookup, InheritanceHandler> inheritanceHandlers = new ConcurrentHashMap<>();

public PatchEnvironmentImpl(
RefmapHolder refmapHolder,
Expand Down Expand Up @@ -77,7 +78,14 @@ public MixinClassGenerator classGenerator() {

@Override
public InheritanceHandler inheritanceHandler(ClassLookup lookup) {
return this.inheritanceHandlers.computeIfAbsent(lookup, InheritanceHandler::new);
// Not computeIfAbsent: the handler constructor can re-enter this method.
InheritanceHandler existing = this.inheritanceHandlers.get(lookup);
if (existing != null) {
return existing;
}
InheritanceHandler created = new InheritanceHandler(lookup);
InheritanceHandler prev = this.inheritanceHandlers.putIfAbsent(lookup, created);
return prev != null ? prev : created;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ public class MixinAnnotations {
public static final String REDIRECT = "Lorg/spongepowered/asm/mixin/injection/Redirect;";
public static final String MODIFY_ARG = "Lorg/spongepowered/asm/mixin/injection/ModifyArg;";
public static final String MODIFY_ARGS = "Lorg/spongepowered/asm/mixin/injection/ModifyArgs;";
public static final String MODIFY_ARGS_INTERNAL_NAME = "org/spongepowered/asm/mixin/injection/ModifyArgs";
public static final String MODIFY_VAR = "Lorg/spongepowered/asm/mixin/injection/ModifyVariable;";
public static final String MODIFY_CONST = "Lorg/spongepowered/asm/mixin/injection/ModifyConstant;";
public static final String MODIFY_CONST_INTERNAL_NAME = "org/spongepowered/asm/mixin/injection/ModifyConstant";
public static final String OVERWRITE = "Lorg/spongepowered/asm/mixin/Overwrite;";
public static final String OVERWRITE_INTERNAL_NAME = "org/spongepowered/asm/mixin/Overwrite";
// Interface mixins
public static final String ACCESSOR = "Lorg/spongepowered/asm/mixin/gen/Accessor;";
public static final String ACCESSOR_INTERNAL_NAME = "org/spongepowered/asm/mixin/gen/Accessor";
public static final String INVOKER = "Lorg/spongepowered/asm/mixin/gen/Invoker;";
public static final String INVOKER_INTERNAL_NAME = "org/spongepowered/asm/mixin/gen/Invoker";
// Mixinextras annotations
public static final String MODIFY_EXPR_VAL = "Lcom/llamalad7/mixinextras/injector/ModifyExpressionValue;";
public static final String MODIFY_EXPR_VAL_INTERNAL_NAME = "com/llamalad7/mixinextras/injector/ModifyExpressionValue";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,12 @@ public class DynamicPatches {

public static Multimap<TxPhase, MethodTransformer> methodTransformers(List<MethodPatch> patches) {
return ImmutableMultimap.of(
TxPhase.EARLY, new LocalCaptureUpgradeTransformer(),
TxPhase.LOADED, new FieldAccessorTypeTransformer(),
TxPhase.VALIDATED, new PipelineMethodTransformer(patches, false)
TxPhase.VALIDATED, new PipelineMethodTransformer(patches, false),
// Must run after the pipeline: captured-locals signatures are re-derived from the patched method
// at the adapted injection point. Safe only because Patcher#normalizeParameterAnnotations keeps the
// parameter annotation counts in sync when captured parameters are added here.
TxPhase.VALIDATED, new LocalCaptureUpgradeTransformer()
);
}
}
Loading