org.teavm.javascript.Decompiler Maven / Gradle / Ivy
/*
* Copyright 2011 Alexey Andreev.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.teavm.javascript;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.teavm.cache.NoCache;
import org.teavm.common.Graph;
import org.teavm.common.GraphIndexer;
import org.teavm.common.Loop;
import org.teavm.common.LoopGraph;
import org.teavm.common.RangeTree;
import org.teavm.javascript.ast.AsyncMethodNode;
import org.teavm.javascript.ast.AsyncMethodPart;
import org.teavm.javascript.ast.BlockStatement;
import org.teavm.javascript.ast.ClassNode;
import org.teavm.javascript.ast.FieldNode;
import org.teavm.javascript.ast.GotoPartStatement;
import org.teavm.javascript.ast.IdentifiedStatement;
import org.teavm.javascript.ast.MethodNode;
import org.teavm.javascript.ast.NativeMethodNode;
import org.teavm.javascript.ast.NodeLocation;
import org.teavm.javascript.ast.NodeModifier;
import org.teavm.javascript.ast.RegularMethodNode;
import org.teavm.javascript.ast.SequentialStatement;
import org.teavm.javascript.ast.Statement;
import org.teavm.javascript.ast.TryCatchStatement;
import org.teavm.javascript.ast.WhileStatement;
import org.teavm.javascript.spi.GeneratedBy;
import org.teavm.javascript.spi.Generator;
import org.teavm.javascript.spi.InjectedBy;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldHolder;
import org.teavm.model.Instruction;
import org.teavm.model.InstructionLocation;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.TryCatchBlock;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.util.AsyncProgramSplitter;
import org.teavm.model.util.ListingBuilder;
import org.teavm.model.util.ProgramUtils;
/**
*
* @author Alexey Andreev
*/
public class Decompiler {
private ClassHolderSource classSource;
private ClassLoader classLoader;
private Graph graph;
private LoopGraph loopGraph;
private GraphIndexer indexer;
private int[] loops;
private int[] loopSuccessors;
private Block[] blockMap;
private int lastBlockId;
private RangeTree codeTree;
private RangeTree.Node currentNode;
private RangeTree.Node parentNode;
private Map generators = new HashMap<>();
private Set methodsToPass = new HashSet<>();
private MethodNodeCache regularMethodCache;
private Set asyncMethods;
private Set splitMethods = new HashSet<>();
private List tryCatchBookmarks = new ArrayList<>();
private Deque stack;
private Program program;
public Decompiler(ClassHolderSource classSource, ClassLoader classLoader, Set asyncMethods,
Set asyncFamilyMethods) {
this.classSource = classSource;
this.classLoader = classLoader;
this.asyncMethods = asyncMethods;
splitMethods.addAll(asyncMethods);
splitMethods.addAll(asyncFamilyMethods);
}
public MethodNodeCache getRegularMethodCache() {
return regularMethodCache;
}
public void setRegularMethodCache(MethodNodeCache regularMethodCache) {
this.regularMethodCache = regularMethodCache;
}
public int getGraphSize() {
return this.graph.size();
}
static class Block {
public Block parent;
public int parentOffset;
public final IdentifiedStatement statement;
public final List body;
public final int end;
public final int start;
public final List tryCatches = new ArrayList<>();
public Block(IdentifiedStatement statement, List body, int start, int end) {
this.statement = statement;
this.body = body;
this.start = start;
this.end = end;
}
}
static class TryCatchBookmark {
Block block;
int offset;
String exceptionType;
Integer exceptionVariable;
int exceptionHandler;
}
public List decompile(Collection classNames) {
List sequence = new ArrayList<>();
Set visited = new HashSet<>();
for (String className : classNames) {
orderClasses(className, visited, sequence);
}
final List result = new ArrayList<>();
for (int i = 0; i < sequence.size(); ++i) {
final String className = sequence.get(i);
result.add(decompile(classSource.get(className)));
}
return result;
}
public List getClassOrdering(Collection classNames) {
List sequence = new ArrayList<>();
Set visited = new HashSet<>();
for (String className : classNames) {
orderClasses(className, visited, sequence);
}
return sequence;
}
public void addGenerator(MethodReference method, Generator generator) {
generators.put(method, generator);
}
public void addMethodToPass(MethodReference method) {
methodsToPass.add(method);
}
private void orderClasses(String className, Set visited, List order) {
if (!visited.add(className)) {
return;
}
ClassHolder cls = classSource.get(className);
if (cls == null) {
return;
}
if (cls.getParent() != null) {
orderClasses(cls.getParent(), visited, order);
}
for (String iface : cls.getInterfaces()) {
orderClasses(iface, visited, order);
}
order.add(className);
}
public ClassNode decompile(ClassHolder cls) {
ClassNode clsNode = new ClassNode(cls.getName(), cls.getParent());
for (FieldHolder field : cls.getFields()) {
FieldNode fieldNode = new FieldNode(field.getName(), field.getType());
fieldNode.getModifiers().addAll(mapModifiers(field.getModifiers()));
fieldNode.setInitialValue(field.getInitialValue());
clsNode.getFields().add(fieldNode);
}
for (MethodHolder method : cls.getMethods()) {
if (method.getModifiers().contains(ElementModifier.ABSTRACT)) {
continue;
}
if (method.getAnnotations().get(InjectedBy.class.getName()) != null ||
methodsToPass.contains(method.getReference())) {
continue;
}
MethodNode methodNode = decompile(method);
clsNode.getMethods().add(methodNode);
}
clsNode.getInterfaces().addAll(cls.getInterfaces());
clsNode.getModifiers().addAll(mapModifiers(cls.getModifiers()));
return clsNode;
}
public MethodNode decompile(MethodHolder method) {
return method.getModifiers().contains(ElementModifier.NATIVE) ? decompileNative(method) :
!asyncMethods.contains(method.getReference()) ? decompileRegular(method) : decompileAsync(method);
}
public NativeMethodNode decompileNative(MethodHolder method) {
Generator generator = generators.get(method.getReference());
if (generator == null) {
AnnotationHolder annotHolder = method.getAnnotations().get(GeneratedBy.class.getName());
if (annotHolder == null) {
throw new DecompilationException("Method " + method.getOwnerName() + "." + method.getDescriptor() +
" is native, but no " + GeneratedBy.class.getName() + " annotation found");
}
ValueType annotValue = annotHolder.getValues().get("value").getJavaClass();
String generatorClassName = ((ValueType.Object)annotValue).getClassName();
try {
Class> generatorClass = Class.forName(generatorClassName, true, classLoader);
generator = (Generator)generatorClass.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
throw new DecompilationException("Error instantiating generator " + generatorClassName +
" for native method " + method.getOwnerName() + "." + method.getDescriptor());
}
}
NativeMethodNode methodNode = new NativeMethodNode(new MethodReference(method.getOwnerName(),
method.getDescriptor()));
methodNode.getModifiers().addAll(mapModifiers(method.getModifiers()));
methodNode.setGenerator(generator);
methodNode.setAsync(asyncMethods.contains(method.getReference()));
return methodNode;
}
public RegularMethodNode decompileRegular(MethodHolder method) {
if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) {
return decompileRegularCacheMiss(method);
}
RegularMethodNode node = regularMethodCache.get(method.getReference());
if (node == null) {
node = decompileRegularCacheMiss(method);
regularMethodCache.store(method.getReference(), node);
}
return node;
}
public RegularMethodNode decompileRegularCacheMiss(MethodHolder method) {
RegularMethodNode methodNode = new RegularMethodNode(method.getReference());
Program program = method.getProgram();
int[] targetBlocks = new int[program.basicBlockCount()];
Arrays.fill(targetBlocks, -1);
try {
methodNode.setBody(getRegularMethodStatement(program, targetBlocks, false).getStatement());
} catch (RuntimeException e) {
StringBuilder sb = new StringBuilder("Error decompiling method " + method.getReference() + ":\n");
sb.append(new ListingBuilder().buildListing(program, " "));
throw new DecompilationException(sb.toString(), e);
}
for (int i = 0; i < program.variableCount(); ++i) {
methodNode.getVariables().add(program.variableAt(i).getRegister());
}
Optimizer optimizer = new Optimizer();
optimizer.optimize(methodNode, method.getProgram());
methodNode.getModifiers().addAll(mapModifiers(method.getModifiers()));
int paramCount = Math.min(method.getSignature().length, program.variableCount());
for (int i = 0; i < paramCount; ++i) {
Variable var = program.variableAt(i);
methodNode.getParameterDebugNames().add(new HashSet<>(var.getDebugNames()));
}
return methodNode;
}
public AsyncMethodNode decompileAsync(MethodHolder method) {
if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) {
return decompileAsyncCacheMiss(method);
}
AsyncMethodNode node = regularMethodCache.getAsync(method.getReference());
if (node == null || !checkAsyncRelevant(node)) {
node = decompileAsyncCacheMiss(method);
regularMethodCache.storeAsync(method.getReference(), node);
}
return node;
}
private boolean checkAsyncRelevant(AsyncMethodNode node) {
AsyncCallsFinder asyncCallsFinder = new AsyncCallsFinder();
for (AsyncMethodPart part : node.getBody()) {
part.getStatement().acceptVisitor(asyncCallsFinder);
}
for (MethodReference asyncCall : asyncCallsFinder.asyncCalls) {
if (!splitMethods.contains(asyncCall)) {
return false;
}
}
asyncCallsFinder.allCalls.removeAll(asyncCallsFinder.asyncCalls);
for (MethodReference asyncCall : asyncCallsFinder.allCalls) {
if (splitMethods.contains(asyncCall)) {
return false;
}
}
return true;
}
private AsyncMethodNode decompileAsyncCacheMiss(MethodHolder method) {
AsyncMethodNode node = new AsyncMethodNode(method.getReference());
AsyncProgramSplitter splitter = new AsyncProgramSplitter(classSource, splitMethods);
splitter.split(method.getProgram());
for (int i = 0; i < splitter.size(); ++i) {
AsyncMethodPart part;
try {
part = getRegularMethodStatement(splitter.getProgram(i), splitter.getBlockSuccessors(i), i > 0);
} catch (RuntimeException e) {
StringBuilder sb = new StringBuilder("Error decompiling method " + method.getReference() +
" part " + i + ":\n");
sb.append(new ListingBuilder().buildListing(splitter.getProgram(i), " "));
throw new DecompilationException(sb.toString(), e);
}
node.getBody().add(part);
}
Program program = method.getProgram();
for (int i = 0; i < program.variableCount(); ++i) {
node.getVariables().add(program.variableAt(i).getRegister());
}
Optimizer optimizer = new Optimizer();
optimizer.optimize(node, splitter);
node.getModifiers().addAll(mapModifiers(method.getModifiers()));
int paramCount = Math.min(method.getSignature().length, program.variableCount());
for (int i = 0; i < paramCount; ++i) {
Variable var = program.variableAt(i);
node.getParameterDebugNames().add(new HashSet<>(var.getDebugNames()));
}
return node;
}
private AsyncMethodPart getRegularMethodStatement(Program program, int[] targetBlocks, boolean async) {
AsyncMethodPart result = new AsyncMethodPart();
lastBlockId = 1;
graph = ProgramUtils.buildControlFlowGraphWithTryCatch(program);
int[] weights = new int[graph.size()];
for (int i = 0; i < weights.length; ++i) {
weights[i] = program.basicBlockAt(i).getInstructions().size();
}
int[] priorities = new int[graph.size()];
for (int i = 0; i < targetBlocks.length; ++i) {
if (targetBlocks[i] >= 0) {
priorities[i] = 1;
}
}
indexer = new GraphIndexer(graph, weights, priorities);
graph = indexer.getGraph();
loopGraph = new LoopGraph(this.graph);
unflatCode();
blockMap = new Block[program.basicBlockCount() * 2 + 1];
stack = new ArrayDeque<>();
this.program = program;
BlockStatement rootStmt = new BlockStatement();
rootStmt.setId("root");
stack.push(new Block(rootStmt, rootStmt.getBody(), -1, -1));
StatementGenerator generator = new StatementGenerator();
generator.classSource = classSource;
generator.program = program;
generator.blockMap = blockMap;
generator.indexer = indexer;
parentNode = codeTree.getRoot();
currentNode = parentNode.getFirstChild();
generator.async = async;
for (int i = 0; i < this.graph.size(); ++i) {
int node = i < indexer.size() ? indexer.nodeAt(i) : -1;
int next = i + 1;
int head = loops[i];
if (head != -1 && loopSuccessors[head] == next) {
next = head;
}
if (node >= 0) {
generator.currentBlock = program.basicBlockAt(node);
int tmp = indexer.nodeAt(next);
generator.nextBlock = tmp >= 0 && next < indexer.size() ? program.basicBlockAt(tmp) : null;
}
closeExpiredBookmarks(generator, generator.currentBlock.getTryCatchBlocks());
List inheritedBookmarks = new ArrayList<>();
Block block = stack.peek();
while (block.end == i) {
Block oldBlock = block;
stack.pop();
block = stack.peek();
if (block.start >= 0) {
int mappedStart = indexer.nodeAt(block.start);
if (blockMap[mappedStart] == oldBlock) {
blockMap[mappedStart] = block;
}
}
for (int j = oldBlock.tryCatches.size() - 1; j >= 0; --j) {
TryCatchBookmark bookmark = oldBlock.tryCatches.get(j);
TryCatchStatement tryCatchStmt = new TryCatchStatement();
tryCatchStmt.setExceptionType(bookmark.exceptionType);
tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable);
tryCatchStmt.getHandler().add(generator.generateJumpStatement(
program.basicBlockAt(bookmark.exceptionHandler)));
List blockPart = oldBlock.body.subList(bookmark.offset, oldBlock.body.size());
tryCatchStmt.getProtectedBody().addAll(blockPart);
blockPart.clear();
if (!tryCatchStmt.getProtectedBody().isEmpty()) {
blockPart.add(tryCatchStmt);
}
inheritedBookmarks.add(bookmark);
}
oldBlock.tryCatches.clear();
}
for (int j = inheritedBookmarks.size() - 1; j >= 0; --j) {
TryCatchBookmark bookmark = inheritedBookmarks.get(j);
bookmark.block = block;
bookmark.offset = block.body.size();
block.tryCatches.add(bookmark);
}
while (parentNode.getEnd() == i) {
currentNode = parentNode.getNext();
parentNode = parentNode.getParent();
}
for (Block newBlock : createBlocks(i)) {
block.body.add(newBlock.statement);
newBlock.parent = block;
newBlock.parentOffset = block.body.size();
stack.push(newBlock);
block = newBlock;
}
createNewBookmarks(generator.currentBlock.getTryCatchBlocks());
if (node >= 0) {
generator.statements.clear();
InstructionLocation lastLocation = null;
NodeLocation nodeLocation = null;
List instructions = generator.currentBlock.getInstructions();
for (int j = 0; j < instructions.size(); ++j) {
Instruction insn = generator.currentBlock.getInstructions().get(j);
if (insn.getLocation() != null && lastLocation != insn.getLocation()) {
lastLocation = insn.getLocation();
nodeLocation = new NodeLocation(lastLocation.getFileName(), lastLocation.getLine());
}
if (insn.getLocation() != null) {
generator.setCurrentLocation(nodeLocation);
}
insn.acceptVisitor(generator);
}
if (targetBlocks[node] >= 0) {
GotoPartStatement stmt = new GotoPartStatement();
stmt.setPart(targetBlocks[node]);
generator.statements.add(stmt);
}
block.body.addAll(generator.statements);
}
}
SequentialStatement resultBody = new SequentialStatement();
resultBody.getSequence().addAll(rootStmt.getBody());
result.setStatement(resultBody);
return result;
}
private void closeExpiredBookmarks(StatementGenerator generator, List tryCatchBlocks) {
tryCatchBlocks = new ArrayList<>(tryCatchBlocks);
Collections.reverse(tryCatchBlocks);
// Find which try catch blocks have remained since the previous basic block
int sz = Math.min(tryCatchBlocks.size(), tryCatchBookmarks.size());
int start;
for (start = 0; start < sz; ++start) {
TryCatchBlock tryCatch = tryCatchBlocks.get(start);
TryCatchBookmark bookmark = tryCatchBookmarks.get(start);
if (tryCatch.getHandler().getIndex() != bookmark.exceptionHandler) {
break;
}
if (!Objects.equals(tryCatch.getExceptionType(), bookmark.exceptionType)) {
break;
}
}
// Close old bookmarks
for (int i = tryCatchBookmarks.size() - 1; i >= start; --i) {
TryCatchBookmark bookmark = tryCatchBookmarks.get(i);
Block block = stack.peek();
while (block != bookmark.block) {
TryCatchStatement tryCatchStmt = new TryCatchStatement();
tryCatchStmt.setExceptionType(bookmark.exceptionType);
tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable);
tryCatchStmt.getHandler().add(generator.generateJumpStatement(
program.basicBlockAt(bookmark.exceptionHandler)));
tryCatchStmt.getProtectedBody().addAll(block.body);
block.body.clear();
if (!tryCatchStmt.getProtectedBody().isEmpty()) {
block.body.add(tryCatchStmt);
}
block = block.parent;
}
TryCatchStatement tryCatchStmt = new TryCatchStatement();
tryCatchStmt.setExceptionType(bookmark.exceptionType);
tryCatchStmt.setExceptionVariable(bookmark.exceptionVariable);
tryCatchStmt.getHandler().add(generator.generateJumpStatement(
program.basicBlockAt(bookmark.exceptionHandler)));
List blockPart = block.body.subList(bookmark.offset, block.body.size());
tryCatchStmt.getProtectedBody().addAll(blockPart);
blockPart.clear();
if (!tryCatchStmt.getProtectedBody().isEmpty()) {
blockPart.add(tryCatchStmt);
}
bookmark.block.tryCatches.remove(bookmark);
}
tryCatchBookmarks.subList(start, tryCatchBookmarks.size()).clear();
}
private void createNewBookmarks(List tryCatchBlocks) {
// Add new bookmarks
for (int i = tryCatchBookmarks.size(); i < tryCatchBlocks.size(); ++i) {
TryCatchBlock tryCatch = tryCatchBlocks.get(i);
TryCatchBookmark bookmark = new TryCatchBookmark();
bookmark.block = stack.peek();
bookmark.offset = bookmark.block.body.size();
bookmark.exceptionHandler = tryCatch.getHandler().getIndex();
bookmark.exceptionType = tryCatch.getExceptionType();
bookmark.exceptionVariable = tryCatch.getExceptionVariable() != null ?
tryCatch.getExceptionVariable().getIndex() : null;
bookmark.block.tryCatches.add(bookmark);
tryCatchBookmarks.add(bookmark);
}
}
private Set mapModifiers(Set modifiers) {
Set result = EnumSet.noneOf(NodeModifier.class);
if (modifiers.contains(ElementModifier.STATIC)) {
result.add(NodeModifier.STATIC);
}
if (modifiers.contains(ElementModifier.INTERFACE)) {
result.add(NodeModifier.INTERFACE);
}
if (modifiers.contains(ElementModifier.ENUM)) {
result.add(NodeModifier.ENUM);
}
if (modifiers.contains(ElementModifier.SYNCHRONIZED)) {
result.add(NodeModifier.SYNCHRONIZED);
}
return result;
}
private List createBlocks(int start) {
List result = new ArrayList<>();
while (currentNode != null && currentNode.getStart() == start) {
Block block;
IdentifiedStatement statement;
boolean loop = false;
if (loopSuccessors[start] == currentNode.getEnd() || isSingleBlockLoop(start)) {
WhileStatement whileStatement = new WhileStatement();
statement = whileStatement;
block = new Block(statement, whileStatement.getBody(), start, currentNode.getEnd());
loop = true;
} else {
BlockStatement blockStatement = new BlockStatement();
statement = blockStatement;
block = new Block(statement, blockStatement.getBody(), start, currentNode.getEnd());
}
result.add(block);
int mappedIndex = indexer.nodeAt(currentNode.getEnd());
if (mappedIndex >= 0 && (blockMap[mappedIndex] == null ||
!(blockMap[mappedIndex].statement instanceof WhileStatement))) {
blockMap[mappedIndex] = block;
}
if (loop) {
blockMap[indexer.nodeAt(start)] = block;
}
parentNode = currentNode;
currentNode = currentNode.getFirstChild();
}
for (Block block : result) {
block.statement.setId("block" + lastBlockId++);
}
return result;
}
private boolean isSingleBlockLoop(int index) {
for (int succ : graph.outgoingEdges(index)) {
if (succ == index) {
return true;
}
}
return false;
}
private void unflatCode() {
Graph graph = this.graph;
int sz = graph.size();
// Find where each loop ends
//
int[] loopSuccessors = new int[sz];
Arrays.fill(loopSuccessors, sz + 1);
for (int node = 0; node < sz; ++node) {
Loop loop = loopGraph.loopAt(node);
while (loop != null) {
loopSuccessors[loop.getHead()] = node + 1;
loop = loop.getParent();
}
}
// For each node find head of loop this node belongs to.
//
int[] loops = new int[sz];
Arrays.fill(loops, -1);
for (int head = 0; head < sz; ++head) {
int end = loopSuccessors[head];
if (end > sz) {
continue;
}
for (int node = head + 1; node < end; ++node) {
loops[node] = head;
}
}
List ranges = new ArrayList<>();
for (int node = 0; node < sz; ++node) {
if (loopSuccessors[node] <= sz) {
ranges.add(new RangeTree.Range(node, loopSuccessors[node]));
}
int start = sz;
for (int prev : graph.incomingEdges(node)) {
start = Math.min(start, prev);
}
if (start < node - 1) {
ranges.add(new RangeTree.Range(start, node));
}
}
for (int node = 0; node < sz; ++node) {
if (isSingleBlockLoop(node)) {
ranges.add(new RangeTree.Range(node, node + 1));
}
}
codeTree = new RangeTree(sz + 1, ranges);
this.loopSuccessors = loopSuccessors;
this.loops = loops;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy