| /* |
| * Copyright 2010-2015 JetBrains s.r.o. |
| * |
| * 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 org.jetbrains.kotlin.codegen; |
| |
| import kotlin.collections.CollectionsKt; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.kotlin.backend.common.output.OutputFile; |
| import org.jetbrains.kotlin.name.SpecialNames; |
| import org.jetbrains.kotlin.test.ConfigurationKind; |
| import org.jetbrains.kotlin.utils.ExceptionUtilsKt; |
| import org.jetbrains.org.objectweb.asm.ClassReader; |
| import org.jetbrains.org.objectweb.asm.ClassVisitor; |
| import org.jetbrains.org.objectweb.asm.Opcodes; |
| |
| import java.lang.reflect.Field; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static org.jetbrains.org.objectweb.asm.Opcodes.*; |
| |
| public class InnerClassInfoGenTest extends CodegenTestCase { |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY); |
| loadFile(); |
| } |
| |
| @NotNull |
| @Override |
| protected String getPrefix() { |
| return "innerClassInfo"; |
| } |
| |
| public void testInnerClassInfo() { |
| InnerClassAttribute innerB = new InnerClassAttribute("A$B", "A", "B", ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| InnerClassAttribute innerC = new InnerClassAttribute("A$B$C", "A$B", "C", ACC_PUBLIC | ACC_FINAL); |
| String companionObjectDefaultName = SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT.asString(); |
| InnerClassAttribute innerACompanionObject = new InnerClassAttribute( |
| "A$" + companionObjectDefaultName, "A", companionObjectDefaultName, ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| |
| extractAndCompareInnerClasses("A", innerB, innerACompanionObject); |
| extractAndCompareInnerClasses("A$B", innerB, innerC); |
| extractAndCompareInnerClasses("A$B$C", innerB, innerC); |
| extractAndCompareInnerClasses("A$" + companionObjectDefaultName, innerACompanionObject); |
| } |
| |
| public void testLocalClass() { |
| InnerClassAttribute innerB = new InnerClassAttribute("A$foo$B", null, "B", ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| |
| extractAndCompareInnerClasses("A", innerB); |
| extractAndCompareInnerClasses("A$foo$B", innerB); |
| } |
| |
| public void testAnonymousClass() { |
| InnerClassAttribute innerB = new InnerClassAttribute("A$B$1", null, null, ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| InnerClassAttribute innerC = new InnerClassAttribute("A$foo$C$1", null, null, ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| |
| extractAndCompareInnerClasses("A", innerB, innerC); |
| extractAndCompareInnerClasses("A$B$1", innerB); |
| extractAndCompareInnerClasses("A$foo$C$1", innerC); |
| } |
| |
| public void testAnonymousObjectInline() { |
| InnerClassAttribute objectInInlineFun = new InnerClassAttribute("A$inlineFun$s$1", null, null, ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| extractAndCompareInnerClasses("A", objectInInlineFun); |
| } |
| |
| public void testEnumEntry() { |
| InnerClassAttribute innerE2 = new InnerClassAttribute("E$E2", "E", "E2", ACC_STATIC | ACC_FINAL); |
| |
| extractAndCompareInnerClasses("E", innerE2); |
| extractAndCompareInnerClasses("E$E2", innerE2); |
| } |
| |
| public void testInnerAccessFlags() { |
| checkAccess("A", "Annotation", ACC_PUBLIC | ACC_STATIC | ACC_INTERFACE | ACC_ABSTRACT | ACC_ANNOTATION); |
| checkAccess("A", "Enum", ACC_PUBLIC | ACC_STATIC | ACC_FINAL | ACC_ENUM); |
| checkAccess("A", "Trait", ACC_PUBLIC | ACC_STATIC | ACC_INTERFACE | ACC_ABSTRACT); |
| checkAccess("A$Trait", "DefaultImpls", ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| |
| checkAccess("A", "OpenStaticClass", ACC_PUBLIC | ACC_STATIC); |
| checkAccess("A", "FinalStaticClass", ACC_PUBLIC | ACC_STATIC | ACC_FINAL); |
| checkAccess("A", "AbstractStaticClass", ACC_PUBLIC | ACC_STATIC | ACC_ABSTRACT); |
| checkAccess("A", "OpenInnerClass", ACC_PUBLIC); |
| checkAccess("A", "FinalInnerClass", ACC_PUBLIC | ACC_FINAL); |
| checkAccess("A", "AbstractInnerClass", ACC_PUBLIC | ACC_ABSTRACT); |
| |
| checkAccess("A", "PrivateClass", ACC_PRIVATE); |
| checkAccess("A", "ProtectedClass", ACC_PROTECTED); |
| checkAccess("A", "InternalClass", ACC_PUBLIC); |
| checkAccess("A", "PublicClass", ACC_PUBLIC); |
| } |
| |
| public void testLambdaClassFlags() { |
| InnerClassAttribute foo = new InnerClassAttribute("A$foo$1", null, null, ACC_STATIC | ACC_FINAL); |
| InnerClassAttribute bar = new InnerClassAttribute("A$bar$1", null, null, ACC_STATIC | ACC_FINAL); |
| |
| extractAndCompareInnerClasses("A", foo, bar); |
| extractAndCompareInnerClasses("A$foo$1", foo); |
| extractAndCompareInnerClasses("A$bar$1", bar); |
| } |
| |
| |
| |
| private void checkAccess(@NotNull String outerName, @NotNull String innerName, int accessFlags) { |
| String name = outerName + "$" + innerName; |
| InnerClassAttribute attribute = CollectionsKt.single(extractInnerClasses(name), value -> innerName.equals(value.innerName)); |
| |
| InnerClassAttribute expectedAttribute = new InnerClassAttribute(name, outerName, innerName, accessFlags); |
| |
| assertEquals(expectedAttribute, attribute); |
| } |
| |
| private void extractAndCompareInnerClasses(@NotNull String className, @NotNull InnerClassAttribute... expectedInnerClasses) { |
| assertSameElements(extractInnerClasses(className), expectedInnerClasses); |
| } |
| |
| @NotNull |
| private List<InnerClassAttribute> extractInnerClasses(@NotNull String className) { |
| OutputFile outputFile = generateClassesInFile().get(className + ".class"); |
| assertNotNull(outputFile); |
| byte[] bytes = outputFile.asByteArray(); |
| ClassReader reader = new ClassReader(bytes); |
| List<InnerClassAttribute> result = new ArrayList<>(); |
| |
| reader.accept(new ClassVisitor(ASM5) { |
| @Override |
| public void visitInnerClass(@NotNull String name, String outerName, String innerName, int access) { |
| result.add(new InnerClassAttribute(name, outerName, innerName, access)); |
| } |
| }, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); |
| |
| return result; |
| } |
| |
| private static class InnerClassAttribute { |
| private final String name; |
| private final String outerName; |
| private final String innerName; |
| private final int access; |
| |
| private InnerClassAttribute(@NotNull String name, @Nullable String outerName, @Nullable String innerName, int access) { |
| this.name = name; |
| this.outerName = outerName; |
| this.innerName = innerName; |
| this.access = access; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) return true; |
| if (o == null || getClass() != o.getClass()) return false; |
| |
| InnerClassAttribute attribute = (InnerClassAttribute) o; |
| |
| if (!name.equals(attribute.name)) return false; |
| if (outerName != null ? !outerName.equals(attribute.outerName) : attribute.outerName != null) return false; |
| if (innerName != null ? !innerName.equals(attribute.innerName) : attribute.innerName != null) return false; |
| if (access != attribute.access) return false; |
| |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = name.hashCode(); |
| result = 31 * result + (outerName != null ? outerName.hashCode() : 0); |
| result = 31 * result + (innerName != null ? innerName.hashCode() : 0); |
| result = 31 * result + access; |
| return result; |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("InnerClass(name=%s, outerName=%s, innerName=%s, access=%s)", |
| name, outerName, innerName, renderAccess(access)); |
| } |
| |
| @NotNull |
| private static String renderAccess(int access) { |
| try { |
| StringBuilder sb = new StringBuilder(); |
| for (Field field : Opcodes.class.getDeclaredFields()) { |
| String name = field.getName(); |
| if (name.startsWith("ACC_") && (access & field.getInt(null)) != 0) { |
| sb.append("|"); |
| sb.append(name); |
| } |
| } |
| String result = sb.toString(); |
| return result.isEmpty() ? "<empty>" : result.substring(1); |
| } |
| catch (Exception e) { |
| throw ExceptionUtilsKt.rethrow(e); |
| } |
| } |
| } |
| } |