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.jet.codegen.binding.CodegenBinding; 027 import org.jetbrains.jet.codegen.context.CodegenContext; 028 import org.jetbrains.jet.codegen.context.PackageContext; 029 import org.jetbrains.jet.codegen.state.GenerationState; 030 import org.jetbrains.jet.codegen.state.JetTypeMapper; 031 import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf; 032 import org.jetbrains.jet.descriptors.serialization.ProtoBuf; 033 import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor; 034 import org.jetbrains.jet.lang.descriptors.*; 035 import org.jetbrains.jet.lang.psi.JetFile; 036 import org.jetbrains.jet.lang.resolve.BindingContextUtils; 037 import org.jetbrains.jet.lang.resolve.DescriptorUtils; 038 import org.jetbrains.jet.lang.resolve.java.JvmAbi; 039 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils; 040 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils; 041 import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils; 042 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder; 043 import org.jetbrains.jet.lang.resolve.name.FqName; 044 import org.jetbrains.jet.lang.resolve.name.Name; 045 import org.jetbrains.org.objectweb.asm.*; 046 import org.jetbrains.org.objectweb.asm.tree.MethodNode; 047 048 import java.io.IOException; 049 import java.io.InputStream; 050 import java.util.Arrays; 051 052 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName; 053 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait; 054 055 public class InlineCodegenUtil { 056 public static final int API = Opcodes.ASM5; 057 public static final String INVOKE = "invoke"; 058 public static final boolean DEFAULT_INLINE_FLAG = true; 059 060 public static final String CAPTURED_FIELD_PREFIX = "$"; 061 062 public static final String THIS$0 = "this$0"; 063 064 public static final String RECEIVER$0 = "receiver$0"; 065 066 @Nullable 067 public static MethodNode getMethodNode( 068 InputStream classData, 069 final String methodName, 070 final String methodDescriptor 071 ) throws ClassNotFoundException, IOException { 072 ClassReader cr = new ClassReader(classData); 073 final MethodNode[] methodNode = new MethodNode[1]; 074 cr.accept(new ClassVisitor(API) { 075 076 @Override 077 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 078 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 079 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); 080 } 081 return null; 082 } 083 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 084 085 return methodNode[0]; 086 } 087 088 089 @NotNull 090 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) { 091 VirtualFile file; 092 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration(); 093 if (parentDeclaration instanceof PackageFragmentDescriptor) { 094 ProtoBuf.Callable proto = deserializedDescriptor.getProto(); 095 if (!proto.hasExtension(JavaProtoBuf.implClassName)) { 096 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor); 097 } 098 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName)); 099 FqName packagePartFqName = 100 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child( 101 name); 102 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName); 103 } else { 104 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor); 105 } 106 107 if (file == null) { 108 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName()); 109 } 110 111 return file; 112 } 113 114 @Nullable 115 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) { 116 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class); 117 return fileFinder.findVirtualFileWithHeader(containerFqName); 118 } 119 120 @Nullable 121 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) { 122 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class); 123 return fileFinder.findVirtualFile(internalName); 124 } 125 126 //TODO: navigate to inner classes 127 @Nullable 128 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) { 129 ClassOrPackageFragmentDescriptor 130 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false); 131 if (containerDescriptor instanceof PackageFragmentDescriptor) { 132 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe()); 133 } 134 if (containerDescriptor instanceof ClassDescriptor) { 135 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor)); 136 if (isTrait(containerDescriptor)) { 137 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX)); 138 } 139 return fqName; 140 } 141 return null; 142 } 143 144 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) { 145 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper); 146 } 147 148 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) { 149 if (currentDescriptor instanceof PackageFragmentDescriptor) { 150 PsiFile file = getContainingFile(codegenContext, typeMapper); 151 152 Type packagePartType; 153 if (file == null) { 154 //in case package fragment clinit 155 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext; 156 packagePartType = ((PackageContext) codegenContext).getPackagePartType(); 157 } else { 158 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file); 159 } 160 161 if (packagePartType == null) { 162 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 163 //noinspection ConstantConditions 164 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() ); 165 } 166 167 return packagePartType.getInternalName(); 168 } 169 else if (currentDescriptor instanceof ClassifierDescriptor) { 170 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 171 return type.getInternalName(); 172 } else if (currentDescriptor instanceof FunctionDescriptor) { 173 ClassDescriptor descriptor = 174 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor); 175 if (descriptor != null) { 176 Type type = typeMapper.mapType(descriptor); 177 return type.getInternalName(); 178 } 179 } 180 181 //TODO: add suffix for special case 182 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 183 184 //noinspection ConstantConditions 185 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix; 186 } 187 188 @Nullable 189 private static VirtualFile findVirtualFileContainingDescriptor( 190 @NotNull Project project, 191 @NotNull DeclarationDescriptor referencedDescriptor 192 ) { 193 FqName containerFqName = getContainerFqName(referencedDescriptor); 194 if (containerFqName == null) { 195 return null; 196 } 197 return findVirtualFileWithHeader(project, containerFqName); 198 } 199 200 201 public static boolean isInvokeOnLambda(String owner, String name) { 202 if (!INVOKE.equals(name)) { 203 return false; 204 } 205 206 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) { 207 if (owner.startsWith(prefix)) { 208 String suffix = owner.substring(prefix.length()); 209 if (isInteger(suffix)) { 210 return true; 211 } 212 } 213 } 214 return false; 215 } 216 217 public static boolean isLambdaConstructorCall(@NotNull String internalName, @NotNull String methodName) { 218 return "<init>".equals(methodName) && isLambdaClass(internalName); 219 } 220 221 public static boolean isLambdaClass(String internalName) { 222 String shortName = getLastNamePart(internalName); 223 int index = shortName.lastIndexOf("$"); 224 225 if (index < 0) { 226 return false; 227 } 228 229 String suffix = shortName.substring(index + 1); 230 return isInteger(suffix); 231 } 232 233 @NotNull 234 private static String getLastNamePart(@NotNull String internalName) { 235 int index = internalName.lastIndexOf("/"); 236 return index < 0 ? internalName : internalName.substring(index + 1); 237 } 238 239 @Nullable 240 public static PsiFile getContainingFile(CodegenContext codegenContext, JetTypeMapper typeMapper) { 241 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 242 PsiElement psiElement = BindingContextUtils.descriptorToDeclaration(typeMapper.getBindingContext(), contextDescriptor); 243 if (psiElement != null) { 244 return psiElement.getContainingFile(); 245 } 246 return null; 247 } 248 249 @NotNull 250 public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 251 return new MaxCalcNode(methodNode); 252 } 253 254 private static boolean isInteger(@NotNull String string) { 255 if (string.isEmpty()) { 256 return false; 257 } 258 259 for (int i = 0; i < string.length(); i++) { 260 if (!Character.isDigit(string.charAt(i))) { 261 return false; 262 } 263 } 264 265 return true; 266 } 267 268 public static boolean isCapturedFieldName(@NotNull String fieldName) { 269 // TODO: improve this heuristic 270 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) || 271 THIS$0.equals(fieldName) || 272 RECEIVER$0.equals(fieldName); 273 } 274 }