001 /* 002 * Copyright 2010-2013 JetBrains s.r.o. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017 package org.jetbrains.jet.codegen.inline; 018 019 import com.intellij.openapi.components.ServiceManager; 020 import com.intellij.openapi.project.Project; 021 import com.intellij.openapi.vfs.VirtualFile; 022 import com.intellij.psi.PsiElement; 023 import com.intellij.psi.PsiFile; 024 import org.jetbrains.annotations.NotNull; 025 import org.jetbrains.annotations.Nullable; 026 import org.jetbrains.asm4.*; 027 import org.jetbrains.asm4.tree.MethodNode; 028 import org.jetbrains.jet.codegen.PackageCodegen; 029 import org.jetbrains.jet.codegen.binding.CodegenBinding; 030 import org.jetbrains.jet.codegen.context.CodegenContext; 031 import org.jetbrains.jet.codegen.context.PackageContext; 032 import org.jetbrains.jet.codegen.state.GenerationState; 033 import org.jetbrains.jet.codegen.state.JetTypeMapper; 034 import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf; 035 import org.jetbrains.jet.descriptors.serialization.ProtoBuf; 036 import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor; 037 import org.jetbrains.jet.lang.descriptors.*; 038 import org.jetbrains.jet.lang.resolve.BindingContextUtils; 039 import org.jetbrains.jet.lang.resolve.DescriptorUtils; 040 import org.jetbrains.jet.lang.resolve.java.JvmAbi; 041 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 042 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils; 043 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder; 044 import org.jetbrains.jet.lang.resolve.name.FqName; 045 import org.jetbrains.jet.lang.resolve.name.Name; 046 047 import java.io.IOException; 048 import java.io.InputStream; 049 import java.util.Arrays; 050 051 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName; 052 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait; 053 054 public class InlineCodegenUtil { 055 056 public final static int API = Opcodes.ASM4; 057 058 public final static String INVOKE = "invoke"; 059 060 @Nullable 061 public static MethodNode getMethodNode( 062 InputStream classData, 063 final String methodName, 064 final String methodDescriptor 065 ) throws ClassNotFoundException, IOException { 066 ClassReader cr = new ClassReader(classData); 067 final MethodNode[] methodNode = new MethodNode[1]; 068 cr.accept(new ClassVisitor(API) { 069 070 @Override 071 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 072 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 073 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); 074 } 075 return null; 076 } 077 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 078 079 return methodNode[0]; 080 } 081 082 083 @NotNull 084 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) { 085 VirtualFile file; 086 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration(); 087 if (parentDeclaration instanceof PackageFragmentDescriptor) { 088 ProtoBuf.Callable proto = deserializedDescriptor.getFunctionProto(); 089 if (!proto.hasExtension(JavaProtoBuf.implClassName)) { 090 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor); 091 } 092 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName)); 093 FqName packagePartFqName = 094 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child( 095 name); 096 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName); 097 } else { 098 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor); 099 } 100 101 if (file == null) { 102 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName()); 103 } 104 105 return file; 106 } 107 108 @Nullable 109 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) { 110 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class); 111 return fileFinder.findVirtualFileWithHeader(containerFqName); 112 } 113 114 @Nullable 115 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) { 116 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class); 117 return fileFinder.findVirtualFile(internalName); 118 } 119 120 //TODO: navigate to inner classes 121 @Nullable 122 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) { 123 ClassOrPackageFragmentDescriptor 124 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false); 125 if (containerDescriptor instanceof PackageFragmentDescriptor) { 126 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe()); 127 } 128 if (containerDescriptor instanceof ClassDescriptor) { 129 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor)); 130 if (isTrait(containerDescriptor)) { 131 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX)); 132 } 133 return fqName; 134 } 135 return null; 136 } 137 138 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) { 139 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper); 140 } 141 142 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) { 143 if (currentDescriptor instanceof PackageFragmentDescriptor) { 144 PsiFile file = getContainingFile(codegenContext, typeMapper); 145 146 Type packagePartType; 147 if (file == null) { 148 //in case package fragment clinit 149 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext; 150 packagePartType = ((PackageContext) codegenContext).getPackagePartType(); 151 } else { 152 packagePartType = 153 PackageCodegen.getPackagePartType(PackageClassUtils.getPackageClassFqName(getFqName(currentDescriptor).toSafe()), 154 file.getVirtualFile()); 155 } 156 157 if (packagePartType == null) { 158 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 159 //noinspection ConstantConditions 160 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() ); 161 } 162 163 return packagePartType.getInternalName(); 164 } 165 else if (currentDescriptor instanceof ClassifierDescriptor) { 166 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 167 return type.getInternalName(); 168 } else if (currentDescriptor instanceof FunctionDescriptor) { 169 ClassDescriptor descriptor = 170 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor); 171 if (descriptor != null) { 172 Type type = typeMapper.mapType(descriptor); 173 return type.getInternalName(); 174 } 175 } 176 177 //TODO: add suffix for special case 178 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 179 180 //noinspection ConstantConditions 181 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix; 182 } 183 184 @Nullable 185 private static VirtualFile findVirtualFileContainingDescriptor( 186 @NotNull Project project, 187 @NotNull DeclarationDescriptor referencedDescriptor 188 ) { 189 FqName containerFqName = getContainerFqName(referencedDescriptor); 190 if (containerFqName == null) { 191 return null; 192 } 193 return findVirtualFileWithHeader(project, containerFqName); 194 } 195 196 197 public static boolean isInvokeOnLambda(String owner, String name) { 198 if (!INVOKE.equals(name)) { 199 return false; 200 } 201 202 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) { 203 if (owner.startsWith(prefix)) { 204 String suffix = owner.substring(prefix.length()); 205 if (isInteger(suffix)) { 206 return true; 207 } 208 } 209 } 210 return false; 211 } 212 213 public static boolean isLambdaConstructorCall(@NotNull String internalName, @NotNull String methodName) { 214 return "<init>".equals(methodName) && isLambdaClass(internalName); 215 } 216 217 public static boolean isLambdaClass(String internalName) { 218 String shortName = getLastNamePart(internalName); 219 int index = shortName.lastIndexOf("$"); 220 221 if (index < 0) { 222 return false; 223 } 224 225 String suffix = shortName.substring(index + 1); 226 return isInteger(suffix); 227 } 228 229 @NotNull 230 private static String getLastNamePart(@NotNull String internalName) { 231 int index = internalName.lastIndexOf("/"); 232 return index < 0 ? internalName : internalName.substring(index + 1); 233 } 234 235 @Nullable 236 public static PsiFile getContainingFile(CodegenContext codegenContext, JetTypeMapper typeMapper) { 237 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 238 PsiElement psiElement = BindingContextUtils.descriptorToDeclaration(typeMapper.getBindingContext(), contextDescriptor); 239 if (psiElement != null) { 240 return psiElement.getContainingFile(); 241 } 242 return null; 243 } 244 245 @NotNull 246 public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 247 return new MaxCalcNode(methodNode); 248 } 249 250 private static boolean isInteger(@NotNull String string) { 251 if (string.isEmpty()) { 252 return false; 253 } 254 255 for (int i = 0; i < string.length(); i++) { 256 if (!Character.isDigit(string.charAt(i))) { 257 return false; 258 } 259 } 260 261 return true; 262 } 263 }