001 /* 002 * Copyright 2010-2015 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.kotlin.codegen.inline; 018 019 import com.intellij.openapi.project.Project; 020 import com.intellij.openapi.vfs.VirtualFile; 021 import com.intellij.psi.PsiElement; 022 import com.intellij.psi.PsiFile; 023 import org.jetbrains.annotations.NotNull; 024 import org.jetbrains.annotations.Nullable; 025 import org.jetbrains.annotations.TestOnly; 026 import org.jetbrains.kotlin.backend.common.output.OutputFile; 027 import org.jetbrains.kotlin.codegen.binding.CodegenBinding; 028 import org.jetbrains.kotlin.codegen.context.CodegenContext; 029 import org.jetbrains.kotlin.codegen.context.PackageContext; 030 import org.jetbrains.kotlin.codegen.state.GenerationState; 031 import org.jetbrains.kotlin.codegen.state.JetTypeMapper; 032 import org.jetbrains.kotlin.descriptors.*; 033 import org.jetbrains.kotlin.load.java.JvmAbi; 034 import org.jetbrains.kotlin.load.kotlin.DeserializedResolverUtils; 035 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils; 036 import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils; 037 import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder; 038 import org.jetbrains.kotlin.name.FqName; 039 import org.jetbrains.kotlin.name.Name; 040 import org.jetbrains.kotlin.psi.JetFile; 041 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils; 042 import org.jetbrains.kotlin.resolve.DescriptorUtils; 043 import org.jetbrains.kotlin.resolve.jvm.AsmTypes; 044 import org.jetbrains.kotlin.serialization.ProtoBuf; 045 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor; 046 import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf; 047 import org.jetbrains.org.objectweb.asm.*; 048 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter; 049 import org.jetbrains.org.objectweb.asm.tree.*; 050 import org.jetbrains.org.objectweb.asm.util.Textifier; 051 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor; 052 053 import java.io.IOException; 054 import java.io.PrintWriter; 055 import java.io.StringWriter; 056 import java.util.Arrays; 057 import java.util.ListIterator; 058 059 import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName; 060 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait; 061 062 public class InlineCodegenUtil { 063 public static final int API = Opcodes.ASM5; 064 public static final String INVOKE = "invoke"; 065 066 public static final String CAPTURED_FIELD_PREFIX = "$"; 067 068 public static final String THIS$0 = "this$0"; 069 070 public static final String RECEIVER$0 = "receiver$0"; 071 072 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$"; 073 074 public static final String ROOT_LABEL = "$$$$$ROOT$$$$$"; 075 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker"; 076 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall"; 077 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall"; 078 public static final String INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END = "goToTryCatchBlockEnd"; 079 080 @Nullable 081 public static MethodNode getMethodNode( 082 byte[] classData, 083 final String methodName, 084 final String methodDescriptor 085 ) throws ClassNotFoundException, IOException { 086 ClassReader cr = new ClassReader(classData); 087 final MethodNode[] methodNode = new MethodNode[1]; 088 cr.accept(new ClassVisitor(API) { 089 090 @Override 091 public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) { 092 if (methodName.equals(name) && methodDescriptor.equals(desc)) { 093 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions); 094 } 095 return null; 096 } 097 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); 098 099 return methodNode[0]; 100 } 101 102 103 @NotNull 104 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) { 105 VirtualFile file; 106 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration(); 107 if (parentDeclaration instanceof PackageFragmentDescriptor) { 108 ProtoBuf.Callable proto = deserializedDescriptor.getProto(); 109 if (!proto.hasExtension(JvmProtoBuf.implClassName)) { 110 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor); 111 } 112 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JvmProtoBuf.implClassName)); 113 FqName packagePartFqName = 114 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child( 115 name); 116 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName); 117 } else { 118 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor); 119 } 120 121 if (file == null) { 122 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName()); 123 } 124 125 return file; 126 } 127 128 @Nullable 129 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) { 130 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project); 131 return fileFinder.findVirtualFileWithHeader(containerFqName); 132 } 133 134 @Nullable 135 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) { 136 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project); 137 return fileFinder.findVirtualFile(internalName); 138 } 139 140 //TODO: navigate to inner classes 141 @Nullable 142 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) { 143 ClassOrPackageFragmentDescriptor 144 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false); 145 if (containerDescriptor instanceof PackageFragmentDescriptor) { 146 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe()); 147 } 148 if (containerDescriptor instanceof ClassDescriptor) { 149 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor)); 150 if (isTrait(containerDescriptor)) { 151 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX)); 152 } 153 return fqName; 154 } 155 return null; 156 } 157 158 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) { 159 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper); 160 } 161 162 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) { 163 if (currentDescriptor instanceof PackageFragmentDescriptor) { 164 PsiFile file = getContainingFile(codegenContext); 165 166 Type packagePartType; 167 if (file == null) { 168 //in case package fragment clinit 169 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext; 170 packagePartType = ((PackageContext) codegenContext).getPackagePartType(); 171 } else { 172 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file); 173 } 174 175 if (packagePartType == null) { 176 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 177 //noinspection ConstantConditions 178 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() ); 179 } 180 181 return packagePartType.getInternalName(); 182 } 183 else if (currentDescriptor instanceof ClassifierDescriptor) { 184 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor); 185 return type.getInternalName(); 186 } else if (currentDescriptor instanceof FunctionDescriptor) { 187 ClassDescriptor descriptor = 188 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor); 189 if (descriptor != null) { 190 Type type = typeMapper.mapType(descriptor); 191 return type.getInternalName(); 192 } 193 } 194 195 //TODO: add suffix for special case 196 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString(); 197 198 //noinspection ConstantConditions 199 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix; 200 } 201 202 @Nullable 203 private static VirtualFile findVirtualFileContainingDescriptor( 204 @NotNull Project project, 205 @NotNull DeclarationDescriptor referencedDescriptor 206 ) { 207 FqName containerFqName = getContainerFqName(referencedDescriptor); 208 if (containerFqName == null) { 209 return null; 210 } 211 return findVirtualFileWithHeader(project, containerFqName); 212 } 213 214 215 public static boolean isInvokeOnLambda(String owner, String name) { 216 if (!INVOKE.equals(name)) { 217 return false; 218 } 219 220 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) { 221 if (owner.startsWith(prefix)) { 222 String suffix = owner.substring(prefix.length()); 223 if (isInteger(suffix)) { 224 return true; 225 } 226 } 227 } 228 return false; 229 } 230 231 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) { 232 return "<init>".equals(methodName) && isAnonymousClass(internalName); 233 } 234 235 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) { 236 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName); 237 } 238 239 public static boolean isAnonymousClass(String internalName) { 240 String shortName = getLastNamePart(internalName); 241 int index = shortName.lastIndexOf("$"); 242 243 if (index < 0) { 244 return false; 245 } 246 247 String suffix = shortName.substring(index + 1); 248 return isInteger(suffix); 249 } 250 251 @NotNull 252 private static String getLastNamePart(@NotNull String internalName) { 253 int index = internalName.lastIndexOf("/"); 254 return index < 0 ? internalName : internalName.substring(index + 1); 255 } 256 257 @Nullable 258 public static PsiFile getContainingFile(CodegenContext codegenContext) { 259 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor(); 260 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor); 261 if (psiElement != null) { 262 return psiElement.getContainingFile(); 263 } 264 return null; 265 } 266 267 @NotNull 268 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) { 269 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode); 270 } 271 272 private static boolean isInteger(@NotNull String string) { 273 if (string.isEmpty()) { 274 return false; 275 } 276 277 for (int i = 0; i < string.length(); i++) { 278 if (!Character.isDigit(string.charAt(i))) { 279 return false; 280 } 281 } 282 283 return true; 284 } 285 286 public static boolean isCapturedFieldName(@NotNull String fieldName) { 287 // TODO: improve this heuristic 288 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) || 289 THIS$0.equals(fieldName) || 290 RECEIVER$0.equals(fieldName); 291 } 292 293 public static boolean isReturnOpcode(int opcode) { 294 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN; 295 } 296 297 //marked return could be either non-local or local in case of labeled lambda self-returns 298 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { 299 assert isReturnOpcode(returnIns.getOpcode()) : "Should be called on return instruction, but " + returnIns; 300 AbstractInsnNode globalFlag = returnIns.getPrevious(); 301 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); 302 } 303 304 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { 305 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false); 306 } 307 308 public static Type getReturnType(int opcode) { 309 switch (opcode) { 310 case Opcodes.RETURN: return Type.VOID_TYPE; 311 case Opcodes.IRETURN: return Type.INT_TYPE; 312 case Opcodes.DRETURN: return Type.DOUBLE_TYPE; 313 case Opcodes.FRETURN: return Type.FLOAT_TYPE; 314 case Opcodes.LRETURN: return Type.LONG_TYPE; 315 default: return AsmTypes.OBJECT_TYPE; 316 } 317 } 318 319 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) { 320 InsnList instructions = to.instructions; 321 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator(); 322 while (iterator.hasNext()) { 323 AbstractInsnNode next = iterator.next(); 324 instructions.insertBefore(beforeNode, next); 325 } 326 } 327 328 329 public static MethodNode createEmptyMethodNode() { 330 return new MethodNode(API, 0, "fake", "()V", null, null); 331 } 332 333 private static boolean isLastGoto(@NotNull AbstractInsnNode insnNode, @NotNull AbstractInsnNode stopAt) { 334 if (insnNode.getOpcode() == Opcodes.GOTO) { 335 insnNode = insnNode.getNext(); 336 while (insnNode != stopAt && isLineNumberOrLabel(insnNode)) { 337 insnNode = insnNode.getNext(); 338 } 339 return stopAt == insnNode; 340 } 341 return false; 342 } 343 344 static boolean isLineNumberOrLabel(@Nullable AbstractInsnNode node) { 345 return node instanceof LineNumberNode || node instanceof LabelNode; 346 } 347 348 349 @NotNull 350 public static LabelNode firstLabelInChain(@NotNull LabelNode node) { 351 LabelNode curNode = node; 352 while (curNode.getPrevious() instanceof LabelNode) { 353 curNode = (LabelNode) curNode.getPrevious(); 354 } 355 return curNode; 356 } 357 358 @NotNull 359 public static String getNodeText(@Nullable MethodNode node) { 360 return getNodeText(node, new Textifier()); 361 } 362 363 @NotNull 364 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) { 365 if (node == null) { 366 return "Not generated"; 367 } 368 node.accept(new TraceMethodVisitor(textifier)); 369 StringWriter sw = new StringWriter(); 370 textifier.print(new PrintWriter(sw)); 371 sw.flush(); 372 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString(); 373 } 374 375 @NotNull 376 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) { 377 //try to find just compiled classes then in dependencies 378 try { 379 OutputFile outputFile = state.getFactory().get(internalName + ".class"); 380 if (outputFile != null) { 381 return new ClassReader(outputFile.asByteArray()); 382 } else { 383 VirtualFile file = findVirtualFile(state.getProject(), internalName); 384 if (file == null) { 385 throw new RuntimeException("Couldn't find virtual file for " + internalName); 386 } 387 return new ClassReader(file.contentsToByteArray()); 388 } 389 } 390 catch (IOException e) { 391 throw new RuntimeException(e); 392 } 393 } 394 395 public static void generateGoToTryCatchBlockEndMarker(@NotNull InstructionAdapter v) { 396 v.invokestatic(INLINE_MARKER_CLASS_NAME, INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END, "()V", false); 397 } 398 399 public static boolean isGoToTryCatchBlockEnd(@NotNull AbstractInsnNode node) { 400 if (!(node.getPrevious() instanceof MethodInsnNode)) return false; 401 MethodInsnNode previous = (MethodInsnNode) node.getPrevious(); 402 return node.getOpcode() == Opcodes.GOTO && 403 INLINE_MARKER_CLASS_NAME.equals(previous.owner) && 404 INLINE_MARKER_GOTO_TRY_CATCH_BLOCK_END.equals(previous.name); 405 } 406 407 public static class LabelTextifier extends Textifier { 408 409 public LabelTextifier() { 410 super(API); 411 } 412 413 @Nullable 414 @TestOnly 415 @SuppressWarnings("UnusedDeclaration") 416 public String getLabelNameIfExists(@NotNull Label l) { 417 return labelNames == null ? null : labelNames.get(l); 418 } 419 } 420 421 public static void addInlineMarker( 422 @NotNull InstructionAdapter v, 423 boolean isStartNotEnd 424 ) { 425 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME, 426 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME), 427 "()V", false); 428 } 429 }