diff --git a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java index 2dc207e..2ba0490 100644 --- a/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java +++ b/src/org/jetbrains/java/decompiler/modules/decompiler/vars/VarProcessor.java @@ -21,6 +21,7 @@ import org.jetbrains.java.decompiler.modules.decompiler.stats.Statement; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.struct.gen.VarType; +import org.jetbrains.java.decompiler.util.TextUtil; import java.util.*; import java.util.Map.Entry; @@ -64,8 +65,11 @@ public class VarProcessor { String name = mapVarNames.get(pair); Integer index = mapOriginalVarIndices.get(pair.var); - if (index != null && mapDebugVarNames.containsKey(index)) { - name = mapDebugVarNames.get(index); + if (index != null) { + String debugName = mapDebugVarNames.get(index); + if (debugName != null && TextUtil.isValidIdentifier(debugName, method.getClassStruct().getBytecodeVersion())) { + name = debugName; + } } Integer counter = mapNames.get(name); diff --git a/src/org/jetbrains/java/decompiler/util/TextUtil.java b/src/org/jetbrains/java/decompiler/util/TextUtil.java index 8957fe1..21f10e4 100644 --- a/src/org/jetbrains/java/decompiler/util/TextUtil.java +++ b/src/org/jetbrains/java/decompiler/util/TextUtil.java @@ -15,13 +15,23 @@ */ package org.jetbrains.java.decompiler.util; +import org.jetbrains.java.decompiler.code.CodeConstants; import org.jetbrains.java.decompiler.main.ClassesProcessor; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.TextBuffer; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.modules.decompiler.ExprProcessor; +import java.util.Arrays; +import java.util.HashSet; + public class TextUtil { + private static final HashSet KEYWORDS = new HashSet<>(Arrays.asList( + "abstract", "default", "if", "private", "this", "boolean", "do", "implements", "protected", "throw", "break", "double", "import", + "public", "throws", "byte", "else", "instanceof", "return", "transient", "case", "extends", "int", "short", "try", "catch", "final", + "interface", "static", "void", "char", "finally", "long", "strictfp", "volatile", "class", "float", "native", "super", "while", + "const", "for", "new", "switch", "continue", "goto", "package", "synchronized", "true", "false", "null", "assert")); + public static void writeQualifiedSuper(TextBuffer buf, String qualifier) { ClassesProcessor.ClassNode classNode = (ClassesProcessor.ClassNode)DecompilerContext.getProperty(DecompilerContext.CURRENT_CLASS_NODE); if (!qualifier.equals(classNode.classStruct.qualifiedName)) { @@ -51,4 +61,26 @@ public class TextUtil { sTemp = ("0000" + sTemp).substring(sTemp.length()); return "\\u" + sTemp; } + + public static boolean isValidIdentifier(String id, int version) { + return isJavaIdentifier(id) && !isKeyword(id, version); + } + + private static boolean isJavaIdentifier(String id) { + if (id.isEmpty() || !Character.isJavaIdentifierStart(id.charAt(0))) { + return false; + } + + for (int i = 1; i < id.length(); i++) { + if (!Character.isJavaIdentifierPart(id.charAt(i))) { + return false; + } + } + + return true; + } + + private static boolean isKeyword(String id, int version) { + return KEYWORDS.contains(id) || version > CodeConstants.BYTECODE_JAVA_5 && "enum".equals(id); + } } \ No newline at end of file diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index 5bdbdc5..dd509f6 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -86,6 +86,7 @@ public class SingleClassesTest { @Test public void testStaticNameClash() { doTest("pkg/TestStaticNameClash"); } @Test public void testExtendingSubclass() { doTest("pkg/TestExtendingSubclass"); } @Test public void testSyntheticAccess() { doTest("pkg/TestSyntheticAccess"); } + @Test public void testIllegalVarName() { doTest("pkg/TestIllegalVarName"); } private void doTest(String testFile, String... companionFiles) { ConsoleDecompiler decompiler = fixture.getDecompiler(); @@ -132,4 +133,4 @@ public class SingleClassesTest { return files; } -} +} \ No newline at end of file diff --git a/testData/classes/pkg/TestIllegalVarName.class b/testData/classes/pkg/TestIllegalVarName.class new file mode 100644 index 0000000..5fd5ec6 Binary files /dev/null and b/testData/classes/pkg/TestIllegalVarName.class differ diff --git a/testData/results/TestIllegalVarName.dec b/testData/results/TestIllegalVarName.dec new file mode 100644 index 0000000..3eb1ea5 --- /dev/null +++ b/testData/results/TestIllegalVarName.dec @@ -0,0 +1,33 @@ +package pkg; + +import kotlin.Metadata; +import kotlin.jvm.internal.Intrinsics; +import org.jetbrains.annotations.NotNull; + +@Metadata( + mv = {1, 1, 0}, + bv = {1, 0, 0}, + k = 1, + d1 = {"\u0000\u001a\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0010\b\n\u0000\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0016\u0010\u0003\u001a\u00020\u00042\u0006\u0010\u0005\u001a\u00020\u00042\u0006\u0010\u0006\u001a\u00020\u0007¨\u0006\b"}, + d2 = {"Lpkg/TestIllegalVarName;", "", "()V", "m", "", "this", "enum", "", "java-decompiler-plugin"} +) +public final class TestIllegalVarName { + @NotNull + public final String m(@NotNull String var1, int var2) { + Intrinsics.checkParameterIsNotNull(var1, "this"); + return var1 + '/' + var2;// 5 + } +} + +class 'pkg/TestIllegalVarName' { + method 'm (Ljava/lang/String;I)Ljava/lang/String;' { + 1 16 + 3 16 + 11 17 + 1a 17 + 1d 17 + } +} + +Lines mapping: +5 <-> 18 diff --git a/testData/src/pkg/TestIllegalVarName.kt b/testData/src/pkg/TestIllegalVarName.kt new file mode 100644 index 0000000..cf47c6e --- /dev/null +++ b/testData/src/pkg/TestIllegalVarName.kt @@ -0,0 +1,7 @@ +package pkg + +class TestIllegalVarName { + fun m(`this`: String, `enum`: Int): String { + return `this` + '/' + `enum` + } +} \ No newline at end of file