All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.llamalad7.mixinextras.expression.impl.point.ExpressionInjectionPoint Maven / Gradle / Ivy

package com.llamalad7.mixinextras.expression.impl.point;

import com.llamalad7.mixinextras.expression.Definition;
import com.llamalad7.mixinextras.expression.impl.ExpressionParserFacade;
import com.llamalad7.mixinextras.expression.impl.ExpressionService;
import com.llamalad7.mixinextras.expression.impl.ast.expressions.Expression;
import com.llamalad7.mixinextras.expression.impl.flow.ComplexDataException;
import com.llamalad7.mixinextras.expression.impl.flow.FlowInterpreter;
import com.llamalad7.mixinextras.expression.impl.flow.FlowValue;
import com.llamalad7.mixinextras.expression.impl.flow.expansion.InsnExpander;
import com.llamalad7.mixinextras.expression.impl.pool.BytecodeIdentifierPool;
import com.llamalad7.mixinextras.expression.impl.pool.IdentifierPool;
import com.llamalad7.mixinextras.expression.impl.utils.FlowDecorations;
import com.llamalad7.mixinextras.injector.ModifyExpressionValueInjectionInfo;
import com.llamalad7.mixinextras.injector.ModifyReceiverInjectionInfo;
import com.llamalad7.mixinextras.injector.ModifyReturnValueInjectionInfo;
import com.llamalad7.mixinextras.injector.v2.WrapWithConditionInjectionInfo;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperationInjectionInfo;
import com.llamalad7.mixinextras.service.MixinExtrasVersion;
import com.llamalad7.mixinextras.utils.*;
import com.llamalad7.mixinextras.wrapper.WrapperInjectionInfo;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfig;
import org.spongepowered.asm.mixin.injection.InjectionPoint;
import org.spongepowered.asm.mixin.injection.InjectionPoint.AtCode;
import org.spongepowered.asm.mixin.injection.struct.*;
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode;
import org.spongepowered.asm.util.Annotations;

import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

/**
 * See {@link com.llamalad7.mixinextras.expression.Expression @Expression}.
 */
@AtCode("MIXINEXTRAS:EXPRESSION")
public class ExpressionInjectionPoint extends InjectionPoint {
    private static List CURRENT_TARGETS;
    private static InjectionInfo CURRENT_INFO;

    private final int ordinal;
    private final String id;
    private final boolean isInSlice;

    private boolean initialized;
    private IdentifierPool pool;
    private List expressions;
    private ExpressionContext.Type contextType;

    static {
        ExpressionService.offerInstance(new RuntimeExpressionService());
    }

    public ExpressionInjectionPoint(InjectionPointData data) {
        super(data);
        this.ordinal = data.getOrdinal();
        this.id = data.getId() != null ? data.getId() : "";
        this.isInSlice = data.get(Decorations.IS_IN_SLICE, false);
    }

