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