Identify loops in java byte code

Viewed 10211

I am trying to instrument java byte code.

I want to recognize the entry and exit of a java loop, but I have found the identification of loops to be quite challenging. I have spent a good few hours looking at ASM and open source de-compilers (whom I thought must solve this problem all the time), however, I came up short.

The tool I am augmenting / extending, is using ASM, so ideally I would like to know how to instrument the entry and exit of the different loop constructs in java via ASM. However, I would also welcome a recommendation for a good open source de-compiler, as clearly they would have solved the same problem.

4 Answers

I know this is an old question - however, there was specific interest in how this would be achievable with the ASM library, and this may be of use to future visitors. Bearing in mind the caveats other answers give warning against generalized assumptions related to the "goto" statement, there is a way to do that. (This assumes that any grouping of code within a given method that can "loop" should be detected - usually this is an actual loop construct, but other (rare, but present) examples have been provided of how this can occur.)

The main thing you'd need to do is keep track of the "labels" (locations in the byte code) that ASM visits prior to what it terms a "jump instruction" - if the label it jumps to has already been encountered in the context of the same method, then you have a potential for looping code.

A notable difference I saw between the answers here and how ASM behaved is that it read the "looping" jump commands for a simple file as opcodes other than "goto" - this may be just changes in Java compilation in the time since this was asked, but seemed worth noting.

The basic example code for ASM is this (this is entered via the checkForLoops method):

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public void checkForLoops(Path classFile) {
    LoopClassVisitor classVisitor = new LoopClassVisitor();

    try (InputStream inputStream = Files.newInputStream(classFile)) {
        ClassReader cr = new ClassReader(inputStream);

        cr.accept(classVisitor, 0);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

public class LoopClassVisitor extends ClassVisitor {

    public LoopClassVisitor() {
        super(Opcodes.ASM7);
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String descriptor, String signature,
            String[] exceptions) {
        return new LoopMethodVisitor();
    }

}

public class LoopMethodVisitor extends MethodVisitor {

    private List<Label> visitedLabels;

    public LoopMethodVisitor() {
        super(Opcodes.ASM7);

        visitedLabels = new ArrayList<>();
    }

    @Override
    public void visitLineNumber(final int line, final Label start) {
        System.out.println("lnLabel: " + start.toString());

        visitedLabels.add(start);
    }

    @Override
    public void visitLabel(final Label label) {
        System.out.println("vLabel: " + label.toString());

        visitedLabels.add(label);
    }

    @Override
    public void visitJumpInsn(final int opcode, final Label label) {
        System.out.println("Label: " + label.toString());

        if (visitedLabels.contains(label)) {
            System.out.println("Op: " + opcode + ", GOTO to previous command - possible looped execution");
        }
    }

}

You could additionally attach line number information when available to the labels, and track that within the method visitor, to output where the detect loops start and end within source.

Related