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    }