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