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