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