    @Override
    public boolean find(String desc, InsnList insns, Collection nodes) {
        if (insns.size() == 0) {
            return false;
        }
        Target target = getTarget(insns);
        if (!initialized) {
            initialize(target);
        }
        Collection flows =
                TargetDecorations.getOrPut(target, "ValueFlow",
                        () -> FlowInterpreter.analyze(CURRENT_INFO.getClassNode(), target.method, null)
                );
        Set result = new HashSet<>();

        Map> genericDecorations = new IdentityHashMap<>();
        Map> injectorSpecificDecorations = new IdentityHashMap<>();
        List captured = new ArrayList<>();
        Expression.OutputSink sink = new Expression.OutputSink() {
            @Override
            public void capture(FlowValue node, Expression expr, ExpressionContext ctx) {
                AbstractInsnNode capturedInsn = node.getInsn();
                InsnExpander.Expansion expansion = InsnExpander.prepareExpansion(node, target, CURRENT_INFO, ctx);
                AbstractInsnNode targetInsn;
                BiConsumer decorate;
                BiConsumer decorateInjectorSpecific;
                if (expansion != null) {
                    targetInsn = expansion.compound;
                    decorate = (k, v) -> expansion.decorate(CURRENT_INFO, k, v);
                    decorateInjectorSpecific = (k, v) -> expansion.decorateInjectorSpecific(CURRENT_INFO, k, v);
                } else {
                    targetInsn = node.getInsn();
                    InjectionNode injectionNode = target.addInjectionNode(capturedInsn);
                    decorate = injectionNode::decorate;
                    decorateInjectorSpecific = (k, v) -> InjectorUtils.decorateInjectorSpecific(injectionNode, CURRENT_INFO, k, v);
                }
                Map decorations = genericDecorations.get(capturedInsn);
                if (decorations != null) {
                    for (Map.Entry decoration : decorations.entrySet()) {
                        decorate.accept(decoration.getKey(), decoration.getValue());
                    }
                }
                Map injectorSpecific = injectorSpecificDecorations.get(capturedInsn);
                if (injectorSpecific != null) {
                    for (Map.Entry decoration : injectorSpecific.entrySet()) {
                        decorateInjectorSpecific.accept(decoration.getKey(), decoration.getValue());
                    }
                }
                for (Map.Entry decoration : node.getDecorations().entrySet()) {
                    if (decoration.getKey().startsWith(FlowDecorations.PERSISTENT)) {
                        decorate.accept(decoration.getKey(), decoration.getValue());
                    }
                }
                captured.add(targetInsn);
            }

            @Override
            public void decorate(AbstractInsnNode insn, String key, Object value) {
                genericDecorations.computeIfAbsent(insn, k -> new HashMap<>()).put(key, value);
            }

            @Override
            public void decorateInjectorSpecific(AbstractInsnNode insn, String key, Object value) {
                injectorSpecificDecorations.computeIfAbsent(insn, k -> new HashMap<>()).put(key, value);
            }
        };
        ExpressionContext ctx = new ExpressionContext(
                pool,
                sink,
                target.classNode,
                target.method,
                contextType,
                false
        );

        for (Expression expr : expressions) {
            for (FlowValue flow : flows) {
                try {
                    if (expr.matches(flow, ctx)) {
                        result.addAll(captured);
                    }
                } catch (ComplexDataException ignored) {
                }
                genericDecorations.clear();
                injectorSpecificDecorations.clear();
                captured.clear();
            }
        }
        int i = 0;
        boolean found = false;
        for (ListIterator it = insns.iterator(); it.hasNext(); ) {
            AbstractInsnNode insn = it.next();

            if (result.contains(insn)) {
                if (ordinal < 0 || ordinal == i) {
                    nodes.add(insn);
                    found = true;
                }
                i++;
            }
        }

        return found;
    }

    private void initialize(Target target) {
        checkDeclaredMinVersion();
        initialized = true;
        AnnotationNode poolAnnotation = ASMUtils.getRepeatedMEAnnotation(CURRENT_INFO.getMethod(), Definition.class);
        pool = new BytecodeIdentifierPool(target, CURRENT_INFO, poolAnnotation);
        expressions = parseExpressions();
        contextType = selectContextType();
    }

    public static void withContext(InjectionInfo info, Runnable runnable) {
        InjectionInfo oldInfo = CURRENT_INFO;
        List oldTargets = CURRENT_TARGETS;
        try {
            CURRENT_INFO = info;
            CURRENT_TARGETS = CompatibilityHelper.getTargets(info);
            runnable.run();
        } finally {
            CURRENT_INFO = oldInfo;
            CURRENT_TARGETS = oldTargets;
        }
    }

    /**
     * Hacky way to get the {@link Target} even though we're not passed it.
     */
    private Target getTarget(InsnList insns) {
        AbstractInsnNode marker = insns.getFirst();
        Target target = null;
        for (Target candidate : CURRENT_TARGETS) {
            if (candidate.method.instructions.contains(marker)) {
                target = candidate;
                break;
            }
        }
        if (target == null) {
            throw new IllegalStateException("Could not find target for " + insns);
        }
        // This target is the least likely to be used again so push it to the back:
        CURRENT_TARGETS.remove(target);
        CURRENT_TARGETS.add(target);
        return target;
    }

    private void checkDeclaredMinVersion() {
        IMixinConfig config = CompatibilityHelper.getMixin(CURRENT_INFO).getMixin().getConfig();
        MixinExtrasVersion min = MixinConfigUtils.minVersionFor(config);
        if (min.getNumber() < MixinExtrasVersion.V0_5_0_BETA_1.getNumber()) {
            throw new UnsupportedOperationException(
                    String.format(
                            "In order to use Expressions, Mixin Config '%s' needs to declare a \"%s\"" +
                                    " of at least %s! E.g. `\"%2$s\": \"%s\"`",
                            config, MixinConfigUtils.KEY_MIN_VERSION, MixinExtrasVersion.V0_5_0_BETA_1, MixinExtrasVersion.LATEST
                    )
            );
        }
    }

    private List parseExpressions() {
        List strings = getMatchingExpressions(CURRENT_INFO.getMethod());
        return strings.stream().map(ExpressionParserFacade::parse).collect(Collectors.toList());
    }

    private List getMatchingExpressions(MethodNode method) {
        List result = new ArrayList<>();
        AnnotationNode expressions = ASMUtils.getRepeatedMEAnnotation(method, com.llamalad7.mixinextras.expression.Expression.class);
        for (AnnotationNode expression : Annotations.getValue(expressions, "value", true)) {
            if (Annotations.getValue(expression, "id", "").equals(this.id)) {
                result.addAll(Annotations.getValue(expression, "value", true));
            }
        }
        if (result.isEmpty()) {
            String idText = id.isEmpty() ? "" : "for id '" + id + "' ";
            throw new IllegalStateException("No expression found " + idText + "on " + CURRENT_INFO);
        }
        return result;
    }

    private ExpressionContext.Type selectContextType() {
        if (isInSlice) {
            return ExpressionContext.Type.SLICE;
        }
        InjectionInfo info = CURRENT_INFO;
        while (info instanceof WrapperInjectionInfo) {
            info = ((WrapperInjectionInfo) info).getDelegate();
        }
        if (info instanceof CallbackInjectionInfo) {
            return ExpressionContext.Type.INJECT;
        }
        if (info instanceof ModifyArgInjectionInfo) {
            return ExpressionContext.Type.MODIFY_ARG;
        }
        if (info instanceof ModifyArgsInjectionInfo) {
            return ExpressionContext.Type.MODIFY_ARGS;
        }
        if (info instanceof ModifyConstantInjectionInfo) {
            return ExpressionContext.Type.MODIFY_CONSTANT;
        }
        if (info instanceof ModifyExpressionValueInjectionInfo) {
            return ExpressionContext.Type.MODIFY_EXPRESSION_VALUE;
        }
        if (info instanceof ModifyReceiverInjectionInfo) {
            return ExpressionContext.Type.MODIFY_RECEIVER;
        }
        if (info instanceof ModifyReturnValueInjectionInfo) {
            return ExpressionContext.Type.MODIFY_RETURN_VALUE;
        }
        if (info instanceof ModifyVariableInjectionInfo) {
            return ExpressionContext.Type.MODIFY_VARIABLE;
        }
        if (info instanceof RedirectInjectionInfo) {
            return ExpressionContext.Type.REDIRECT;
        }
        if (info instanceof WrapOperationInjectionInfo) {
            return ExpressionContext.Type.WRAP_OPERATION;
        }
        if (info instanceof WrapWithConditionInjectionInfo) {
            return ExpressionContext.Type.WRAP_WITH_CONDITION;
        }
        return ExpressionContext.Type.CUSTOM;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy