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