[java-decompiler] IDEA-282931 Support sealed classes decompilation

Adds the sealed modifier to decompiled sealed classes and interfaces. Also adds non-sealed modifier when a class isn't final, isn't sealed but exstends or implements a sealed class or interface.

GitOrigin-RevId: 4633fa9c153c8117f300fc1af96040bc3389dccf
This commit is contained in:
Bart van Helvert
2021-12-09 20:07:48 +00:00
committed by intellij-monorepo-bot
parent 2f46eb0f99
commit b201e28172
38 changed files with 227 additions and 2 deletions
@@ -420,6 +420,7 @@ public class ClassWriter {
}
List<StructRecordComponent> components = cl.getRecordComponents();
List<String> permittedSubclassSignature = cl.getPermittedSubclasses();
if (components != null) {
// records are implicitly final
@@ -428,6 +429,13 @@ public class ClassWriter {
appendModifiers(buffer, flags, CLASS_ALLOWED, isInterface, CLASS_EXCLUDED);
if (permittedSubclassSignature != null) {
buffer.append("sealed ");
}
else if (node.isNonSealed()) {
buffer.append("non-sealed ");
}
if (isEnum) {
buffer.append("enum ");
}
@@ -498,9 +506,33 @@ public class ClassWriter {
}
}
if (permittedSubclassSignature != null) {
List<ClassNode> permittedOuterSubClasses = getClassNodes(permittedSubclassSignature).stream()
.filter(subClass -> !subClass.enclosingClasses.contains(node.classStruct.qualifiedName))
.collect(Collectors.toList());
if (!permittedOuterSubClasses.isEmpty()) { // only generate permits lists for nested classes
buffer.append("permits ");
for (int i = 0; i < permittedOuterSubClasses.size(); i++) {
if (i > 0) {
buffer.append(", ");
}
ClassNode subClass = permittedOuterSubClasses.get(i);
DecompilerContext.getImportCollector().getShortName(subClass.classStruct.qualifiedName); // add qualified name to potential import list
buffer.append(subClass.simpleName);
}
buffer.append(' ');
}
}
buffer.append('{').appendLineSeparator();
}
private static List<ClassNode> getClassNodes(List<String> qualifiedClassNames) {
return qualifiedClassNames.stream()
.map(str -> DecompilerContext.getClassProcessor().getMapRootClasses().get(str))
.collect(Collectors.toList()
);
}
private static boolean isVarArgRecord(StructClass cl) {
String canonicalConstructorDescriptor =
cl.getRecordComponents().stream().map(c -> c.getDescriptor()).collect(Collectors.joining("", "(", ")V"));
@@ -1,4 +1,4 @@
// 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.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. 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;
@@ -22,6 +22,7 @@ import org.jetbrains.java.decompiler.struct.attr.StructEnclosingMethodAttribute;
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
import org.jetbrains.java.decompiler.struct.attr.StructInnerClassesAttribute;
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
import org.jetbrains.java.decompiler.struct.gen.VarType;
import org.jetbrains.java.decompiler.util.InterpreterUtil;
import org.jetbrains.java.decompiler.util.TextBuffer;
@@ -29,6 +30,7 @@ import org.jetbrains.java.decompiler.util.TextBuffer;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.stream.Collectors;
public class ClassesProcessor implements CodeConstants {
public static final int AVERAGE_CLASS_SIZE = 16 * 1024;
@@ -122,6 +124,27 @@ public class ClassesProcessor implements CodeConstants {
}
}
// set non-sealed if class extends or implements a sealed class
for (Entry<String, ClassNode> ent : mapRootClasses.entrySet()) {
ClassNode clazz = ent.getValue();
List<String> qualifiedSealedSuperNames = new ArrayList<>(Arrays.asList(clazz.classStruct.getInterfaceNames()));
PrimitiveConstant superConst = clazz.classStruct.superClass;
if (superConst != null) qualifiedSealedSuperNames.add(superConst.getString());
List<ClassNode> potentialSealedSupers = qualifiedSealedSuperNames.stream()
.map(mapRootClasses::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
for (ClassNode potentialSealedSuper : potentialSealedSupers) {
if (potentialSealedSuper.classStruct.getPermittedSubclasses() != null &&
potentialSealedSuper.classStruct.getPermittedSubclasses().contains(clazz.classStruct.qualifiedName) &&
(clazz.access & CodeConstants.ACC_FINAL) == 0 &&
clazz.classStruct.getPermittedSubclasses() == null
) {
clazz.setNonSealed(true);
}
}
}
if (bDecompileInner) {
// connect nested classes
for (Entry<String, ClassNode> ent : mapRootClasses.entrySet()) {
@@ -418,6 +441,7 @@ public class ClassesProcessor implements CodeConstants {
public int type;
public int access;
public boolean isNonSealed = false;
public String simpleName;
public final StructClass classStruct;
private ClassWrapper wrapper;
@@ -491,6 +515,14 @@ public class ClassesProcessor implements CodeConstants {
return node.wrapper;
}
public boolean isNonSealed() {
return isNonSealed;
}
public void setNonSealed(boolean nonSealed) {
isNonSealed = nonSealed;
}
public static class LambdaInformation {
public String method_name;
public String method_descriptor;
@@ -3,6 +3,7 @@ package org.jetbrains.java.decompiler.struct;
import org.jetbrains.java.decompiler.code.CodeConstants;
import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute;
import org.jetbrains.java.decompiler.struct.attr.StructPermittedSubclassesAttribute;
import org.jetbrains.java.decompiler.struct.attr.StructRecordAttribute;
import org.jetbrains.java.decompiler.struct.consts.ConstantPool;
import org.jetbrains.java.decompiler.struct.consts.PrimitiveConstant;
@@ -160,6 +161,12 @@ public class StructClass extends StructMember {
return recordAttr.getComponents();
}
public List<String> getPermittedSubclasses() {
StructPermittedSubclassesAttribute permittedSubClassAttr = getAttribute(StructGeneralAttribute.ATTRIBUTE_PERMITTED_SUBCLASSES);
if (permittedSubClassAttr == null) return null;
return permittedSubClassAttr.getClasses();
}
public int[] getInterfaces() {
return interfaces;
}
@@ -1,4 +1,4 @@
// 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.
// Copyright 2000-2021 JetBrains s.r.o. and contributors. 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;
@@ -36,6 +36,7 @@ public class StructGeneralAttribute {
public static final Key<StructMethodParametersAttribute> ATTRIBUTE_METHOD_PARAMETERS = new Key<>("MethodParameters");
public static final Key<StructModuleAttribute> ATTRIBUTE_MODULE = new Key<>("Module");
public static final Key<StructRecordAttribute> ATTRIBUTE_RECORD = new Key<>("Record");
public static final Key<StructPermittedSubclassesAttribute> ATTRIBUTE_PERMITTED_SUBCLASSES = new Key<>("PermittedSubclasses");
@SuppressWarnings("unused")
public static class Key<T extends StructGeneralAttribute> {
@@ -100,6 +101,8 @@ public class StructGeneralAttribute {
}
else if (ATTRIBUTE_RECORD.name.equals(name)) {
return new StructRecordAttribute();
} else if (ATTRIBUTE_PERMITTED_SUBCLASSES.name.equals(name)) {
return new StructPermittedSubclassesAttribute();
}
else {
return null; // unsupported attribute
@@ -0,0 +1,35 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. 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.Arrays;
import java.util.List;
/*
PermittedSubclasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
u2 classes[number_of_classes];
}
*/
public class StructPermittedSubclassesAttribute extends StructGeneralAttribute {
List<String> classes;
@Override
public void initContent(DataInputFullStream data, ConstantPool pool) throws IOException {
int numberOfClasses = data.readUnsignedShort();
String[] classes = new String[numberOfClasses];
for (int i = 0; i < numberOfClasses; i++) {
classes[i] = pool.getPrimitiveConstant(data.readUnsignedShort()).getString();
}
this.classes = Arrays.asList(classes);
}
public List<String> getClasses() {
return classes;
}
}
@@ -147,6 +147,24 @@ public class SingleClassesTest {
@Test public void testRecordVararg() { doTest("records/TestRecordVararg"); }
@Test public void testRecordGenericVararg() { doTest("records/TestRecordGenericVararg"); }
@Test public void testRecordAnno() { doTest("records/TestRecordAnno"); }
@Test public void testRootWithClassInner() { doTest("sealed/RootWithClassInner"); }
@Test public void testRootWithInterfaceInner() { doTest("sealed/RootWithInterfaceInner"); }
@Test public void testRootWithClassOuter() { doTest("sealed/RootWithClassOuter",
"sealed/ClassExtends", "sealed/ClassNonSealed", "sealed/ClassNonSealedExtendsImplements");
}
@Test public void testRootWithInterfaceOuter() { doTest("sealed/RootWithInterfaceOuter",
"sealed/ClassImplements", "sealed/InterfaceNonSealed", "sealed/ClassNonSealedExtendsImplements");
}
@Test public void testClassNonSealed() { doTest("sealed/ClassNonSealed",
"sealed/RootWithClassOuter", "sealed/ClassExtends", "sealed/ClassNonSealedExtendsImplements");
}
@Test public void testClassNonSealedExtendsImplements() { doTest("sealed/ClassNonSealedExtendsImplements",
"sealed/RootWithClassOuter", "sealed/ClassExtends", "sealed/ClassNonSealed");
}
@Test public void testInterfaceNonSealed() { doTest("sealed/InterfaceNonSealed",
"sealed/RootWithInterfaceOuter", "sealed/ClassImplements", "sealed/ClassNonSealedExtendsImplements");
}
@Test public void testRootWithModule() { doTest("sealed/foo/RootWithModule", "sealed/bar/BarClassExtends");}
@Test public void testInheritanceChainCycle() { doTest("pkg/TestInheritanceChainCycle"); }
@Test public void testDynamicConstantPoolEntry() { doTest("java11/TestDynamicConstantPoolEntry"); }
@@ -0,0 +1,5 @@
package sealed;
non-sealed class ClassNonSealed extends RootWithClassOuter {
}
@@ -0,0 +1,5 @@
package sealed;
non-sealed class ClassNonSealedExtendsImplements extends RootWithClassOuter implements RootWithInterfaceOuter {
}
@@ -0,0 +1,5 @@
package sealed;
non-sealed interface InterfaceNonSealed extends RootWithInterfaceOuter {
}
@@ -0,0 +1,7 @@
package sealed;
sealed class RootWithClassInner {
static final class Inner extends RootWithClassInner {
}
}
@@ -0,0 +1,5 @@
package sealed;
abstract sealed class RootWithClassOuter permits ClassExtends, ClassNonSealed, ClassNonSealedExtendsImplements {
}
@@ -0,0 +1,7 @@
package sealed;
sealed interface RootWithInterfaceInner {
public static final class Inner implements RootWithInterfaceInner {
}
}
@@ -0,0 +1,5 @@
package sealed;
sealed interface RootWithInterfaceOuter permits ClassImplements, InterfaceNonSealed, ClassNonSealedExtendsImplements {
}
@@ -0,0 +1,7 @@
package sealed.foo;
import sealed.bar.BarClassExtends;
public abstract sealed class RootWithModule permits BarClassExtends {
}
@@ -0,0 +1,4 @@
package sealed;
final class ClassExtends extends RootWithClassOuter {
}
@@ -0,0 +1,4 @@
package sealed;
final class FinalImplements implements RootWithInterfaceOuter {
}
@@ -0,0 +1,4 @@
package sealed;
non-sealed class ClassNonSealed extends RootWithClassOuter {
}
@@ -0,0 +1,4 @@
package sealed;
non-sealed class ClassNonSealedImplements extends RootWithClassOuter implements RootWithInterfaceOuter {
}
@@ -0,0 +1,4 @@
package sealed;
non-sealed interface InterfaceNonSealed extends RootWithInterfaceOuter {
}
@@ -0,0 +1,6 @@
package sealed;
sealed interface RootWithClassInner {
final class Inner implements RootWithClassInner {
}
}
@@ -0,0 +1,4 @@
package sealed;
abstract sealed class RootWithClassOuter permits ClassExtends, ClassNonSealed, ClassNonSealedImplements {
}
@@ -0,0 +1,6 @@
package sealed;
sealed interface RootWithInterfaceInner {
final class Inner implements RootWithInterfaceInner {
}
}
@@ -0,0 +1,4 @@
package sealed;
sealed interface RootWithInterfaceOuter permits FinalImplements, InterfaceNonSealed, ClassNonSealedImplements {
}
@@ -0,0 +1,6 @@
package sealed.bar;
import sealed.foo.RootWithModule;
final public class BarClassExtends extends RootWithModule {
}
@@ -0,0 +1,6 @@
package sealed.foo;
import sealed.bar.BarClassExtends;
public abstract sealed class RootWithModule permits BarClassExtends {
}