Java tutorial
/* * Copyright 2016 Sam Sun <me@samczsun.com> * * 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 com.heliosdecompiler.helios.tasks; import com.github.javaparser.JavaParser; import com.github.javaparser.ParseException; import com.github.javaparser.TokenMgrError; import com.github.javaparser.ast.*; import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.comments.BlockComment; import com.github.javaparser.ast.comments.JavadocComment; import com.github.javaparser.ast.comments.LineComment; import com.github.javaparser.ast.expr.*; import com.github.javaparser.ast.stmt.*; import com.github.javaparser.ast.type.*; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.heliosdecompiler.helios.Helios; import com.heliosdecompiler.helios.api.events.Events; import com.heliosdecompiler.helios.api.events.PreDecompileEvent; import com.heliosdecompiler.helios.gui.ClickableSyntaxTextArea; import com.heliosdecompiler.helios.transformers.Transformer; import com.heliosdecompiler.helios.transformers.decompilers.CFRDecompiler; import com.heliosdecompiler.helios.transformers.decompilers.Decompiler; import com.heliosdecompiler.helios.transformers.disassemblers.Disassembler; import com.heliosdecompiler.helios.LoadedFile; import com.heliosdecompiler.helios.api.events.requests.SearchRequest; import org.apache.commons.io.output.StringBuilderWriter; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.javatuples.Pair; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; import java.io.ByteArrayInputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; public class DecompileTask implements Runnable { private final String fileName; private final String className; private final String simpleName; private final String packageName; private final ClickableSyntaxTextArea textArea; private final Transformer transformer; private final String jumpTo; private CompilationUnit compilationUnit; private List<Integer> lineSizes = new ArrayList<>(); public DecompileTask(String fileName, String className, ClickableSyntaxTextArea textArea, Transformer transformer, String jumpTo) { this.fileName = fileName; this.className = className; this.textArea = textArea; this.transformer = transformer; this.jumpTo = jumpTo; String simpleName = this.className; int lastIndex; if ((lastIndex = simpleName.lastIndexOf('/')) != -1) { simpleName = simpleName.substring(lastIndex + 1, simpleName.length()); simpleName = simpleName.substring(0, simpleName.length() - 6); } this.simpleName = simpleName; String packageName = this.className; if ((lastIndex = packageName.lastIndexOf('/')) != -1) { packageName = packageName.substring(0, lastIndex); } else { packageName = null; } this.packageName = packageName; } @Override public void run() { LoadedFile loadedFile = Helios.getLoadedFile(fileName); byte[] classFile = loadedFile.getAllData().get(className); PreDecompileEvent event = new PreDecompileEvent(transformer, classFile); Events.callEvent(event); classFile = event.getBytes(); StringBuilder output = new StringBuilder(); if (transformer instanceof Decompiler) { if (((Decompiler) transformer).decompile(loadedFile.getClassNode(className), classFile, output)) { CompilationUnit cu = null; try { cu = JavaParser.parse( new ByteArrayInputStream(output.toString().getBytes(StandardCharsets.UTF_8)), "UTF-8", false); this.compilationUnit = cu; } catch (ParseException | TokenMgrError e) { StringBuilder message = new StringBuilder("/*\n"); Consumer<String> write = msg -> { message.append(" * ").append(msg).append("\n"); }; write.accept("Error: Helios could not parse this file. Hyperlinks will not be inserted"); write.accept("The error has been inserted at the bottom of the output"); if (transformer instanceof CFRDecompiler) { write.accept(""); write.accept("I noticed you are using CFR"); write.accept( "Try nagging the author to output valid Java even if the code is undecompilable"); } message.append(" */\n\n"); output.insert(0, message.toString()); message.setLength(0); message.append("\n/*\n"); StringBuilder exceptionToString = new StringBuilder(); e.printStackTrace(new PrintWriter(new StringBuilderWriter(exceptionToString))); String[] lines = exceptionToString.toString().split("\r*\n"); for (String line : lines) { write.accept(line); } message.append(" */"); output.append(message.toString()); } finally { if (cu != null) { String result = output.toString(); result = result.replaceAll("\r*\n", "\n"); output = new StringBuilder(result); try { handle(cu, output.toString()); } catch (Throwable t) { textArea.links.clear(); StringBuilder message = new StringBuilder("/*\n"); Consumer<String> write = msg -> { message.append(" * ").append(msg).append("\n"); }; write.accept( "Error: Helios could not parse this file. Hyperlinks will not be inserted"); write.accept("The error has been inserted at the bottom of the output"); message.append(" */\n\n"); output.insert(0, message.toString()); message.setLength(0); message.append("\n/*\n"); StringBuilder exceptionToString = new StringBuilder(); t.printStackTrace(new PrintWriter(new StringBuilderWriter(exceptionToString))); String[] lines = exceptionToString.toString().split("\r*\n"); for (String line : lines) { write.accept(line); } message.append(" */"); output.append(message.toString()); t.printStackTrace(); } } } textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); textArea.setCodeFoldingEnabled(true); } textArea.setText(output.toString()); if (jumpTo != null) { Events.callEvent(new SearchRequest(jumpTo, false, false, false, true)); } } else if (transformer instanceof Disassembler) { if (((Disassembler) transformer).disassembleClassNode(loadedFile.getClassNode(className), classFile, output)) { textArea.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_JAVA); textArea.setCodeFoldingEnabled(true); } textArea.setText(output.toString()); } } private void handle(CompilationUnit comp, String output) { for (String s : output.split("\n")) { lineSizes.add(s.length()); } imports: for (ImportDeclaration decl : comp.getImports()) { String fullName = decl.getName().toString(); if (fullName.endsWith("*")) continue; //Ignore wildcard imports String internalName = fullName.replace('.', '/'); Set<LoadedFile> check = new HashSet<>(); check.addAll(Helios.getAllFiles()); check.addAll(Helios.getPathFiles().values()); for (LoadedFile loadedFile : check) { if (loadedFile.getAllData().containsKey(internalName + ".class")) { Pair<Integer, Integer> offsets = getOffsets(lineSizes, decl.getName()); ClickableSyntaxTextArea.Link link = new ClickableSyntaxTextArea.Link( decl.getName().getBeginLine(), decl.getName().getBeginColumn(), offsets.getValue0(), offsets.getValue1()); link.fileName = loadedFile.getName(); link.className = internalName + ".class"; link.jumpTo = ""; textArea.links.add(link); continue imports; } } } comp.accept(new VoidVisitorAdapter<Node>() { @Override public void visit(CompilationUnit n, Node arg) { super.visit(n, n); } @Override public void visit(PackageDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(ImportDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(TypeParameter n, Node arg) { super.visit(n, n); } @Override public void visit(LineComment n, Node arg) { super.visit(n, n); } @Override public void visit(BlockComment n, Node arg) { super.visit(n, n); } @Override public void visit(ClassOrInterfaceDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(EnumDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(EmptyTypeDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(EnumConstantDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(AnnotationDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(AnnotationMemberDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(FieldDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(VariableDeclarator n, Node arg) { super.visit(n, n); } @Override public void visit(VariableDeclaratorId n, Node arg) { super.visit(n, n); } @Override public void visit(ConstructorDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(MethodDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(Parameter n, Node arg) { super.visit(n, n); } @Override public void visit(MultiTypeParameter n, Node arg) { super.visit(n, n); } @Override public void visit(EmptyMemberDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(InitializerDeclaration n, Node arg) { super.visit(n, n); } @Override public void visit(JavadocComment n, Node arg) { super.visit(n, n); } @Override public void visit(PrimitiveType n, Node arg) { super.visit(n, n); } @Override public void visit(ReferenceType n, Node arg) { super.visit(n, n); } @Override public void visit(VoidType n, Node arg) { super.visit(n, n); } @Override public void visit(WildcardType n, Node arg) { super.visit(n, n); } @Override public void visit(UnknownType n, Node arg) { super.visit(n, n); } @Override public void visit(ArrayAccessExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ArrayCreationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ArrayInitializerExpr n, Node arg) { super.visit(n, n); } @Override public void visit(AssignExpr n, Node arg) { super.visit(n, n); } @Override public void visit(BinaryExpr n, Node arg) { super.visit(n, n); } @Override public void visit(CastExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ClassExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ConditionalExpr n, Node arg) { super.visit(n, n); } @Override public void visit(EnclosedExpr n, Node arg) { super.visit(n, n); } @Override public void visit(FieldAccessExpr n, Node arg) { super.visit(n, n); } @Override public void visit(InstanceOfExpr n, Node arg) { super.visit(n, n); } @Override public void visit(StringLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(IntegerLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(LongLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(IntegerLiteralMinValueExpr n, Node arg) { super.visit(n, n); } @Override public void visit(LongLiteralMinValueExpr n, Node arg) { super.visit(n, n); } @Override public void visit(CharLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(DoubleLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(BooleanLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(NullLiteralExpr n, Node arg) { super.visit(n, n); } @Override public void visit(MethodCallExpr n, Node arg) { recursivelyHandleNameExpr(n, n.getNameExpr(), 0); visit(n.getNameExpr(), n); super.visit(n, n); } @Override public void visit(ClassOrInterfaceType n, Node arg) { handleClassOrInterfaceType(n); super.visit(n, n); } @Override public void visit(NameExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ObjectCreationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(QualifiedNameExpr n, Node arg) { super.visit(n, n); } @Override public void visit(ThisExpr n, Node arg) { super.visit(n, n); } @Override public void visit(SuperExpr n, Node arg) { super.visit(n, n); } @Override public void visit(UnaryExpr n, Node arg) { super.visit(n, n); } @Override public void visit(VariableDeclarationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(MarkerAnnotationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(SingleMemberAnnotationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(NormalAnnotationExpr n, Node arg) { super.visit(n, n); } @Override public void visit(MemberValuePair n, Node arg) { super.visit(n, n); } @Override public void visit(ExplicitConstructorInvocationStmt n, Node arg) { super.visit(n, n); } @Override public void visit(TypeDeclarationStmt n, Node arg) { super.visit(n, n); } @Override public void visit(AssertStmt n, Node arg) { super.visit(n, n); } @Override public void visit(BlockStmt n, Node arg) { super.visit(n, n); } @Override public void visit(LabeledStmt n, Node arg) { super.visit(n, n); } @Override public void visit(EmptyStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ExpressionStmt n, Node arg) { super.visit(n, n); } @Override public void visit(SwitchStmt n, Node arg) { super.visit(n, n); } @Override public void visit(SwitchEntryStmt n, Node arg) { super.visit(n, n); } @Override public void visit(BreakStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ReturnStmt n, Node arg) { super.visit(n, n); } @Override public void visit(IfStmt n, Node arg) { super.visit(n, n); } @Override public void visit(WhileStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ContinueStmt n, Node arg) { super.visit(n, n); } @Override public void visit(DoStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ForeachStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ForStmt n, Node arg) { super.visit(n, n); } @Override public void visit(ThrowStmt n, Node arg) { super.visit(n, n); } @Override public void visit(SynchronizedStmt n, Node arg) { super.visit(n, n); } @Override public void visit(TryStmt n, Node arg) { super.visit(n, n); } @Override public void visit(CatchClause n, Node arg) { super.visit(n, n); } @Override public void visit(LambdaExpr n, Node arg) { super.visit(n, n); } @Override public void visit(MethodReferenceExpr n, Node arg) { super.visit(n, n); } @Override public void visit(TypeExpr n, Node arg) { super.visit(n, n); } }, null); } private void print(int d, String msg) { String space = ""; for (int i = 0; i < d; i++) { space += " "; } System.out.println(space + msg); } private List<String> generatePossibilities(ClassOrInterfaceType type) { List<String> possibilities = new ArrayList<>(); /* * This is the ClassOrInterfaceType as the entire string. * Eg. java.lang.System or System */ String fullName = type.toString(); /* * This will only be one word. * Eg. System */ String simpleName = type.getName(); /* * If the fullName is something like Outer.Inner, asInnerClass will be Outer$Inner * * However, if the fullName is a FQN, then asInnerClass will not be valid * Eg, java$lang$System */ String asInnerClass = fullName.replace('.', '$'); for (ImportDeclaration importDeclaration : compilationUnit.getImports()) { if (importDeclaration.isAsterisk()) { String fullImport = importDeclaration.getName().toString(); String internalName = fullImport.replace('.', '/'); possibilities.add(internalName + "/" + asInnerClass + ".class"); } else if (importDeclaration.isStatic()) { } else { NameExpr importName = importDeclaration.getName(); if (importName.getName().equals(simpleName)) { String javaName = importDeclaration.getName().toString(); String internalName = javaName.replace('.', '/'); possibilities.add(internalName + ".class"); } } } /* * We must consider Fully Qualified Names * Therefore the string java.lang.System could be java$lang$System, java.lang$System, or java.lang.System */ String[] split = fullName.split("\\."); for (int i = 0; i < split.length; i++) { StringBuilder builder = new StringBuilder(); for (int start = 0; start < i; start++) { builder.append(split[start]).append("/"); } for (int start = i; start < split.length; start++) { builder.append(split[start]).append("$"); } if (builder.length() > 0 && (builder.charAt(builder.length() - 1) == '$' || builder.charAt(builder.length() - 1) == '/')) { builder.setLength(builder.length() - 1); } builder.append(".class"); possibilities.add(builder.toString()); } return possibilities; } private String recursivelyHandleNameExpr(MethodCallExpr methodCallExpr, NameExpr nameExpr, int depth) { //fixme generics if (methodCallExpr.getNameExpr() != nameExpr) return null; print(depth, "RHNE " + methodCallExpr + " " + nameExpr); print(depth, "Scope is " + ((methodCallExpr.getScope() == null) ? null : methodCallExpr.getScope().getClass()) + " " + methodCallExpr.getScope()); Pair<Integer, Integer> offsets = getOffsets(lineSizes, nameExpr); ClickableSyntaxTextArea.Link link = new ClickableSyntaxTextArea.Link(nameExpr.getBeginLine(), nameExpr.getBeginColumn(), offsets.getValue0(), offsets.getValue1()); Set<String> possibleClassNames = new HashSet<>(); if (methodCallExpr.getScope() instanceof NameExpr || methodCallExpr.getScope() instanceof ArrayAccessExpr) { Node tmp = methodCallExpr.getScope(); if (tmp instanceof ArrayAccessExpr) { ArrayAccessExpr expr = (ArrayAccessExpr) tmp; tmp = expr.getName(); //todo could be other than nameexpr } /* * Cases: * Static method * SomeClass.someStaticMethod() * Variable * myVar.someVirtualMethod() * Field * field.someVirtualMethod() */ Node fnode = tmp; NameExpr scopeExpr = (NameExpr) tmp; String scope = scopeExpr.toString(); if (scope.contains(".")) { throw new IllegalArgumentException("Was not expecting '.' in " + scope); } /* * In Java, variables have priority * Therefore, something like this * * Object Integer = null; * Integer.parseInt("4"); * * would fail */ Node node = methodCallExpr.getParentNode(); List<com.github.javaparser.ast.type.Type> ref = new ArrayList<>(); List<Node> parentChain = new ArrayList<>(); Node tmpNode = node; while (tmpNode != null) { parentChain.add(tmpNode); tmpNode = tmpNode.getParentNode(); } while (ref.size() == 0 && node != null) { print(depth, "Trying to find localvar in " + node.getClass()); node.accept(new VoidVisitorAdapter<Node>() { @Override public void visit(VariableDeclarationExpr n, Node arg) { boolean equals = false; for (VariableDeclarator var : n.getVars()) { if (var.getId().getName().equals(scopeExpr.getName())) { equals = true; } } if (equals) { print(depth, "Found VariableDeclarationExpr " + n); print(depth, "This is it! Type is " + n.getType()); ref.add(n.getType()); } super.visit(n, n); } @Override public void visit(MultiTypeParameter n, Node arg) { if (n.getId().getName().equals(((NameExpr) fnode).getName())) { print(depth, "Found VariableDeclarationExpr " + n); print(depth, "This is it! Type is " + n.getType()); ref.addAll(n.getType().getElements()); } } @Override public void visit(Parameter n, Node arg) { if (n.getId().getName().equals(((NameExpr) fnode).getName())) { print(depth, "Found Parameter " + n); print(depth, "This is it! Type is " + n.getType()); ref.add(n.getType()); } } @Override public void visit(BlockStmt n, Node arg) { if (parentChain.contains(n)) { super.visit(n, n); } } }, null); if (node instanceof BodyDeclaration) { // We don't want to check for variables outside of this method. That would be a field break; } node = node.getParentNode(); } if (ref.size() > 0) { if (ref.size() > 1) { throw new IllegalArgumentException("Was not expecting more than one localvar " + ref); } com.github.javaparser.ast.type.Type type = ref.get(0); //fixme check all while (type instanceof ReferenceType) { type = ((ReferenceType) type).getType(); } print(depth, "Final type is " + type.getClass() + " " + type); if (type instanceof ClassOrInterfaceType) { ClassOrInterfaceType coit = (ClassOrInterfaceType) type; possibleClassNames.addAll(generatePossibilities(coit)); possibleClassNames.add("java/lang/" + coit.getName() + ".class"); if (packageName != null) { possibleClassNames.add(packageName + "/" + coit.getName() + ".class"); } } else { throw new IllegalArgumentException("Got unexpected type " + type.getClass()); } } /* * Check for static method invocation * If this class was called "Test" we want to check for * * Test.staticMethod(); */ print(depth, "Simple name is " + simpleName); if (scopeExpr.getName().equals(simpleName)) { possibleClassNames.add(this.className); } /* * Finally, check imports */ for (ImportDeclaration importDeclaration : compilationUnit.getImports()) { if (importDeclaration.isAsterisk()) { String fullImport = importDeclaration.getName().toString(); String internalName = fullImport.replace('.', '/'); possibleClassNames.add(internalName + "/" + scope + ".class"); } else if (importDeclaration.isStatic()) { } else { NameExpr importName = importDeclaration.getName(); if (importName.getName().equals(scope)) { String javaName = importDeclaration.getName().toString(); String internalName = javaName.replace('.', '/'); possibleClassNames.add(internalName + ".class"); } } } /* * java.lang.* classes don't need to be imported * Add it just in case */ possibleClassNames.add("java/lang/" + scope + ".class"); FieldAccessExpr expr = new FieldAccessExpr(null, scope); Set<String> owners = handleFieldExpr(expr, className, depth); possibleClassNames.addAll(owners); /* * Classes in the current package don't need to be imported * Add it just in case */ if (packageName != null) { possibleClassNames.add(packageName + "/" + scope + ".class"); } } else if (methodCallExpr.getScope() instanceof MethodCallExpr) { /* * Recursively handle the chained method. The return should be the class name we want */ possibleClassNames.add(recursivelyHandleNameExpr((MethodCallExpr) methodCallExpr.getScope(), ((MethodCallExpr) methodCallExpr.getScope()).getNameExpr(), depth + 1)); } else if (methodCallExpr.getScope() == null) { /* * Another way of calling a static/virtual method within the same class. * * someStaticMethod(); */ possibleClassNames.add(this.className); } else if (methodCallExpr.getScope() instanceof ThisExpr) { /* * Another way of calling a static/virtual method within the same class * * this.someVirtualMethod(); * * fixme what about Outer.this.method(); */ possibleClassNames.add(this.className); } else if (methodCallExpr.getScope() instanceof SuperExpr) { /* * Calling a super method * * super.someVirtualMethod(); */ LoadedFile loadedFile = Helios.getLoadedFile(fileName); ClassNode node = loadedFile.getClassNode(this.className); possibleClassNames.add(node.superName); } else if (methodCallExpr.getScope() instanceof EnclosedExpr) { /* * fixme We could be missing CastExprs elsewhere but it's unlikely * * EnclosedExpr represents an expression surrounded by brackets * It's assumed that there may be a cast within * * ((String) obj).toCharArray(); */ EnclosedExpr enclosedExpr = (EnclosedExpr) methodCallExpr.getScope(); if (enclosedExpr.getInner() instanceof CastExpr) { CastExpr castExpr = (CastExpr) enclosedExpr.getInner(); com.github.javaparser.ast.type.Type type = castExpr.getType(); while (type instanceof ReferenceType) { type = ((ReferenceType) type).getType(); } if (type instanceof ClassOrInterfaceType) { ClassOrInterfaceType coit = (ClassOrInterfaceType) type; possibleClassNames.addAll(handleClassOrInterfaceType(coit)); } else { throw new IllegalArgumentException("Got unexpected type " + type.getClass()); } } } else if (methodCallExpr.getScope() instanceof FieldAccessExpr) { // Handle fields /* * Could either be a field OR a FQN * * System.out.println(); -> System.out is the FieldAccessExpr * * java.lang.System.out.println(); -> java.lang.System.out is the FieldAccessExpr */ FieldAccessExpr expr = (FieldAccessExpr) methodCallExpr.getScope(); String left = expr.getScope().toString(); Set<String> possible; if (left.equals("this")) { possible = new HashSet<>(); possible.add(className); } else { ClassOrInterfaceType type = new ClassOrInterfaceType(left); type.setBeginLine(expr.getScope().getBeginLine()); type.setEndLine(expr.getScope().getEndLine()); type.setBeginColumn(expr.getScope().getBeginColumn()); type.setEndColumn(expr.getScope().getEndColumn()); possible = handleClassOrInterfaceType(type); } if (possible.size() > 0) { // Maybe field print(depth, "FieldAccessExpr field: " + expr.getScope() + " " + expr.getField() + " " + expr.getScope().getClass() + " " + possible); for (String p : possible) { Set<String> types = handleFieldExpr(expr, p, depth); possibleClassNames.addAll(types); } } else { ClassOrInterfaceType type = new ClassOrInterfaceType(expr.toString()); type.setBeginLine(expr.getBeginLine()); type.setEndLine(expr.getEndLine()); type.setBeginColumn(expr.getBeginColumn()); type.setEndColumn(expr.getEndColumn()); possible = handleClassOrInterfaceType(type); if (possible.size() == 0) { print(depth, "Error: Could not parse FieldAccessExpr"); } else { print(depth, "FieldAccessExpr fqn: " + expr.getScope() + " " + expr.getField() + " " + expr.getScope().getClass() + " " + possible); possibleClassNames.addAll(possible); } } } else if (methodCallExpr.getScope() instanceof ArrayAccessExpr) { /* * somearray[index].method() */ } else if (methodCallExpr.getScope() instanceof ObjectCreationExpr) { /* * new Object().method() */ ObjectCreationExpr objectCreationExpr = (ObjectCreationExpr) methodCallExpr.getScope(); possibleClassNames.addAll(handleClassOrInterfaceType(objectCreationExpr.getType())); } print(depth, possibleClassNames.toString()); Map<String, LoadedFile> mapping = possibleClassNames.stream() .map(name -> new AbstractMap.SimpleEntry<>(name, getFileFor(name))) .filter(ent -> ent.getValue() != null) .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); if (mapping.size() == 0) { print(depth, "Error: Could not find classname"); } else if (mapping.size() > 1) { print(depth, "Error: More than one classname found: " + mapping.keySet()); //fixme filter by which one contains the method } else { print(depth, "ClassName is " + mapping.keySet()); String className = mapping.keySet().iterator().next(); String internalName = className.substring(0, className.length() - 6); try { while (true) { LoadedFile readFrom = null; String fileName = internalName + ".class"; LoadedFile file = Helios.getLoadedFile(this.fileName); if (file.getAllData().get(fileName) != null) { readFrom = file; } else { Set<LoadedFile> check = new HashSet<>(); check.addAll(Helios.getAllFiles()); check.addAll(Helios.getPathFiles().values()); for (LoadedFile loadedFile : check) { if (loadedFile.getAllData().get(fileName) != null) { readFrom = loadedFile; break; } } } if (readFrom != null) { print(depth, "Found in " + readFrom.getName()); link.fileName = readFrom.getName(); link.className = fileName; link.jumpTo = " " + nameExpr.getName() + "("; textArea.links.add(link); ClassNode classNode = readFrom.getEmptyClasses().get(internalName); print(depth, "Looking for method with name " + methodCallExpr.getName() + " in " + internalName + " " + classNode); MethodNode node = classNode.methods.stream() .filter(mn -> mn.name.equals(methodCallExpr.getName())).findFirst().orElse(null); if (node != null) { link.className = internalName + ".class"; Type returnType = Type.getType(node.desc); if (returnType.getReturnType().getSort() == Type.OBJECT) { print(depth, "Found method with return type " + returnType); return returnType.getReturnType().getInternalName() + ".class"; } else if (returnType.getReturnType().getSort() == Type.ARRAY) { return "java/lang/Object.class"; } else { return null; } } else { print(depth, "Could not find methodnode " + methodCallExpr.getName()); } if (internalName.equals("java/lang/Object")) { break; } internalName = classNode.superName; } else { print(depth, "Could not find readfrom "); break; } } } catch (Exception e) { e.printStackTrace(System.out); } } return null; } private Map<ClassOrInterfaceType, Set<String>> handled = new IdentityHashMap<>(); private Map<ClassOrInterfaceType, String> tostring = new HashMap<>(); private Function<ClassOrInterfaceType, String> toStringComputer = type -> { StringBuilder fullNameBuilder = new StringBuilder(); while (type != null) { fullNameBuilder.insert(0, "."); fullNameBuilder.insert(0, type.getName()); type = type.getScope(); } fullNameBuilder.setLength(fullNameBuilder.length() - 1); return fullNameBuilder.toString(); }; private Set<String> handleFieldExpr(FieldAccessExpr fieldAccessExpr, String owner, int depth) { Set<String> types = new HashSet<>(); Pair<Integer, Integer> offsets = getOffsets(lineSizes, fieldAccessExpr); ClickableSyntaxTextArea.Link link = new ClickableSyntaxTextArea.Link(fieldAccessExpr.getBeginLine(), fieldAccessExpr.getBeginColumn(), offsets.getValue0(), offsets.getValue1()); String className = owner; String internalName = className.substring(0, className.length() - 6); try { while (true) { LoadedFile readFrom = null; String fileName = internalName + ".class"; LoadedFile file = Helios.getLoadedFile(this.fileName); if (file.getAllData().get(fileName) != null) { readFrom = file; } else { Set<LoadedFile> check = new HashSet<>(); check.addAll(Helios.getAllFiles()); check.addAll(Helios.getPathFiles().values()); for (LoadedFile loadedFile : check) { if (loadedFile.getAllData().get(fileName) != null) { readFrom = loadedFile; break; } } } if (readFrom != null) { print(depth, "Found in " + readFrom.getName()); link.fileName = readFrom.getName(); link.className = fileName; link.jumpTo = " " + fieldAccessExpr.getField(); textArea.links.add(link); ClassNode classNode = readFrom.getEmptyClasses().get(internalName); print(depth, "Looking for field with name " + fieldAccessExpr.getField() + " in " + internalName + " " + classNode); List<FieldNode> fields = classNode.fields.stream() .filter(f -> f.name.equals(fieldAccessExpr.getField())).collect(Collectors.toList()); if (fields.size() > 0) { link.className = internalName + ".class"; for (FieldNode fieldNode : fields) { Type type = Type.getType(fieldNode.desc); types.add(type.getInternalName() + ".class"); } return types; } else { print(depth, "Could not find field " + fieldAccessExpr.getField()); } if (internalName.equals("java/lang/Object")) { break; } internalName = classNode.superName; } else { print(depth, "Could not find readfrom "); break; } } } catch (Exception e) { e.printStackTrace(System.out); } return types; } private Set<String> handleClassOrInterfaceType(ClassOrInterfaceType classOrInterfaceType) { Set<String> result = handled.get(classOrInterfaceType); if (result != null) { return result; } result = new HashSet<>(); handled.put(classOrInterfaceType, result); System.out.println("Handling ClassOrInterfaceType " + classOrInterfaceType + " on line " + classOrInterfaceType.getBeginLine()); /* * Possibilities: * Simple name: System * -> Could be imported direcly * -> Could be imported using wildcard * -> Could be package-local * -> Could be java.lang * Inner class (Java): System.Inner * Inner class (Internal): System$Inner * Fully Qualified Name (Java): java.lang.System * Fully Qualified Name with inner (Java): java.lang.System.Inner * Fully Qualified Name with inner (internal): java.lang.System$Inner */ Pair<Integer, Integer> offsets = getOffsets(lineSizes, classOrInterfaceType); ClickableSyntaxTextArea.Link link = new ClickableSyntaxTextArea.Link(classOrInterfaceType.getBeginLine(), classOrInterfaceType.getBeginColumn(), offsets.getValue0(), offsets.getValue1()); /* Could be any of the above possibilities */ String fullName = tostring.computeIfAbsent(classOrInterfaceType, toStringComputer); Set<String> allPossibilitiesWithoutImports = new HashSet<>(); String[] split = fullName.split("\\."); for (int i = 0; i < split.length; i++) { StringBuilder builder = new StringBuilder(); for (int start = 0; start < i; start++) { builder.append(split[start]).append("/"); } for (int start = i; start < split.length; start++) { builder.append(split[start]).append("$"); } if (builder.length() > 0 && (builder.charAt(builder.length() - 1) == '$' || builder.charAt(builder.length() - 1) == '/')) { builder.setLength(builder.length() - 1); } builder.append(".class"); allPossibilitiesWithoutImports.add(builder.toString()); } Set<String> possibleClassNames = new HashSet<>(); possibleClassNames.addAll(allPossibilitiesWithoutImports); for (ImportDeclaration importDeclaration : compilationUnit.getImports()) { String javaName = importDeclaration.getName().toString(); if (importDeclaration.isAsterisk()) { String fullImport = importDeclaration.getName().toString(); String internalName = fullImport.replace('.', '/'); for (String name : allPossibilitiesWithoutImports) { possibleClassNames.add(internalName + "/" + name); } } else if (importDeclaration.isStatic()) { } else { for (String name : allPossibilitiesWithoutImports) { String nameWithoutClass = name.substring(0, name.length() - 6); String simple = nameWithoutClass; int index; if ((index = simple.indexOf('.')) != -1) { simple = simple.substring(0, index); if (importDeclaration.getName().getName().equals(simple)) { possibleClassNames.add(javaName.replace('.', '/') + "$" + simple.substring(index + 1).replace('.', '$') + ".class"); } } if (importDeclaration.getName().getName().equals(nameWithoutClass)) { possibleClassNames.add(javaName.replace('.', '/') + ".class"); } } } } possibleClassNames.add("java/lang/" + classOrInterfaceType.getName() + ".class"); if (packageName != null) { possibleClassNames.add(packageName + "/" + classOrInterfaceType.getName() + ".class"); } System.out.println(possibleClassNames); Map<String, LoadedFile> mapping = possibleClassNames.stream() .map(name -> new AbstractMap.SimpleEntry<>(name, getFileFor(name))) .filter(ent -> ent.getValue() != null) .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); System.out.println("Result: " + mapping.keySet()); if (mapping.size() == 0) { System.out.println("ERROR: Could not find file which contains " + possibleClassNames); } else if (mapping.size() > 1) { System.out.println("ERROR: Multiple results: " + mapping.keySet()); } else { Map.Entry<String, LoadedFile> entry = mapping.entrySet().iterator().next(); link.fileName = entry.getValue().getName(); link.className = entry.getKey(); link.jumpTo = " " + classOrInterfaceType.getName() + " "; textArea.links.add(link); } result.addAll(mapping.keySet()); return result; } private LoadedFile getFileFor(String fileName) { LoadedFile file = Helios.getLoadedFile(this.fileName); if (file.getAllData().get(fileName) != null) { return file; } else { Set<LoadedFile> check = new HashSet<>(); check.addAll(Helios.getAllFiles()); check.addAll(Helios.getPathFiles().values()); for (LoadedFile loadedFile : check) { if (loadedFile.getAllData().get(fileName) != null) { return loadedFile; } } } return null; } private Pair<Integer, Integer> getOffsets(List<Integer> lineSizes, Node node) { int offset = 0; for (int i = 0; i < node.getBeginLine() - 1; i++) { offset += lineSizes.get(i) + 1; } offset += node.getBeginColumn() - 1; int offsetEnd = 0; for (int i = 0; i < node.getEndLine() - 1; i++) { offsetEnd += lineSizes.get(i) + 1; } offsetEnd += node.getEndColumn() - 1; return new Pair<>(offset, offsetEnd); } }