[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/jruby/jruby-openssl/master/src/build/java/ShimInliner.java [Back]  [Original]

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.BasicInterpreter;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;

/*
 * Inlines every static method of org.jruby.ext.openssl.shim and the classes.
 * Classes that do not call into the shim are not rewritten at all.
 *
 * NOTE: unlike ProGuard only removes the *call*; does not fold the constant left behind,
 * so a dead FIPS branch still stays in the bytecode.
 */
public class ShimInliner {

    private static final String SHIM_PKG = "org/jruby/ext/openssl/shim/";

    public static void main(String[] args) throws Exception {
        final Path classesDir = Paths.get(args[0]);

        final List classFiles = new ArrayList();
        try (Stream walk = Files.walk(classesDir)) {
            walk.filter(p -> p.toString().endsWith(".class")).forEach(classFiles::add);
        }

        // every static shim method is a candidate
        final Map shims = new LinkedHashMap();
        for (Path p : classFiles) {
            ClassNode cn = read(p);
            if (cn.name.startsWith(SHIM_PKG)) shims.put(cn.name, cn);
        }
        if (shims.isEmpty()) {
            // already inlined (re-run over the same output dir)
            // unless a caller still points at a shim we just cannot see
            if (referencedBy(classFiles, classesDir, SHIM_PKG)) {
                throw new IllegalStateException("shim classes are gone but still referenced - "
                        + classesDir + " is half-compiled, run a clean build");
            }
            System.out.println("[ShimInliner] no shim classes, nothing to do");
            return;
        }

        final Map candidates = new HashMap();
        for (ClassNode cn : shims.values()) {
            for (MethodNode mn : cn.methods) {
                if ((mn.access & Opcodes.ACC_STATIC) != 0 && !"".equals(mn.name)) {
                    candidates.put(cn.name + '.' + mn.name + mn.desc, mn);
                }
            }
        }

        // some shim methods call each other (decodeString -> private helpers);
        // flatten the shim bodies first - avoid an inlined body pointing at a shim class
        for (int pass = 0; pass < 3; pass++) {
            for (ClassNode cn : shims.values()) {
                // a skip here may still inline on a later pass, leftovers surface as a kept shim
                for (MethodNode mn : cn.methods) inlineInto(cn, mn, candidates, new ArrayList());
            }
        }

        final Collection skipped = new LinkedHashSet();
        int rewritten = 0, inlined = 0;
        for (Path p : classFiles) {
            ClassNode cn = read(p);
            if (cn.name.startsWith(SHIM_PKG)) continue;
            int n = 0;
            for (MethodNode mn : cn.methods) n += inlineInto(cn, mn, candidates, skipped);
            if (n > 0) {
                Files.write(p, write(cn));
                rewritten++;
                inlined += n;
                System.out.println("[ShimInliner] " + cn.name + ": inlined " + n + " call(s)");
            }
        }

        int deleted = 0;
        final List kept = new ArrayList();
        for (String shim : shims.keySet()) {
            if (referencedBy(classFiles, classesDir, shim)) {
                kept.add(shim);
                continue;
            }
            Files.delete(classesDir.resolve(shim + ".class"));
            deleted++;
        }
        System.out.println("[ShimInliner] inlined " + inlined + " call(s) in " + rewritten
                + " class(es), deleted " + deleted + " of " + shims.size() + " shim class(es)");

        // a shim left behind ships FIPS-variant code in the plain gem, refusing to build beats
        // shipping it silently
        if (!skipped.isEmpty() || !kept.isEmpty()) {
            StringBuilder msg = new StringBuilder("shim inlining incomplete:");
            for (String s : skipped) msg.append("\n  not inlined: ").append(s);
            for (String s : kept) msg.append("\n  still referenced: ").append(s);
            throw new IllegalStateException(msg.toString());
        }
    }

    /** @return number of call sites inlined */
    private static int inlineInto(ClassNode owner, MethodNode method, Map candidates,
            Collection skipped) throws Exception {
        if (method.instructions == null || method.instructions.size() == 0) return 0;

        final List calls = new ArrayList();
        for (AbstractInsnNode insn : method.instructions.toArray()) {
            if (insn.getOpcode() == Opcodes.INVOKESTATIC) {
                MethodInsnNode call = (MethodInsnNode) insn;
                if (call.owner.startsWith(SHIM_PKG) && candidates.containsKey(key(call))) calls.add(call);
            }
        }
        if (calls.isEmpty()) return 0;

        // entering a handler clears the operand stack, so a callee that catches can only be
        // inlined where the stack holds nothing but the call arguments - index up front,
        // splicing shifts instruction positions
        Frame[] frames = null;
        final Map indices = new HashMap();
        for (MethodInsnNode call : calls) {
            if (!candidates.get(key(call)).tryCatchBlocks.isEmpty()) {
                if (frames == null) frames = new Analyzer(new BasicInterpreter()).analyze(owner.name, method);
                indices.put(call, method.instructions.indexOf(call));
            }
        }

        int count = 0;
        for (MethodInsnNode call : calls) {
            MethodNode callee = candidates.get(key(call));
            if (!callee.tryCatchBlocks.isEmpty()) {
                Frame frame = frames[indices.get(call)];
                int args = Type.getArgumentTypes(call.desc).length;
                if (frame == null || frame.getStackSize() - args != 0) {
                    skipped.add(key(call) + " in " + owner.name + '.' + method.name
                            + " - catches exceptions, stack not empty");
                    continue;
                }
            }
            splice(method, call, callee);
            count++;
        }
        return count;
    }

    private static void splice(MethodNode method, MethodInsnNode call, MethodNode callee) {
        final int base = method.maxLocals; // callee locals live above the caller's
        final Type[] argTypes = Type.getArgumentTypes(call.desc);

        final InsnList body = new InsnList();
        int[] slots = new int[argTypes.length];
        for (int i = 0, slot = 0; i < argTypes.length; i++) {
            slots[i] = slot;
            slot += argTypes[i].getSize();
        }
        for (int i = argTypes.length - 1; i >= 0; i--) { // arguments come off the stack in reverse
            body.add(new VarInsnNode(argTypes[i].getOpcode(Opcodes.ISTORE), base + slots[i]));
        }

        final Map labels = new HashMap();
        for (AbstractInsnNode insn : callee.instructions.toArray()) {
            if (insn instanceof LabelNode) labels.put((LabelNode) insn, new LabelNode());
        }
        final LabelNode end = new LabelNode();

        for (AbstractInsnNode insn : callee.instructions.toArray()) {
            // callee's line numbers belong to another source file, and frames get
            // recomputed on write
            if (insn instanceof LineNumberNode || insn instanceof FrameNode) continue;

            int op = insn.getOpcode();
            if (op >= Opcodes.IRETURN && op = 0) return true;
        }
        return false;
    }

    private static int indexOf(byte[] haystack, byte[] needle) {
        outer:
        for (int i = 0; i 

Web Proxy Viewer  |  New URL  |  Original Page