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.MemberCodegen;
028 import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
029 import org.jetbrains.kotlin.codegen.context.*;
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.fileClasses.FileClassesPackage;
034 import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
035 import org.jetbrains.kotlin.load.java.JvmAbi;
036 import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder;
037 import org.jetbrains.kotlin.load.kotlin.PackageClassUtils;
038 import org.jetbrains.kotlin.name.ClassId;
039 import org.jetbrains.kotlin.name.FqName;
040 import org.jetbrains.kotlin.name.Name;
041 import org.jetbrains.kotlin.psi.JetFile;
042 import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
043 import org.jetbrains.kotlin.resolve.DescriptorUtils;
044 import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilPackage;
045 import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
046 import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
047 import org.jetbrains.kotlin.serialization.ProtoBuf;
048 import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor;
049 import org.jetbrains.kotlin.serialization.jvm.JvmProtoBuf;
050 import org.jetbrains.kotlin.types.expressions.OperatorConventions;
051 import org.jetbrains.org.objectweb.asm.*;
052 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
053 import org.jetbrains.org.objectweb.asm.tree.*;
054 import org.jetbrains.org.objectweb.asm.util.Textifier;
055 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
056
057 import java.io.IOException;
058 import java.io.PrintWriter;
059 import java.io.StringWriter;
060 import java.util.ListIterator;
061
062 import static kotlin.KotlinPackage.substringAfterLast;
063 import static org.jetbrains.kotlin.resolve.DescriptorUtils.getFqName;
064 import static org.jetbrains.kotlin.resolve.DescriptorUtils.isTrait;
065
066 public class InlineCodegenUtil {
067 public static final boolean GENERATE_SMAP = true;
068 public static final int API = Opcodes.ASM5;
069
070 public static final String CAPTURED_FIELD_PREFIX = "$";
071 public static final String THIS$0 = "this$0";
072 public static final String RECEIVER$0 = "receiver$0";
073 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
074 public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$";
075 public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function";
076 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker";
077 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall";
078 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall";
079 public static final String INLINE_MARKER_FINALLY_START = "finallyStart";
080 public static final String INLINE_MARKER_FINALLY_END = "finallyEnd";
081
082 @Nullable
083 public static SMAPAndMethodNode getMethodNode(
084 byte[] classData,
085 final String methodName,
086 final String methodDescriptor,
087 ClassId classId
088 ) throws ClassNotFoundException, IOException {
089 ClassReader cr = new ClassReader(classData);
090 final MethodNode[] node = new MethodNode[1];
091 final String[] debugInfo = new String[2];
092 final int[] lines = new int[2];
093 lines[0] = Integer.MAX_VALUE;
094 lines[1] = Integer.MIN_VALUE;
095 cr.accept(new ClassVisitor(API) {
096
097 @Override
098 public void visitSource(String source, String debug) {
099 super.visitSource(source, debug);
100 debugInfo[0] = source;
101 debugInfo[1] = debug;
102 }
103
104 @Override
105 public MethodVisitor visitMethod(
106 int access,
107 @NotNull String name,
108 @NotNull String desc,
109 String signature,
110 String[] exceptions
111 ) {
112 if (methodName.equals(name) && methodDescriptor.equals(desc)) {
113 node[0] = new MethodNode(API, access, name, desc, signature, exceptions) {
114 @Override
115 public void visitLineNumber(int line, @NotNull Label start) {
116 super.visitLineNumber(line, start);
117 lines[0] = Math.min(lines[0], line);
118 lines[1] = Math.max(lines[1], line);
119 }
120 };
121 return node[0];
122 }
123 return null;
124 }
125 }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG));
126
127 SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.toString(), lines[0], lines[1]);
128 return new SMAPAndMethodNode(node[0], smap);
129 }
130
131 public static void initDefaultSourceMappingIfNeeded(@NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state) {
132 if (state.isInlineEnabled()) {
133 CodegenContext<?> parentContext = context.getParentContext();
134 while (parentContext != null) {
135 if (parentContext instanceof MethodContext) {
136 if (((MethodContext) parentContext).isInlineFunction()) {
137 //just init default one to one mapping
138 codegen.getOrCreateSourceMapper();
139 break;
140 }
141 }
142 parentContext = parentContext.getParentContext();
143 }
144 }
145 }
146
147 @NotNull
148 public static VirtualFile getVirtualFileForCallable(@NotNull ClassId containerClassId, @NotNull GenerationState state) {
149 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(state.getProject());
150 VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId);
151 if (file == null) {
152 throw new IllegalStateException("Couldn't find declaration file for " + containerClassId);
153 }
154 return file;
155 }
156
157 public static ClassId getContainerClassIdForInlineCallable(DeserializedSimpleFunctionDescriptor deserializedDescriptor) {
158 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration();
159 ClassId containerClassId;
160 if (parentDeclaration instanceof PackageFragmentDescriptor) {
161 ProtoBuf.Callable proto = deserializedDescriptor.getProto();
162 if (!proto.hasExtension(JvmProtoBuf.implClassName)) {
163 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor);
164 }
165 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JvmProtoBuf.implClassName));
166 containerClassId = new ClassId(((PackageFragmentDescriptor) parentDeclaration).getFqName(), name);
167 } else {
168 containerClassId = getContainerClassId(deserializedDescriptor);
169 }
170 if (containerClassId == null) {
171 throw new IllegalStateException("Couldn't find container FQName for " + deserializedDescriptor.getName());
172 }
173 return containerClassId;
174 }
175
176 @Nullable
177 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalClassName) {
178 FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
179 String classNameWithDollars = substringAfterLast(internalClassName, "/", internalClassName);
180 JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project);
181 //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
182 // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE
183 return fileFinder.findVirtualFileWithHeader(new ClassId(packageFqName, Name.identifier(classNameWithDollars)));
184 }
185
186 //TODO: navigate to inner classes
187 @Nullable
188 public static ClassId getContainerClassId(@NotNull DeclarationDescriptor referencedDescriptor) {
189 ClassOrPackageFragmentDescriptor
190 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
191 if (containerDescriptor instanceof PackageFragmentDescriptor) {
192 return PackageClassUtils.getPackageClassId(getFqName(containerDescriptor).toSafe());
193 }
194 if (containerDescriptor instanceof ClassDescriptor) {
195 ClassId classId = DescriptorUtilPackage.getClassId((ClassDescriptor) containerDescriptor);
196 if (isTrait(containerDescriptor)) {
197 FqName relativeClassName = classId.getRelativeClassName();
198 //TODO test nested trait fun inlining
199 classId = new ClassId(classId.getPackageFqName(), Name.identifier(relativeClassName.shortName().asString() + JvmAbi.TRAIT_IMPL_SUFFIX));
200 }
201 return classId;
202 }
203 return null;
204 }
205
206 public static String getInlineName(
207 @NotNull CodegenContext codegenContext,
208 @NotNull JetTypeMapper typeMapper,
209 @NotNull JvmFileClassesProvider fileClassesManager
210 ) {
211 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager);
212 }
213
214 private static String getInlineName(
215 @NotNull CodegenContext codegenContext,
216 @NotNull DeclarationDescriptor currentDescriptor,
217 @NotNull JetTypeMapper typeMapper,
218 @NotNull JvmFileClassesProvider fileClassesProvider
219 ) {
220 if (currentDescriptor instanceof PackageFragmentDescriptor) {
221 PsiFile file = getContainingFile(codegenContext);
222
223 Type implementationOwnerType;
224 if (file == null) {
225 implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext);
226 } else {
227 implementationOwnerType = FileClassesPackage.getFileClassType(fileClassesProvider, (JetFile) file);
228 }
229
230 if (implementationOwnerType == null) {
231 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
232 //noinspection ConstantConditions
233 throw new RuntimeException("Couldn't find declaration for " +
234 contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() +
235 "; context: " + codegenContext);
236 }
237
238 return implementationOwnerType.getInternalName();
239 }
240 else if (currentDescriptor instanceof ClassifierDescriptor) {
241 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
242 return type.getInternalName();
243 } else if (currentDescriptor instanceof FunctionDescriptor) {
244 ClassDescriptor descriptor =
245 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor);
246 if (descriptor != null) {
247 Type type = typeMapper.mapType(descriptor);
248 return type.getInternalName();
249 }
250 }
251
252 //TODO: add suffix for special case
253 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
254
255 //noinspection ConstantConditions
256 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix;
257 }
258
259 public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) {
260 return OperatorConventions.INVOKE.asString().equals(name) &&
261 owner.startsWith(NUMBERED_FUNCTION_PREFIX) &&
262 isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length()));
263 }
264
265 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
266 return "<init>".equals(methodName) && isAnonymousClass(internalName);
267 }
268
269 public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) {
270 return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName);
271 }
272
273 public static boolean isAnonymousClass(String internalName) {
274 String shortName = getLastNamePart(internalName);
275 int index = shortName.lastIndexOf("$");
276
277 if (index < 0) {
278 return false;
279 }
280
281 String suffix = shortName.substring(index + 1);
282 return isInteger(suffix);
283 }
284
285 @NotNull
286 private static String getLastNamePart(@NotNull String internalName) {
287 int index = internalName.lastIndexOf("/");
288 return index < 0 ? internalName : internalName.substring(index + 1);
289 }
290
291 @Nullable
292 public static PsiFile getContainingFile(CodegenContext codegenContext) {
293 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
294 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
295 if (psiElement != null) {
296 return psiElement.getContainingFile();
297 }
298 return null;
299 }
300
301 @NotNull
302 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
303 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
304 }
305
306 private static boolean isInteger(@NotNull String string) {
307 if (string.isEmpty()) {
308 return false;
309 }
310
311 for (int i = 0; i < string.length(); i++) {
312 if (!Character.isDigit(string.charAt(i))) {
313 return false;
314 }
315 }
316
317 return true;
318 }
319
320 public static boolean isCapturedFieldName(@NotNull String fieldName) {
321 // TODO: improve this heuristic
322 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) ||
323 THIS$0.equals(fieldName) ||
324 RECEIVER$0.equals(fieldName);
325 }
326
327 public static boolean isReturnOpcode(int opcode) {
328 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
329 }
330
331 //marked return could be either non-local or local in case of labeled lambda self-returns
332 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
333 if (!isReturnOpcode(returnIns.getOpcode())) {
334 return false;
335 }
336 AbstractInsnNode globalFlag = returnIns.getPrevious();
337 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner);
338 }
339
340 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
341 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
342 }
343
344 public static Type getReturnType(int opcode) {
345 switch (opcode) {
346 case Opcodes.RETURN: return Type.VOID_TYPE;
347 case Opcodes.IRETURN: return Type.INT_TYPE;
348 case Opcodes.DRETURN: return Type.DOUBLE_TYPE;
349 case Opcodes.FRETURN: return Type.FLOAT_TYPE;
350 case Opcodes.LRETURN: return Type.LONG_TYPE;
351 default: return AsmTypes.OBJECT_TYPE;
352 }
353 }
354
355 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) {
356 InsnList instructions = to.instructions;
357 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator();
358 while (iterator.hasNext()) {
359 AbstractInsnNode next = iterator.next();
360 instructions.insertBefore(beforeNode, next);
361 }
362 }
363
364
365 public static MethodNode createEmptyMethodNode() {
366 return new MethodNode(API, 0, "fake", "()V", null, null);
367 }
368
369 @NotNull
370 public static LabelNode firstLabelInChain(@NotNull LabelNode node) {
371 LabelNode curNode = node;
372 while (curNode.getPrevious() instanceof LabelNode) {
373 curNode = (LabelNode) curNode.getPrevious();
374 }
375 return curNode;
376 }
377
378 @NotNull
379 public static String getNodeText(@Nullable MethodNode node) {
380 return getNodeText(node, new Textifier());
381 }
382
383 @NotNull
384 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) {
385 if (node == null) {
386 return "Not generated";
387 }
388 node.accept(new TraceMethodVisitor(textifier));
389 StringWriter sw = new StringWriter();
390 textifier.print(new PrintWriter(sw));
391 sw.flush();
392 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString();
393 }
394
395 @NotNull
396 /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) {
397 //try to find just compiled classes then in dependencies
398 try {
399 OutputFile outputFile = state.getFactory().get(internalName + ".class");
400 if (outputFile != null) {
401 return new ClassReader(outputFile.asByteArray());
402 } else {
403 VirtualFile file = findVirtualFile(state.getProject(), internalName);
404 if (file == null) {
405 throw new RuntimeException("Couldn't find virtual file for " + internalName);
406 }
407 return new ClassReader(file.contentsToByteArray());
408 }
409 }
410 catch (IOException e) {
411 throw new RuntimeException(e);
412 }
413 }
414
415 public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) {
416 v.iconst(depth);
417 v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false);
418 }
419
420 public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) {
421 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END);
422 }
423
424 public static boolean isFinallyStart(@NotNull AbstractInsnNode node) {
425 return isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
426 }
427
428 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) {
429 return isFinallyMarker(node, INLINE_MARKER_FINALLY_END) || isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
430 }
431
432 public static boolean isFinallyMarker(@Nullable AbstractInsnNode node, String name) {
433 if (!(node instanceof MethodInsnNode)) return false;
434 MethodInsnNode method = (MethodInsnNode) node;
435 return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name);
436 }
437
438 public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) {
439 return context.isInlineFunction() || context.isInliningLambda();
440 }
441
442 public static int getConstant(AbstractInsnNode ins) {
443 int opcode = ins.getOpcode();
444 Integer value;
445 if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
446 value = opcode - Opcodes.ICONST_0;
447 }
448 else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) {
449 IntInsnNode index = (IntInsnNode) ins;
450 value = index.operand;
451 }
452 else {
453 LdcInsnNode index = (LdcInsnNode) ins;
454 value = (Integer) index.cst;
455 }
456 return value;
457 }
458
459 public static class LabelTextifier extends Textifier {
460
461 public LabelTextifier() {
462 super(API);
463 }
464
465 @Nullable
466 @TestOnly
467 @SuppressWarnings("UnusedDeclaration")
468 public String getLabelNameIfExists(@NotNull Label l) {
469 return labelNames == null ? null : labelNames.get(l);
470 }
471 }
472
473 public static void addInlineMarker(
474 @NotNull InstructionAdapter v,
475 boolean isStartNotEnd
476 ) {
477 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
478 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME),
479 "()V", false);
480 }
481
482 public static boolean isInlineMarker(AbstractInsnNode insn) {
483 return isInlineMarker(insn, null);
484 }
485
486 public static boolean isInlineMarker(AbstractInsnNode insn, String name) {
487 if (insn instanceof MethodInsnNode) {
488 MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
489 return insn.getOpcode() == Opcodes.INVOKESTATIC &&
490 methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) &&
491 (name != null ? methodInsnNode.name.equals(name)
492 : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) ||
493 methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME));
494 }
495 else {
496 return false;
497 }
498 }
499
500 public static boolean isBeforeInlineMarker(AbstractInsnNode insn) {
501 return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME);
502 }
503
504 public static boolean isAfterInlineMarker(AbstractInsnNode insn) {
505 return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME);
506 }
507
508 public static int getLoadStoreArgSize(int opcode) {
509 return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
510 }
511
512 public static boolean isStoreInstruction(int opcode) {
513 return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE;
514 }
515 }