From f259b38c72268b494f87cb666f67837dd2797fef Mon Sep 17 00:00:00 2001 From: leonardosnt Date: Thu, 11 Feb 2021 12:45:52 +0100 Subject: [PATCH] IDEA-245329: Handle CONSTANT_Module and CONSTANT_Package PR #1406 GitOrigin-RevId: 501d3b66e790316f8ab52606ea4cba41665213c0 --- .../java/decompiler/code/CodeConstants.java | 6 +- .../java/decompiler/main/ClassWriter.java | 117 ++++++++- .../struct/attr/StructGeneralAttribute.java | 6 +- .../struct/attr/StructModuleAttribute.java | 227 ++++++++++++++++++ .../struct/consts/ConstantPool.java | 4 +- .../struct/consts/PrimitiveConstant.java | 4 +- .../java/decompiler/SingleClassesTest.java | 3 +- testData/classes/java9/module-info.class | Bin 0 -> 298 bytes testData/results/module-info.dec | 13 + .../src/java9/sample.module/module-info.java | 15 ++ .../java9/sample.module/test/TestService.java | 3 + .../sample.module/test/TestServiceImpl.java | 3 + .../java9/sample.module/test2/TestClass.java | 3 + 13 files changed, 396 insertions(+), 8 deletions(-) create mode 100644 src/org/jetbrains/java/decompiler/struct/attr/StructModuleAttribute.java create mode 100644 testData/classes/java9/module-info.class create mode 100644 testData/results/module-info.dec create mode 100644 testData/src/java9/sample.module/module-info.java create mode 100644 testData/src/java9/sample.module/test/TestService.java create mode 100644 testData/src/java9/sample.module/test/TestServiceImpl.java create mode 100644 testData/src/java9/sample.module/test2/TestClass.java diff --git a/src/org/jetbrains/java/decompiler/code/CodeConstants.java b/src/org/jetbrains/java/decompiler/code/CodeConstants.java index 720d2a7..2c5cb42 100644 --- a/src/org/jetbrains/java/decompiler/code/CodeConstants.java +++ b/src/org/jetbrains/java/decompiler/code/CodeConstants.java @@ -1,4 +1,4 @@ -// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.code; @SuppressWarnings({"unused", "SpellCheckingInspection"}) @@ -64,6 +64,7 @@ public interface CodeConstants { int ACC_STATIC = 0x0008; int ACC_FINAL = 0x0010; int ACC_SYNCHRONIZED = 0x0020; + int ACC_OPEN = 0x0020; int ACC_NATIVE = 0x0100; int ACC_ABSTRACT = 0x0400; int ACC_STRICT = 0x0800; @@ -75,6 +76,7 @@ public interface CodeConstants { int ACC_ANNOTATION = 0x2000; int ACC_ENUM = 0x4000; int ACC_MANDATED = 0x8000; + int ACC_MODULE = 0x8000; // ---------------------------------------------------------------------- // CLASS FLAGS @@ -112,6 +114,8 @@ public interface CodeConstants { int CONSTANT_MethodHandle = 15; int CONSTANT_MethodType = 16; int CONSTANT_InvokeDynamic = 18; + int CONSTANT_Module = 19; + int CONSTANT_Package = 20; // ---------------------------------------------------------------------- // MethodHandle reference_kind values diff --git a/src/org/jetbrains/java/decompiler/main/ClassWriter.java b/src/org/jetbrains/java/decompiler/main/ClassWriter.java index aa03fa5..bfd46f2 100644 --- a/src/org/jetbrains/java/decompiler/main/ClassWriter.java +++ b/src/org/jetbrains/java/decompiler/main/ClassWriter.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; import org.jetbrains.java.decompiler.code.CodeConstants; @@ -249,6 +249,12 @@ public class ClassWriter { } } + boolean isModuleInfo = cl.hasModifier(CodeConstants.ACC_MODULE) && cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); + + if (isModuleInfo) { + writeModuleInfoBody(buffer, cl); + } + buffer.appendIndent(indent).append('}'); if (node.type != ClassNode.CLASS_ANONYMOUS) { @@ -277,6 +283,99 @@ public class ClassWriter { return false; } + private void writeModuleInfoBody(TextBuffer buffer, StructClass cl) { + StructModuleAttribute moduleAttribute = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); + + for (StructModuleAttribute.RequiresEntry requires : moduleAttribute.requires) { + String moduleName = requires.moduleName.replace('/', '.'); + + buffer.appendIndent(1) + .append("requires ") + .append(moduleName) + .append(';') + .appendLineSeparator(); + } + + for (StructModuleAttribute.ExportsEntry exports : moduleAttribute.exports) { + String packageName = exports.packageName.replace('/', '.'); + + buffer.appendIndent(1).append("exports ").append(packageName); + + List exportToModules = exports.exportToModules; + if (exportToModules.size() > 0) { + buffer.append(" to").appendLineSeparator(); + + int lastIndex = exportToModules.size() - 1; + for (int i = 0; i < exportToModules.size(); i++) { + String moduleName = exportToModules.get(i).replace('/', '.'); + char separator = i == lastIndex ? ';' : ','; + + buffer.appendIndent(2) + .append(moduleName) + .append(separator) + .appendLineSeparator(); + } + } else { + buffer.append(';').appendLineSeparator(); + } + } + + for (StructModuleAttribute.OpensEntry opens : moduleAttribute.opens) { + String packageName = opens.packageName.replace('/', '.'); + + buffer.appendIndent(1).append("opens ").append(packageName); + + List opensToModules = opens.opensToModules; + if (opensToModules.size() > 0) { + buffer.append(" to").appendLineSeparator(); + + int lastIndex = opensToModules.size() - 1; + for (int i = 0; i < opensToModules.size(); i++) { + String moduleName = opensToModules.get(i).replace('/', '.'); + char separator = i == lastIndex ? ';' : ','; + + buffer.appendIndent(2) + .append(moduleName) + .append(separator) + .appendLineSeparator(); + } + } else { + buffer.append(';').appendLineSeparator(); + } + } + + for (String uses : moduleAttribute.uses) { + String className = ExprProcessor.buildJavaClassName(uses); + + buffer.appendIndent(1) + .append("uses ") + .append(className) + .append(';') + .appendLineSeparator(); + } + + for (StructModuleAttribute.ProvidesEntry provides : moduleAttribute.provides) { + String interfaceName = ExprProcessor.buildJavaClassName(provides.interfaceName); + + buffer.appendIndent(1) + .append("provides ") + .append(interfaceName) + .append(" with") + .appendLineSeparator(); + + int lastIndex = provides.implementationNames.size() - 1; + for (int i = 0; i < provides.implementationNames.size(); i++) { + String className = ExprProcessor.buildJavaClassName(provides.implementationNames.get(i)); + char separator = i == lastIndex ? ';' : ','; + + buffer.appendIndent(2) + .append(className) + .append(separator) + .appendLineSeparator(); + } + } + } + private static void addTracer(StructClass cls, StructMethod method, BytecodeMappingTracer tracer) { StructLineNumberTableAttribute table = method.getAttribute(StructGeneralAttribute.ATTRIBUTE_LINE_NUMBER_TABLE); tracer.setLineNumberTable(table); @@ -299,6 +398,7 @@ public class ClassWriter { boolean isEnum = DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM) && (flags & CodeConstants.ACC_ENUM) != 0; boolean isInterface = (flags & CodeConstants.ACC_INTERFACE) != 0; boolean isAnnotation = (flags & CodeConstants.ACC_ANNOTATION) != 0; + boolean isModuleInfo = (flags & CodeConstants.ACC_MODULE) != 0 && cl.hasAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); if (isDeprecated) { appendDeprecation(buffer, indent); @@ -344,11 +444,24 @@ public class ClassWriter { else if (components != null) { buffer.append("record "); } + else if (isModuleInfo) { + StructModuleAttribute moduleAttribute = cl.getAttribute(StructGeneralAttribute.ATTRIBUTE_MODULE); + + if ((moduleAttribute.moduleFlags & CodeConstants.ACC_OPEN) != 0) { + buffer.append("open "); + } + + buffer.append("module "); + buffer.append(moduleAttribute.moduleName); + } else { buffer.append("class "); } - buffer.append(node.simpleName); + // Handled above + if (!isModuleInfo) { + buffer.append(node.simpleName); + } GenericClassDescriptor descriptor = getGenericClassDescriptor(cl); if (descriptor != null && !descriptor.fparameters.isEmpty()) { diff --git a/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java b/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java index 8fff5db..041e266 100644 --- a/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java +++ b/src/org/jetbrains/java/decompiler/struct/attr/StructGeneralAttribute.java @@ -1,4 +1,4 @@ -// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct.attr; import org.jetbrains.java.decompiler.struct.consts.ConstantPool; @@ -34,6 +34,7 @@ public class StructGeneralAttribute { public static final Key ATTRIBUTE_DEPRECATED = new Key<>("Deprecated"); public static final Key ATTRIBUTE_LINE_NUMBER_TABLE = new Key<>("LineNumberTable"); public static final Key ATTRIBUTE_METHOD_PARAMETERS = new Key<>("MethodParameters"); + public static final Key ATTRIBUTE_MODULE = new Key<>("Module"); public static final Key ATTRIBUTE_RECORD = new Key<>("Record"); public static class Key { @@ -98,6 +99,9 @@ public class StructGeneralAttribute { else if (ATTRIBUTE_METHOD_PARAMETERS.getName().equals(name)) { attr = new StructMethodParametersAttribute(); } + else if (ATTRIBUTE_MODULE.getName().equals(name)) { + attr = new StructModuleAttribute(); + } else if (ATTRIBUTE_RECORD.getName().equals(name)) { attr = new StructRecordAttribute(); } diff --git a/src/org/jetbrains/java/decompiler/struct/attr/StructModuleAttribute.java b/src/org/jetbrains/java/decompiler/struct/attr/StructModuleAttribute.java new file mode 100644 index 0000000..23c0486 --- /dev/null +++ b/src/org/jetbrains/java/decompiler/struct/attr/StructModuleAttribute.java @@ -0,0 +1,227 @@ +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.java.decompiler.struct.attr; + +import org.jetbrains.java.decompiler.struct.consts.ConstantPool; +import org.jetbrains.java.decompiler.util.DataInputFullStream; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class StructModuleAttribute extends StructGeneralAttribute { + public String moduleName; + public int moduleFlags; + public String moduleVersion; + + public List requires; + public List exports; + public List opens; + public List uses; + public List provides; + + @Override + public void initContent(DataInputFullStream data, ConstantPool pool) throws IOException { + int moduleNameIndex = data.readUnsignedShort(); + + this.moduleName = pool.getPrimitiveConstant(moduleNameIndex).getString(); + this.moduleFlags = data.readUnsignedShort(); + + int moduleVersionIndex = data.readUnsignedShort(); + if (moduleVersionIndex != 0) { + moduleVersion = pool.getPrimitiveConstant(moduleVersionIndex).getString(); + } + + this.requires = readRequires(data, pool); + this.exports = readExports(data, pool); + this.opens = readOpens(data, pool); + this.uses = readUses(data, pool); + this.provides = readProvides(data, pool); + } + + public List readRequires(DataInputFullStream data, ConstantPool pool) throws IOException { + int requiresCount = data.readUnsignedShort(); + + if (requiresCount <= 0) { + return Collections.emptyList(); + } + + List requires = new ArrayList<>(requiresCount); + for (int i = 0; i < requiresCount; i++) { + int moduleNameIndex = data.readUnsignedShort(); + int moduleFlags = data.readUnsignedShort(); + int versionIndex = data.readUnsignedShort(); + + String moduleName = pool.getPrimitiveConstant(moduleNameIndex).getString(); + String version = versionIndex == 0 ? null : pool.getPrimitiveConstant(versionIndex).getString(); + + requires.add(new RequiresEntry(moduleName, moduleFlags, version)); + } + + return requires; + } + + private List readExports(DataInputFullStream data, ConstantPool pool) throws IOException { + int exportsCount = data.readUnsignedShort(); + + if (exportsCount <= 0) { + return Collections.emptyList(); + } + + List exports = new ArrayList<>(exportsCount); + + for (int i = 0; i < exportsCount; i++) { + int packageNameIndex = data.readUnsignedShort(); + int exportsFlags = data.readUnsignedShort(); + int exportsToCount = data.readUnsignedShort(); + + List exportsToModules; + if (exportsToCount > 0) { + exportsToModules = new ArrayList<>(exportsToCount); + + for (int j = 0; j < exportsToCount; j++) { + int moduleNameIndex = data.readUnsignedShort(); + String moduleName = pool.getPrimitiveConstant(moduleNameIndex).getString(); + + exportsToModules.add(moduleName); + } + } else { + exportsToModules = Collections.emptyList(); + } + + String packageName = pool.getPrimitiveConstant(packageNameIndex).getString(); + + exports.add(new ExportsEntry(packageName, exportsFlags, exportsToModules)); + } + + return exports; + } + + private List readOpens(DataInputFullStream data, ConstantPool pool) throws IOException { + int opensCount = data.readUnsignedShort(); + + if (opensCount <= 0) { + return Collections.emptyList(); + } + + List opens = new ArrayList<>(opensCount); + + for (int i = 0; i < opensCount; i++) { + int packageNameIndex = data.readUnsignedShort(); + int opensFlags = data.readUnsignedShort(); + int opensToCount = data.readUnsignedShort(); + + List opensToModules; + if (opensToCount > 0) { + opensToModules = new ArrayList<>(opensToCount); + + for (int j = 0; j < opensToCount; j++) { + int moduleNameIndex = data.readUnsignedShort(); + String moduleName = pool.getPrimitiveConstant(moduleNameIndex).getString(); + + opensToModules.add(moduleName); + } + } else { + opensToModules = Collections.emptyList(); + } + + String packageName = pool.getPrimitiveConstant(packageNameIndex).getString(); + + opens.add(new OpensEntry(packageName, opensFlags, opensToModules)); + } + + return opens; + } + + private List readUses(DataInputFullStream data, ConstantPool pool) throws IOException { + int usesCount = data.readUnsignedShort(); + if (usesCount <= 0) { + return Collections.emptyList(); + } + + List uses = new ArrayList<>(usesCount); + for (int i = 0; i < usesCount; i++) { + int classNameIndex = data.readUnsignedShort(); + String className = pool.getPrimitiveConstant(classNameIndex).getString(); + + uses.add(className); + } + + return uses; + } + + private List readProvides(DataInputFullStream data, ConstantPool pool) throws IOException { + int providesCount = data.readUnsignedShort(); + if (providesCount <= 0) { + return Collections.emptyList(); + } + + List provides = new ArrayList<>(providesCount); + for (int i = 0; i < providesCount; i++) { + int interfaceNameIndex = data.readUnsignedShort(); + String interfaceName = pool.getPrimitiveConstant(interfaceNameIndex).getString(); + + // Always nonzero + int providesWithCount = data.readUnsignedShort(); + List implementationNames = new ArrayList<>(providesWithCount); + + for (int j = 0; j < providesWithCount; j++) { + int classNameIndex = data.readUnsignedShort(); + String className = pool.getPrimitiveConstant(classNameIndex).getString(); + + implementationNames.add(className); + } + + provides.add(new ProvidesEntry(interfaceName, implementationNames)); + } + + return provides; + } + + public static final class RequiresEntry { + public String moduleName; + public int moduleFlags; + public String moduleVersion; + + public RequiresEntry(String moduleName, int moduleFlags, String moduleVersion) { + this.moduleName = moduleName; + this.moduleFlags = moduleFlags; + this.moduleVersion = moduleVersion; + } + } + + public static final class ExportsEntry { + public String packageName; + public int exportsFlags; + public List exportToModules; + + public ExportsEntry(String packageName, int exportsFlags, List exportToModules) { + this.packageName = packageName; + this.exportsFlags = exportsFlags; + this.exportToModules = exportToModules; + } + } + + public static final class OpensEntry { + public String packageName; + public int opensFlags; + public List opensToModules; + + public OpensEntry(String packageName, int exportsFlags, List exportToModules) { + this.packageName = packageName; + this.opensFlags = exportsFlags; + this.opensToModules = exportToModules; + } + } + + public static final class ProvidesEntry { + public String interfaceName; + public List implementationNames; + + public ProvidesEntry(String interfaceName, List implementationNames) { + this.interfaceName = interfaceName; + this.implementationNames = implementationNames; + } + } + +} \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/struct/consts/ConstantPool.java b/src/org/jetbrains/java/decompiler/struct/consts/ConstantPool.java index 0ea6e01..d90ba1d 100644 --- a/src/org/jetbrains/java/decompiler/struct/consts/ConstantPool.java +++ b/src/org/jetbrains/java/decompiler/struct/consts/ConstantPool.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct.consts; import org.jetbrains.java.decompiler.code.CodeConstants; @@ -64,6 +64,8 @@ public class ConstantPool implements NewClassNameBuilder { case CodeConstants.CONSTANT_Class: case CodeConstants.CONSTANT_String: case CodeConstants.CONSTANT_MethodType: + case CodeConstants.CONSTANT_Module: + case CodeConstants.CONSTANT_Package: pool.add(new PrimitiveConstant(tag, in.readUnsignedShort())); nextPass[0].set(i); break; diff --git a/src/org/jetbrains/java/decompiler/struct/consts/PrimitiveConstant.java b/src/org/jetbrains/java/decompiler/struct/consts/PrimitiveConstant.java index 626a91b..408622a 100644 --- a/src/org/jetbrains/java/decompiler/struct/consts/PrimitiveConstant.java +++ b/src/org/jetbrains/java/decompiler/struct/consts/PrimitiveConstant.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.struct.consts; public class PrimitiveConstant extends PooledConstant { @@ -31,7 +31,7 @@ public class PrimitiveConstant extends PooledConstant { @Override public void resolveConstant(ConstantPool pool) { - if (type == CONSTANT_Class || type == CONSTANT_String || type == CONSTANT_MethodType) { + if (type == CONSTANT_Class || type == CONSTANT_String || type == CONSTANT_MethodType || type == CONSTANT_Module || type == CONSTANT_Package) { value = pool.getPrimitiveConstant(index).getString(); initConstant(); } diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index c720949..c6f1b8c 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -74,6 +74,7 @@ public class SingleClassesTest { "pkg/TestShadowingSuperClass"); } @Test public void testStringConcat() { doTest("pkg/TestStringConcat"); } @Test public void testJava9StringConcat() { doTest("java9/TestJava9StringConcat"); } + @Test public void testJava9ModuleInfo() { doTest("java9/module-info"); } @Test public void testJava11StringConcat() { doTest("java11/TestJava11StringConcat"); } @Test public void testMethodReferenceSameName() { doTest("pkg/TestMethodReferenceSameName"); } @Test public void testMethodReferenceLetterClass() { doTest("pkg/TestMethodReferenceLetterClass"); } @@ -175,4 +176,4 @@ public class SingleClassesTest { return files; } -} \ No newline at end of file +} diff --git a/testData/classes/java9/module-info.class b/testData/classes/java9/module-info.class new file mode 100644 index 0000000000000000000000000000000000000000..59005e4978071319bbaa38052ffa71306eec0b30 GIT binary patch literal 298 zcmZ9INeTin5Ji8rL!75>H{QXtx)D5p;6f1e02bi66)?QxsMXAQBGBo5-Lt+gpu0{vs9Zfx#@6ct=Ysj*GCm| zKkiVt-`VJ2sFFce`W?i6CU~ZjNFs~K9&e5@d&p*Pv$+PtV DKj}Rb literal 0 HcmV?d00001 diff --git a/testData/results/module-info.dec b/testData/results/module-info.dec new file mode 100644 index 0000000..c45d666 --- /dev/null +++ b/testData/results/module-info.dec @@ -0,0 +1,13 @@ +module sample.module { + requires java.base; + exports test; + exports test2 to + java.base; + opens test; + opens test2 to + java.base; + uses java.util.spi.ToolProvider; + provides test.TestService with + test.TestServiceImpl; +} + diff --git a/testData/src/java9/sample.module/module-info.java b/testData/src/java9/sample.module/module-info.java new file mode 100644 index 0000000..ea71a46 --- /dev/null +++ b/testData/src/java9/sample.module/module-info.java @@ -0,0 +1,15 @@ +module sample.module { + requires java.base; + + uses java.util.spi.ToolProvider; + + provides test.TestService with test.TestServiceImpl; + + exports test; + + exports test2 to java.base; + + opens test; + + opens test2 to java.base; +} \ No newline at end of file diff --git a/testData/src/java9/sample.module/test/TestService.java b/testData/src/java9/sample.module/test/TestService.java new file mode 100644 index 0000000..c2a6cd7 --- /dev/null +++ b/testData/src/java9/sample.module/test/TestService.java @@ -0,0 +1,3 @@ +package test; + +public interface TestService {} \ No newline at end of file diff --git a/testData/src/java9/sample.module/test/TestServiceImpl.java b/testData/src/java9/sample.module/test/TestServiceImpl.java new file mode 100644 index 0000000..9a153e8 --- /dev/null +++ b/testData/src/java9/sample.module/test/TestServiceImpl.java @@ -0,0 +1,3 @@ +package test; + +public class TestServiceImpl implements TestService {} \ No newline at end of file diff --git a/testData/src/java9/sample.module/test2/TestClass.java b/testData/src/java9/sample.module/test2/TestClass.java new file mode 100644 index 0000000..202153e --- /dev/null +++ b/testData/src/java9/sample.module/test2/TestClass.java @@ -0,0 +1,3 @@ +package test2; + +public class TestClass {} \ No newline at end of file