/*
* Copyright (c) 2013-2016 Chris Newland.
* Licensed under https://github.com/AdoptOpenJDK/jitwatch/blob/master/LICENSE-BSD
* Instructions: https://github.com/AdoptOpenJDK/jitwatch/wiki
*/
package org.adoptopenjdk.jitwatch.jarscan.sequencesearch;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_COMMA;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_DOUBLE_QUOTE;
import static org.adoptopenjdk.jitwatch.core.JITWatchConstants.S_NEWLINE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.adoptopenjdk.jitwatch.jarscan.IJarScanOperation;
import org.adoptopenjdk.jitwatch.model.bytecode.BCParamNumeric;
import org.adoptopenjdk.jitwatch.model.bytecode.BytecodeInstruction;
import org.adoptopenjdk.jitwatch.model.bytecode.MemberBytecode;
import org.adoptopenjdk.jitwatch.model.bytecode.Opcode;
public class SequenceSearchOperation implements IJarScanOperation
{
private List<FoundSequence> matchingMethods = new ArrayList<>();
private List<Opcode> chain = new LinkedList<>();
private List<Opcode> wantedChain = new LinkedList<>();
public SequenceSearchOperation(String sequence)
{
String[] searchSequence = sequence.toLowerCase().split(S_COMMA);
for (String mnemonic : searchSequence)
{
Opcode opcode = Opcode.getByMnemonic(mnemonic);
wantedChain.add(opcode);
}
}
private boolean compareChains()
{
boolean match = true;
if (chain.size() == wantedChain.size())
{
for (int i = 0; i < chain.size(); i++)
{
Opcode gotOpcode = chain.get(i);
Opcode wantOpcode = wantedChain.get(i);
if (gotOpcode != wantOpcode)
{
match = false;
break;
}
}
}
return match;
}
public void reset()
{
chain.clear();
}
@Override
public void processInstructions(String className, MemberBytecode memberBytecode)
{
reset();
List<BytecodeInstruction> instructions = memberBytecode.getInstructions();
for (int i = 0; i < instructions.size(); i++)
{
boolean matched = handleChainStartingAtIndex(i, instructions);
if (matched)
{
BytecodeInstruction firstInstruction = instructions.get(i);
int startingBCI = firstInstruction.getOffset();
FoundSequence foundSequence = new FoundSequence(startingBCI, memberBytecode.getMemberSignatureParts());
matchingMethods.add(foundSequence);
}
}
}
private boolean handleChainStartingAtIndex(int index, List<BytecodeInstruction> instructions)
{
boolean stopChain = false;
boolean abandonChain = false;
boolean matched = false;
Set<Integer> visitedBCI = new HashSet<>();
while (chain.size() < wantedChain.size())
{
BytecodeInstruction instruction = instructions.get(index);
int instrBCI = instruction.getOffset();
visitedBCI.add(instrBCI);
Opcode opcode = instruction.getOpcode();
// =======================
// The Rules
// =======================
// *RETURN ends a chain. Chain is discarded if not required length
// INVOKE* drops through to next bytecode
// GOTO* is followed
// IF*, TABLESWITCH, and LOOKUPSWITCH - drop through
// JSR, JSR_W, RET are not followed - discard the chain
// ATHROW ends a chain
// loops are detected and end the parsing
switch (opcode)
{
case IRETURN:
case LRETURN:
case FRETURN:
case DRETURN:
case ARETURN:
case RETURN:
stopChain = true;
break;
case ATHROW:
stopChain = true;
break;
case JSR:
case JSR_W:
case RET:
abandonChain = true;
break;
case GOTO:
case GOTO_W:
int gotoBCI = ((BCParamNumeric) instruction.getParameters().get(0)).getValue();
if (!visitedBCI.contains(gotoBCI))
{
index = getIndexForBCI(instructions, gotoBCI);
}
break;
default:
index++;
break;
}
chain.add(opcode);
if (stopChain)
{
if (chain.size() == wantedChain.size())
{
matched = compareChains();
}
reset();
break;
}
else if (abandonChain)
{
reset();
break;
}
else if (chain.size() == wantedChain.size())
{
matched = compareChains();
reset();
break;
}
}
return matched;
}
private int getIndexForBCI(List<BytecodeInstruction> instructions, int bci)
{
int index = -1;
for (int i = 0; i < instructions.size(); i++)
{
BytecodeInstruction instruction = instructions.get(i);
if (instruction.getOffset() == bci)
{
index = i;
break;
}
}
return index;
}
@Override
public String getReport()
{
StringBuilder builder = new StringBuilder();
Collections.sort(matchingMethods, new Comparator<FoundSequence>()
{
@Override
public int compare(FoundSequence o1, FoundSequence o2)
{
return o1.toString().compareTo(o2.toString());
}
});
for (FoundSequence seq : matchingMethods)
{
builder.append(S_DOUBLE_QUOTE);
builder.append(seq.getMemberSignatureParts().toStringSingleLine());
builder.append(S_DOUBLE_QUOTE);
builder.append(S_COMMA);
builder.append(seq.getStartingBCI());
builder.append(S_NEWLINE);
}
return builder.toString();
}
}