diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/FernFlowerDecompiler.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/FernFlowerDecompiler.java index b16b9c95d..71ca66b93 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/FernFlowerDecompiler.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/FernFlowerDecompiler.java @@ -22,17 +22,23 @@ import org.apache.commons.lang3.ArrayUtils; import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InnerClassNode; import the.bytecode.club.bytecodeviewer.BytecodeViewer; import the.bytecode.club.bytecodeviewer.Constants; +import the.bytecode.club.bytecodeviewer.api.ASMUtil; import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.decompilers.AbstractDecompiler; import the.bytecode.club.bytecodeviewer.resources.ExternalResources; +import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings; import the.bytecode.club.bytecodeviewer.util.ExceptionUtils; import the.bytecode.club.bytecodeviewer.util.ProcessUtils; import the.bytecode.club.bytecodeviewer.util.TempFile; import java.io.*; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; import static the.bytecode.club.bytecodeviewer.Constants.*; import static the.bytecode.club.bytecodeviewer.translation.TranslatedStrings.*; @@ -51,12 +57,67 @@ public FernFlowerDecompiler() super("FernFlower Decompiler", "fernflower"); } + private String[] inners; + private final List innerFiles = new ArrayList<>(); + @Override public String decompileClassNode(ClassNode cn, byte[] bytes) { TempFile tempFile = null; String exception; + List innerClasses = cn.innerClasses; + List innerTempFiles = new ArrayList<>(); + AtomicReference innerTempFile = new AtomicReference<>(); + if (BytecodeViewer.viewer.din.isSelected()) + { + inners = new String[innerClasses.size()]; + for (int i = 0; i < innerClasses.size(); i++) + { + if (innerClasses.get(i).outerName != null && innerClasses.get(i).outerName.equals(cn.name)) + { + inners[i] = innerClasses.get(i).name; + } + else if (innerClasses.get(i).outerName == null) + { + String name = innerClasses.get(i).name; + name = name.substring(name.lastIndexOf('/') + 1); + if (name.contains(cn.name.substring(cn.name.lastIndexOf('/') + 1))) + { + inners[i] = innerClasses.get(i).name; + } + } + } + + for (ResourceContainer container : BytecodeViewer.resourceContainers.values()) + { + container.resourceClasses.forEach((s, classNode) -> { + for (String innerClassName : inners) + { + if (s.equals(innerClassName)) + { + innerTempFile.set(TempFile.createTemporaryFile(true, ".class")); + File tempInputClassFile2 = innerTempFile.get().getFile(); + try (FileOutputStream fos = new FileOutputStream(tempInputClassFile2)) + { + fos.write(ASMUtil.nodeToBytes(classNode)); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + finally + { + innerFiles.add(tempInputClassFile2); + innerTempFile.get().markAsCreatedFile(tempInputClassFile2); + innerTempFiles.add(innerTempFile.get()); + } + } + } + }); + } + } + try { //create the temporary files @@ -85,7 +146,12 @@ public String decompileClassNode(ClassNode cn, byte[] bytes) } else { - org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler.main(generateMainMethod(tempInputClassFile.getAbsolutePath(), new File(TEMP_DIRECTORY).getAbsolutePath())); + List strings = generate(tempInputClassFile.getAbsolutePath(), + new File(TEMP_DIRECTORY).getAbsolutePath()); + + String[] args = strings.toArray(new String[0]); + + org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler.main(args); } //if rename is enabled the file name will be the actual class name @@ -110,8 +176,23 @@ public String decompileClassNode(ClassNode cn, byte[] bytes) finally { //cleanup temp files - if(tempFile != null) + if (tempFile != null) tempFile.cleanup(); + + if (innerTempFile.get() != null) + innerTempFile.get().cleanup(); + + for (TempFile file : innerTempFiles) + { + file.cleanup(); + File file1 = new File(TEMP_DIRECTORY + file.getUniqueName() + ".java"); + if (file1.exists()) + { + file1.delete(); + } + } + + innerFiles.clear(); } return FERNFLOWER + " " + ERROR + "! " + ExceptionUI.SEND_STACKTRACE_TO + NL + NL @@ -140,31 +221,64 @@ public void decompileToZip(String sourceJar, String zipName) } + private List generate(String className, String folder) + { + List strings = new ArrayList<>(); + strings.add("-rbr=" + ffOnValue(BytecodeViewer.viewer.rbr.isSelected())); + strings.add("-rsy=" + ffOnValue(BytecodeViewer.viewer.rsy.isSelected())); + strings.add("-din=" + ffOnValue(BytecodeViewer.viewer.din.isSelected())); + strings.add("-dc4=" + ffOnValue(BytecodeViewer.viewer.dc4.isSelected())); + strings.add("-das=" + ffOnValue(BytecodeViewer.viewer.das.isSelected())); + strings.add("-hes=" + ffOnValue(BytecodeViewer.viewer.hes.isSelected())); + strings.add("-hdc=" + ffOnValue(BytecodeViewer.viewer.hdc.isSelected())); + strings.add("-dgs=" + ffOnValue(BytecodeViewer.viewer.dgs.isSelected())); + strings.add("-ner=" + ffOnValue(BytecodeViewer.viewer.ner.isSelected())); + strings.add("-den=" + ffOnValue(BytecodeViewer.viewer.den.isSelected())); + strings.add("-rgn=" + ffOnValue(BytecodeViewer.viewer.rgn.isSelected())); + strings.add("-bto=" + ffOnValue(BytecodeViewer.viewer.bto.isSelected())); + strings.add("-nns=" + ffOnValue(BytecodeViewer.viewer.nns.isSelected())); + strings.add("-uto=" + ffOnValue(BytecodeViewer.viewer.uto.isSelected())); + strings.add("-udv=" + ffOnValue(BytecodeViewer.viewer.udv.isSelected())); + strings.add("-rer=" + ffOnValue(BytecodeViewer.viewer.rer.isSelected())); + strings.add("-fdi=" + ffOnValue(BytecodeViewer.viewer.fdi.isSelected())); + strings.add("-asc=" + ffOnValue(BytecodeViewer.viewer.asc.isSelected())); + strings.add("-ren=" + ffOnValue(BytecodeViewer.viewer.ren.isSelected())); + strings.add(className); + if (BytecodeViewer.viewer.din.isSelected()) + { + for (File file : innerFiles) + strings.add(file.getAbsolutePath()); + } + + strings.add(folder); + return strings; + } + private String[] generateMainMethod(String className, String folder) { return new String[] - { - "-rbr=" + ffOnValue(BytecodeViewer.viewer.rbr.isSelected()), - "-rsy=" + ffOnValue(BytecodeViewer.viewer.rsy.isSelected()), - "-din=" + ffOnValue(BytecodeViewer.viewer.din.isSelected()), - "-dc4=" + ffOnValue(BytecodeViewer.viewer.dc4.isSelected()), - "-das=" + ffOnValue(BytecodeViewer.viewer.das.isSelected()), - "-hes=" + ffOnValue(BytecodeViewer.viewer.hes.isSelected()), - "-hdc=" + ffOnValue(BytecodeViewer.viewer.hdc.isSelected()), - "-dgs=" + ffOnValue(BytecodeViewer.viewer.dgs.isSelected()), - "-ner=" + ffOnValue(BytecodeViewer.viewer.ner.isSelected()), - "-den=" + ffOnValue(BytecodeViewer.viewer.den.isSelected()), - "-rgn=" + ffOnValue(BytecodeViewer.viewer.rgn.isSelected()), - "-bto=" + ffOnValue(BytecodeViewer.viewer.bto.isSelected()), - "-nns=" + ffOnValue(BytecodeViewer.viewer.nns.isSelected()), - "-uto=" + ffOnValue(BytecodeViewer.viewer.uto.isSelected()), - "-udv=" + ffOnValue(BytecodeViewer.viewer.udv.isSelected()), - "-rer=" + ffOnValue(BytecodeViewer.viewer.rer.isSelected()), - "-fdi=" + ffOnValue(BytecodeViewer.viewer.fdi.isSelected()), - "-asc=" + ffOnValue(BytecodeViewer.viewer.asc.isSelected()), - "-ren=" + ffOnValue(BytecodeViewer.viewer.ren.isSelected()), - className, folder - }; + { + "-rbr=" + ffOnValue(BytecodeViewer.viewer.rbr.isSelected()), + "-rsy=" + ffOnValue(BytecodeViewer.viewer.rsy.isSelected()), + "-din=" + ffOnValue(BytecodeViewer.viewer.din.isSelected()), + "-dc4=" + ffOnValue(BytecodeViewer.viewer.dc4.isSelected()), + "-das=" + ffOnValue(BytecodeViewer.viewer.das.isSelected()), + "-hes=" + ffOnValue(BytecodeViewer.viewer.hes.isSelected()), + "-hdc=" + ffOnValue(BytecodeViewer.viewer.hdc.isSelected()), + "-dgs=" + ffOnValue(BytecodeViewer.viewer.dgs.isSelected()), + "-ner=" + ffOnValue(BytecodeViewer.viewer.ner.isSelected()), + "-den=" + ffOnValue(BytecodeViewer.viewer.den.isSelected()), + "-rgn=" + ffOnValue(BytecodeViewer.viewer.rgn.isSelected()), + "-bto=" + ffOnValue(BytecodeViewer.viewer.bto.isSelected()), + "-nns=" + ffOnValue(BytecodeViewer.viewer.nns.isSelected()), + "-uto=" + ffOnValue(BytecodeViewer.viewer.uto.isSelected()), + "-udv=" + ffOnValue(BytecodeViewer.viewer.udv.isSelected()), + "-rer=" + ffOnValue(BytecodeViewer.viewer.rer.isSelected()), + "-fdi=" + ffOnValue(BytecodeViewer.viewer.fdi.isSelected()), + "-asc=" + ffOnValue(BytecodeViewer.viewer.asc.isSelected()), + "-ren=" + ffOnValue(BytecodeViewer.viewer.ren.isSelected()), + className, folder + }; } private String ffOnValue(boolean b) diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/JDGUIDecompiler.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/JDGUIDecompiler.java index 4395bc91a..78951852b 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/JDGUIDecompiler.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/impl/JDGUIDecompiler.java @@ -19,20 +19,26 @@ package the.bytecode.club.bytecodeviewer.decompilers.impl; import com.konloch.disklib.DiskReader; +import org.apache.commons.io.FilenameUtils; import org.jd.core.v1.ClassFileToJavaSourceDecompiler; import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InnerClassNode; +import the.bytecode.club.bytecodeviewer.BytecodeViewer; import the.bytecode.club.bytecodeviewer.Constants; +import the.bytecode.club.bytecodeviewer.api.ASMUtil; import the.bytecode.club.bytecodeviewer.api.ExceptionUI; import the.bytecode.club.bytecodeviewer.decompilers.AbstractDecompiler; import the.bytecode.club.bytecodeviewer.decompilers.jdgui.CommonPreferences; import the.bytecode.club.bytecodeviewer.decompilers.jdgui.DirectoryLoader; import the.bytecode.club.bytecodeviewer.decompilers.jdgui.JDGUIClassFileUtil; import the.bytecode.club.bytecodeviewer.decompilers.jdgui.PlainTextPrinter; +import the.bytecode.club.bytecodeviewer.resources.ResourceContainer; import the.bytecode.club.bytecodeviewer.translation.TranslatedStrings; import the.bytecode.club.bytecodeviewer.util.ExceptionUtils; import the.bytecode.club.bytecodeviewer.util.TempFile; import java.io.*; +import java.util.List; import static the.bytecode.club.bytecodeviewer.Constants.FS; import static the.bytecode.club.bytecodeviewer.Constants.NL; @@ -59,6 +65,26 @@ public String decompileClassNode(ClassNode cn, byte[] bytes) TempFile tempFile = null; String exception; + List innerClasses = cn.innerClasses; + String[] inners = new String[innerClasses.size()]; + for (int i = 0; i < innerClasses.size(); i++) + { + if (innerClasses.get(i).name.equals(cn.name)) + break; + + if (innerClasses.get(i).outerName != null && innerClasses.get(i).outerName.equals(cn.name)) + { + inners[i] = innerClasses.get(i).name; + } + else if (innerClasses.get(i).outerName == null) + { + String name = innerClasses.get(i).name; + name = name.substring(name.lastIndexOf('/') + 1); + if (name.contains(cn.name.substring(cn.name.lastIndexOf('/') + 1))) + inners[i] = innerClasses.get(i).name; + } + } + try { //create the temporary files @@ -75,9 +101,33 @@ public String decompileClassNode(ClassNode cn, byte[] bytes) fos.write(bytes); } + // create the inner class temp files + File innerTempFile; + for (ResourceContainer container : BytecodeViewer.resourceContainers.values()) + { + for (String s : container.resourceClasses.keySet()) + { + for (String innerClassName : inners) + { + if (s.equals(innerClassName)) + { + ClassNode cn2 = container.resourceClasses.get(innerClassName); + tempFile.setUniqueName(cn2.name); + innerTempFile = tempFile.createFileFromExtension(false, false, ".class"); + try (FileOutputStream fos = new FileOutputStream(innerTempFile)) + { + fos.write(ASMUtil.nodeToBytes(cn2)); + } + } + } + } + } + String pathToClass = tempClassFile.getAbsolutePath().replace('/', File.separatorChar).replace('\\', File.separatorChar); String directoryPath = JDGUIClassFileUtil.ExtractDirectoryPath(pathToClass); - String internalPath = JDGUIClassFileUtil.ExtractInternalPath(directoryPath, pathToClass); + String internalPath = FilenameUtils.removeExtension(JDGUIClassFileUtil.ExtractInternalPath(directoryPath, + pathToClass)); + CommonPreferences preferences = new CommonPreferences() { diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/jdgui/DirectoryLoader.java b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/jdgui/DirectoryLoader.java index 882b56521..ba5a64ba4 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/jdgui/DirectoryLoader.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/decompilers/jdgui/DirectoryLoader.java @@ -46,6 +46,9 @@ public DirectoryLoader(File file) throws LoaderException @Override public byte[] load(String internalPath) throws LoaderException { + if (!internalPath.endsWith(".class")) + internalPath = internalPath + ".class"; + File file = new File(this.codebase, internalPath); try (FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis)) @@ -61,7 +64,7 @@ public byte[] load(String internalPath) throws LoaderException @Override public boolean canLoad(String internalPath) { - File file = new File(this.codebase, internalPath); + File file = new File(this.codebase, internalPath + ".class"); return file.exists() && file.isFile(); } } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/MyErrorStripe.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/MyErrorStripe.java index 5f9deff71..58928c844 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/MyErrorStripe.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/MyErrorStripe.java @@ -86,7 +86,7 @@ private int yToLine(int y) if (y < h) { float at = y / (float) h; - line = Math.round((Math.max(lineCount, linesPerVisibleRect) - 1) * at); + line = Math.round((float) (Math.max(lineCount, linesPerVisibleRect) - 1) * at); } return line; @@ -199,7 +199,6 @@ public void removeNotify() private class Listener extends MouseAdapter { - private final Rectangle r = new Rectangle(); @Override public void mouseClicked(@NotNull MouseEvent e) @@ -219,7 +218,7 @@ public void mouseClicked(@NotNull MouseEvent e) { try { - int offset = textArea.getLineOfOffset(line); + int offset = textArea.getLineStartOffset(line); textArea.setCaretPosition(offset); RSyntaxUtilities.selectAndPossiblyCenter(textArea, new DocumentRange(offset, offset), false); } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/actions/GoToAction.java b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/actions/GoToAction.java index 23b5409e6..ff728c7ea 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/actions/GoToAction.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/gui/components/actions/GoToAction.java @@ -53,7 +53,7 @@ public void actionPerformed(ActionEvent e) // Open the class that is associated with the field's owner. if (!field.owner.equals(container.getName())) { - open(textArea, false, true, false); + find(textArea, false, true, false); return; } @@ -136,7 +136,7 @@ public void actionPerformed(ActionEvent e) } }); - open(textArea, false, false, true); + find(textArea, false, false, true); } } })); @@ -166,7 +166,7 @@ public void actionPerformed(ActionEvent e) }); // Should not really do anything when the class is already open - open(textArea, true, false, false); + find(textArea, true, false, false); } } })); @@ -184,11 +184,42 @@ private ClassFileContainer openClass(String lexeme, boolean field, boolean metho if (field) { + ClassFieldLocation fieldLocation = container.getFieldLocationsFor(lexeme).get(0); String className = container.getClassForField(lexeme); - BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, className + ".class"); - ClassViewer activeResource = (ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource(); - HashMap classFiles = BytecodeViewer.viewer.workPane.classFiles; - return wait(classFiles, activeResource); + ClassReferenceLocation referenceLocation = container.getClassReferenceLocationsFor(fieldLocation.owner).get(0); + + // If the field we want to go to wasn't an expression like Class.field. For example param.field or + // variable.field + if (className.isEmpty()) + { + ClassFieldLocation classFieldLocation = container.getFieldLocationsFor(lexeme).get(0); + className = classFieldLocation.owner; + ClassReferenceLocation classReferenceLocation = + container.getClassReferenceLocationsFor(className).get(0); + if (classReferenceLocation == null) + return null; + + String packagePath = classReferenceLocation.packagePath; + + if (packagePath.startsWith("java") || packagePath.startsWith("javax") || packagePath.startsWith("com.sun")) + return null; + + if (!packagePath.isEmpty()) + className = packagePath + "/" + className.substring(className.lastIndexOf('/') + 1); + } + + if (!fieldLocation.owner.equals(referenceLocation.owner)) + { + className = referenceLocation.packagePath + "/" + referenceLocation.owner; + } + + if (resourceContainer.resourceClasses.containsKey(className)) + { + BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, className + ".class"); + ClassViewer activeResource = (ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource(); + HashMap classFiles = BytecodeViewer.viewer.workPane.classFiles; + return wait(classFiles, activeResource); + } } else if (method) { @@ -237,6 +268,11 @@ else if (method) resourceName = packagePath + "/" + lexeme; } + if (!classReferenceLocation.owner.equals(container.getName())) + { + resourceName = packagePath + "/" + classReferenceLocation.owner; + } + if (resourceContainer.resourceClasses.containsKey(resourceName)) { BytecodeViewer.viewer.workPane.addClassResource(resourceContainer, resourceName + ".class"); @@ -249,7 +285,7 @@ else if (method) return null; } - private void open(RSyntaxTextArea textArea, boolean isClass, boolean isField, boolean isMethod) + private void find(RSyntaxTextArea textArea, boolean isClass, boolean isField, boolean isMethod) { Thread thread = new Thread(() -> { @@ -354,6 +390,16 @@ private ClassFileContainer wait(HashMap classFiles, private void moveCursor(int line, int columnStart) { + // Wait for 100ms so we make sure there is enough time between loading the class and registering cursor movement + try + { + Thread.sleep(100); + } + catch (InterruptedException e) + { + throw new RuntimeException(e); + } + for (int i = 0; i < 3; i++) { BytecodeViewPanel panel = ((ClassViewer) BytecodeViewer.viewer.workPane.getActiveResource()).getPanel(i); diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/ClassFileContainer.java b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/ClassFileContainer.java index 989d8778a..f70beeebd 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/ClassFileContainer.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/ClassFileContainer.java @@ -75,9 +75,9 @@ public boolean parse() return true; } } - catch (IOException e) + catch (Throwable e) { - throw new RuntimeException(e); + System.err.println("Failed to parse " + this.getName() + ": " + e.getMessage()); } return false; diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/FieldAccessParser.java b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/FieldAccessParser.java index 0faa2d4f2..98a6918ae 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/FieldAccessParser.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/FieldAccessParser.java @@ -1,9 +1,16 @@ package the.bytecode.club.bytecodeviewer.resources.classcontainer.parser.visitors; import com.github.javaparser.Range; +import com.github.javaparser.ast.body.CallableDeclaration; import com.github.javaparser.ast.expr.*; import com.github.javaparser.resolution.UnsolvedSymbolException; +import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; +import com.github.javaparser.resolution.types.ResolvedType; import the.bytecode.club.bytecodeviewer.resources.classcontainer.ClassFileContainer; +import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassFieldLocation; +import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassLocalVariableLocation; +import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassParameterLocation; +import the.bytecode.club.bytecodeviewer.resources.classcontainer.locations.ClassReferenceLocation; import java.util.Objects; @@ -16,7 +23,14 @@ class FieldAccessParser { - static void parse(ClassFileContainer container, FieldAccessExpr expr) + /** + * Solve a field that is accessed through a lambda and not within a method or constructor + * + * @param container The {@link ClassFileContainer} + * @param expr The {@link FieldAccessExpr} + * @param className The class name of the class that is accessing the field + */ + static void parse(ClassFileContainer container, FieldAccessExpr expr, String className) { Range fieldRange = Objects.requireNonNull(expr.getTokenRange().orElse(null)).getEnd().getRange().orElse(null); if (fieldRange == null) @@ -25,8 +39,53 @@ static void parse(ClassFileContainer container, FieldAccessExpr expr) Value fieldValue = new Value(expr.getName(), fieldRange); Expression scope = expr.getScope(); + if (scope instanceof NameExpr) + { + NameExpr nameExpr = (NameExpr) scope; + Range scopeRange = nameExpr.getRange().orElse(null); + if (scopeRange == null) + return; + + Value scopeValue = new Value(nameExpr.getName(), scopeRange); + try + { + ResolvedValueDeclaration vd = nameExpr.resolve(); + if (vd.isField()) + { + container.putField(scopeValue.name, new ClassFieldLocation(getOwner(container), "reference", + scopeValue.line, scopeValue.columnStart, scopeValue.columnEnd + 1)); + } + else if (vd.isVariable()) + { + container.putLocalVariable(scopeValue.name, new ClassLocalVariableLocation(getOwner(container), + className, "reference", scopeValue.line, scopeValue.columnStart, scopeValue.columnEnd + 1)); + } + else if (vd.isParameter()) + { + container.putParameter(scopeValue.name, new ClassParameterLocation(getOwner(container), className, + "reference", scopeValue.line, scopeValue.columnStart, scopeValue.columnEnd + 1)); + } + + putFieldResolvedValues(container, expr, nameExpr, fieldValue); + } + catch (Exception e) + { + printException(expr, e); + } + } + } + + static void parse(ClassFileContainer container, FieldAccessExpr expr, CallableDeclaration method) + { + Range fieldRange = Objects.requireNonNull(expr.getTokenRange().orElse(null)).getEnd().getRange().orElse(null); + if (fieldRange == null) + return; - // Ex. Clazz.field -> Clazz + Value fieldValue = new Value(expr.getName(), fieldRange); + + Expression scope = expr.getScope(); + + // Ex. Clazz.field -> Clazz or c.field -> c if (scope instanceof NameExpr) { NameExpr nameExpr = (NameExpr) scope; @@ -38,7 +97,11 @@ static void parse(ClassFileContainer container, FieldAccessExpr expr) try { - putClassResolvedValues(container, expr, nameExpr, scopeValue, fieldValue); + // Scope + putResolvedValues(container, "reference", method, nameExpr, scopeValue); + + // Field + putFieldResolvedValues(container, expr, nameExpr, fieldValue); } catch (UnsolvedSymbolException ignore) { @@ -58,7 +121,8 @@ else if (scope instanceof ThisExpr) try { putFieldResolvedValues(container, expr, thisExpr, fieldValue); - } catch (UnsolvedSymbolException e) + } + catch (UnsolvedSymbolException e) { printException(expr, e); } @@ -69,7 +133,39 @@ else if (scope instanceof EnclosedExpr) try { putFieldResolvedValues(container, expr, enclosedExpr, fieldValue); - } catch (UnsolvedSymbolException e) + } + catch (UnsolvedSymbolException e) + { + printException(expr, e); + } + } + else + { + try + { + // If the scope is something like 'this.field1' and similar + ResolvedType resolvedType = expr.getScope().calculateResolvedType(); + if (!resolvedType.isReferenceType()) + return; + + String qualifiedName = resolvedType.asReferenceType().getQualifiedName(); + String className = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); + // If the class is not registered as a reference yet + if (container.getClassReferenceLocationsFor(className) == null) + { + String packageName = ""; + if (qualifiedName.contains(".")) + packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.')).replace('.', '/'); + + // For this purpose, we do not care about its line, columnStart or columnEnd + container.putClassReference(className, new ClassReferenceLocation(className, packageName, + fieldValue.name, "reference", -1, -1, -1)); + } + + container.putField(fieldValue.name, new ClassFieldLocation(className, "reference", fieldValue.line, + fieldValue.columnStart, fieldValue.columnEnd + 1)); + } + catch (Exception e) { printException(expr, e); } @@ -104,7 +200,9 @@ static void parseStatic(ClassFileContainer container, FieldAccessExpr expr) try { putClassResolvedValues(container, expr, nameExpr, scopeValue, fieldValue); - } catch (UnsolvedSymbolException e) { + } + catch (UnsolvedSymbolException e) + { printException(expr, e); } } @@ -112,9 +210,12 @@ static void parseStatic(ClassFileContainer container, FieldAccessExpr expr) else if (scope instanceof ThisExpr) { ThisExpr thisExpr = (ThisExpr) scope; - try { + try + { putFieldResolvedValues(container, expr, thisExpr, fieldValue); - } catch (UnsolvedSymbolException e) { + } + catch (UnsolvedSymbolException e) + { printException(expr, e); } } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/MyVoidVisitor.java b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/MyVoidVisitor.java index de7f2dbc4..ba45e323d 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/MyVoidVisitor.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/MyVoidVisitor.java @@ -12,6 +12,7 @@ import com.github.javaparser.ast.type.TypeParameter; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; import com.github.javaparser.resolution.declarations.ResolvedConstructorDeclaration; +import com.github.javaparser.resolution.declarations.ResolvedEnumDeclaration; import com.github.javaparser.resolution.declarations.ResolvedMethodDeclaration; import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; @@ -294,8 +295,8 @@ public void visit(ClassOrInterfaceDeclaration n, Object arg) ResolvedReferenceTypeDeclaration resolve = n.resolve(); this.classFileContainer.putClassReference(resolve.getName(), - new ClassReferenceLocation(getOwner(classFileContainer), - resolve.getPackageName(), "", "declaration", value.line, value.columnStart, value.columnEnd + 1)); + new ClassReferenceLocation(resolve.getName(), + resolve.getPackageName(), "", "declaration", value.line, value.columnStart, value.columnEnd + 1)); } catch (Exception e) { @@ -343,9 +344,22 @@ public void visit(ClassOrInterfaceType n, Object arg) if (qualifiedName.contains(".")) packagePath = qualifiedName.substring(0, qualifiedName.lastIndexOf('.')).replace('.', '/'); - this.classFileContainer.putClassReference(classValue.name, - new ClassReferenceLocation(getOwner(classFileContainer), - packagePath, "", "reference", classValue.line, classValue.columnStart, classValue.columnEnd + 1)); + ClassOrInterfaceType classScope = n.getScope().orElse(null); + if (classScope == null) + { + this.classFileContainer.putClassReference(classValue.name, + new ClassReferenceLocation(classValue.name, + packagePath, "", "reference", classValue.line, classValue.columnStart, + classValue.columnEnd + 1)); + } + else + { + packagePath = packagePath.substring(0, packagePath.lastIndexOf("/")); + + this.classFileContainer.putClassReference(classValue.name, + new ClassReferenceLocation(classScope.getNameAsString(), packagePath, "", "reference", + classValue.line, classValue.columnStart, classValue.columnEnd + 1)); + } } catch (Exception e) { @@ -459,6 +473,19 @@ public void visit(DoStmt n, Object arg) public void visit(EnumDeclaration n, Object arg) { super.visit(n, arg); + + ResolvedEnumDeclaration resolve = n.resolve(); + + Range enumClassNameRange = n.getName().getRange().orElse(null); + if (enumClassNameRange == null) + return; + + Value enumClassValue = new Value(n.getName(), enumClassNameRange); + + this.classFileContainer.putClassReference(enumClassValue.name, + new ClassReferenceLocation(getOwner(classFileContainer), resolve.getPackageName(), "", "declaration", + enumClassValue.line, enumClassValue.columnStart, enumClassValue.columnEnd + 1)); + n.getEntries().forEach(entry -> { SimpleName simpleName = entry.getName(); @@ -470,7 +497,8 @@ public void visit(EnumDeclaration n, Object arg) int line = range.begin.line; int columnStart = range.begin.column; int columnEnd = range.end.column; - this.classFileContainer.putField(name, new ClassFieldLocation(getOwner(classFileContainer), "declaration", line, columnStart, columnEnd + 1)); + this.classFileContainer.putField(name, new ClassFieldLocation(getOwner(classFileContainer), "declaration" + , line, columnStart, columnEnd + 1)); }); } @@ -532,11 +560,17 @@ public void visit(FieldAccessExpr n, Object arg) try { InitializerDeclaration initializer = findInitializerForExpression(n, this.compilationUnit); + ClassOrInterfaceDeclaration classOrInterfaceDeclaration = findClassOrInterfaceForExpression(n, this.compilationUnit); + CallableDeclaration method = findMethodForExpression(n, this.compilationUnit); + if (method == null) + method = findConstructorForExpression(n, this.compilationUnit); - if (initializer == null) - FieldAccessParser.parse(classFileContainer, n); - else + if (method != null) + FieldAccessParser.parse(classFileContainer, n, method); + else if (initializer != null) FieldAccessParser.parseStatic(classFileContainer, n); + else if (classOrInterfaceDeclaration != null) + FieldAccessParser.parse(classFileContainer, n, classOrInterfaceDeclaration.getNameAsString()); } catch (Exception e) { @@ -805,12 +839,17 @@ public void visit(MethodCallExpr n, Object arg) { CallableDeclaration method = findMethodForExpression(n, this.compilationUnit); InitializerDeclaration staticInitializer = null; + ClassOrInterfaceDeclaration classOrInterfaceDeclaration = null; if (method == null) { method = findConstructorForExpression(n, this.compilationUnit); if (method == null) { staticInitializer = findInitializerForExpression(n, this.compilationUnit); + if (staticInitializer == null) + { + classOrInterfaceDeclaration = findClassOrInterfaceForExpression(n, this.compilationUnit); + } } } @@ -839,6 +878,10 @@ else if (staticInitializer != null) { MethodCallParser.parseStatic(classFileContainer, n); } + else if (classOrInterfaceDeclaration != null) + { + MethodCallParser.parse(classFileContainer, n, null); + } } catch (Exception e) { @@ -896,7 +939,29 @@ public void visit(MethodDeclaration n, Object arg) public void visit(MethodReferenceExpr n, Object arg) { super.visit(n, arg); - if (DEBUG) System.err.println("MethodReferenceExpr"); + try + { + ResolvedMethodDeclaration resolve = n.resolve(); + String signature = resolve.getQualifiedSignature(); + String parameters = ""; + if (resolve.getNumberOfParams() != 0) + parameters = signature.substring(signature.indexOf('(') + 1, signature.lastIndexOf(')')); + + Range methodRange = + Objects.requireNonNull(n.getTokenRange().orElse(null)).getEnd().getRange().orElse(null); + if (methodRange == null) + return; + + String methodName = n.getIdentifier(); + + String className = resolve.getClassName(); + classFileContainer.putMethod(methodName, new ClassMethodLocation(className, signature, parameters, + "reference", methodRange.begin.line, methodRange.begin.column, methodRange.end.column + 1)); + } + catch (Exception e) + { + printException(n, e); + } } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParameterParser.java b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParameterParser.java index 729b8dac9..264422374 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParameterParser.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParameterParser.java @@ -2,7 +2,9 @@ import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; +import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration; import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.SimpleName; import the.bytecode.club.bytecodeviewer.resources.classcontainer.ClassFileContainer; @@ -23,14 +25,21 @@ public static void parse(CompilationUnit compilationUnit, Parameter p, ClassFile String methodName = findMethodOwnerFor(compilationUnit, node); if (methodName == null) { - System.err.println("Parameter - Method not found"); - return; + ClassOrInterfaceDeclaration classOrInterfaceForExpression = findClassOrInterfaceForExpression((Expression) node, compilationUnit); + if (classOrInterfaceForExpression == null) + { + System.err.println("Parameter - Method not found"); + return; + } + + methodName = classOrInterfaceForExpression.getNameAsString(); } SimpleName name = p.getName(); + String finalMethodName = methodName; name.getRange().ifPresent(range -> { Value parameter = new Value(name, range); - putParameter(container, parameter, methodName, "declaration"); + putParameter(container, parameter, finalMethodName, "declaration"); }); } } diff --git a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParserUtil.java b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParserUtil.java index 265410b84..f8c565f5d 100644 --- a/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParserUtil.java +++ b/src/main/java/the/bytecode/club/bytecodeviewer/resources/classcontainer/parser/visitors/ParserUtil.java @@ -3,10 +3,7 @@ import com.github.javaparser.Range; import com.github.javaparser.ast.CompilationUnit; import com.github.javaparser.ast.Node; -import com.github.javaparser.ast.body.CallableDeclaration; -import com.github.javaparser.ast.body.ConstructorDeclaration; -import com.github.javaparser.ast.body.InitializerDeclaration; -import com.github.javaparser.ast.body.MethodDeclaration; +import com.github.javaparser.ast.body.*; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.SimpleName; @@ -14,6 +11,7 @@ import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.ast.stmt.TryStmt; import com.github.javaparser.ast.visitor.VoidVisitorAdapter; +import com.github.javaparser.resolution.declarations.ResolvedReferenceTypeDeclaration; import com.github.javaparser.resolution.declarations.ResolvedValueDeclaration; import com.github.javaparser.resolution.types.ResolvedReferenceType; import com.github.javaparser.resolution.types.ResolvedType; @@ -56,27 +54,30 @@ public Value(SimpleName simpleName, Range range) * @param resolveExpr The {@code NameExpr} * @param value The value */ - static void putResolvedValues(ClassFileContainer container, String decRef, CallableDeclaration method, - NameExpr resolveExpr, Value value) + static boolean putResolvedValues(ClassFileContainer container, String decRef, CallableDeclaration method, + NameExpr resolveExpr, Value value) { ResolvedValueDeclaration vd = resolveExpr.resolve(); if (vd.isField()) { container.putField(value.name, new ClassFieldLocation(getOwner(container), decRef, value.line, value.columnStart, value.columnEnd + 1)); + return true; } else if (vd.isVariable()) { - container.putLocalVariable(value.name, new ClassLocalVariableLocation(getOwner(container) - , getMethod(method), decRef, value.line, value.columnStart, - value.columnEnd + 1)); + container.putLocalVariable(value.name, new ClassLocalVariableLocation(getOwner(container), + getMethod(method), decRef, value.line, value.columnStart, value.columnEnd + 1)); + return true; } else if (vd.isParameter()) { - container.putParameter(value.name, new ClassParameterLocation(getOwner(container), - getMethod(method), decRef, value.line, value.columnStart, - value.columnEnd + 1)); + container.putParameter(value.name, new ClassParameterLocation(getOwner(container), getMethod(method), + decRef, value.line, value.columnStart, value.columnEnd + 1)); + return true; } + + return false; } /** @@ -96,8 +97,8 @@ static void putResolvedValues(ClassFileContainer container, String decRef, NameE } else if (vd.isVariable()) { - container.putLocalVariable(value.name, new ClassLocalVariableLocation(getOwner(container) - , "static", decRef, value.line, value.columnStart, value.columnEnd + 1)); + container.putLocalVariable(value.name, new ClassLocalVariableLocation(getOwner(container), "static", + decRef, value.line, value.columnStart, value.columnEnd + 1)); } else if (vd.isParameter()) { @@ -140,9 +141,14 @@ static void putClassResolvedValues(ClassFileContainer container, Expression visi if (qualifiedName.contains(".")) packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.')).replace('.', '/'); - container.putClassReference(className, new ClassReferenceLocation(ParserUtil.getOwner(container), packageName + ResolvedReferenceTypeDeclaration resolvedReferenceTypeDeclaration = resolvedType.asReferenceType().getTypeDeclaration().orElse(null); + assert resolvedReferenceTypeDeclaration != null; + if (resolvedReferenceTypeDeclaration.getClassName().contains(".")) + packageName = packageName.substring(0, packageName.lastIndexOf('/')); + + container.putClassReference(className, new ClassReferenceLocation(className, packageName , fieldValue.name, "reference", scopeValue.line, scopeValue.columnStart, scopeValue.columnEnd + 1)); - container.putField(fieldValue.name, new ClassFieldLocation(scopeValue.name, "reference", fieldValue.line, + container.putField(fieldValue.name, new ClassFieldLocation(className, "reference", fieldValue.line, fieldValue.columnStart, fieldValue.columnEnd + 1)); } @@ -171,7 +177,7 @@ static void putClassResolvedValues(ClassFileContainer container, Expression visi if (qualifiedName.contains(".")) packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf('.')).replace('.', '/'); - container.putClassReference(className, new ClassReferenceLocation(ParserUtil.getOwner(container), packageName + container.putClassReference(className, new ClassReferenceLocation(className, packageName , "", "reference", scopeValue.line, scopeValue.columnStart, scopeValue.columnEnd + 1)); } @@ -187,8 +193,16 @@ static void putFieldResolvedValues(ClassFileContainer container, Expression visi Expression resolveExpr, Value fieldValue) { ResolvedType resolvedType = visitedExpr.getSymbolResolver().calculateType(resolveExpr); + if (resolvedType.isConstraint()) + { + resolvedType = resolvedType.asConstraintType().getBound(); + } + if (!resolvedType.isReferenceType()) + { return; + } + String qualifiedName = resolvedType.asReferenceType().getQualifiedName(); String className = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1); @@ -514,4 +528,33 @@ public void visit(InitializerDeclaration n, Void arg) return null; } + + static ClassOrInterfaceDeclaration findClassOrInterfaceForExpression(Expression expression, CompilationUnit cu) + { + final boolean[] contains = {false}; + final ClassOrInterfaceDeclaration[] classOrInterfaceDeclaration = {null}; + cu.accept(new VoidVisitorAdapter() + { + @Override + public void visit(ClassOrInterfaceDeclaration n, Void arg) + { + super.visit(n, arg); + if (contains[0]) + return; + + n.getMembers().forEach(member -> { + if (member.containsWithinRange(expression)) + { + contains[0] = true; + classOrInterfaceDeclaration[0] = n; + } + }); + } + }, null); + + if (contains[0]) + return classOrInterfaceDeclaration[0]; + + return null; + } }