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    }