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.components.ServiceManager;
020    import com.intellij.openapi.project.Project;
021    import com.intellij.openapi.vfs.VirtualFile;
022    import com.intellij.psi.PsiElement;
023    import com.intellij.psi.PsiFile;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
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.JvmAbi;
039    import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
040    import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils;
041    import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
042    import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder;
043    import org.jetbrains.jet.lang.resolve.name.FqName;
044    import org.jetbrains.jet.lang.resolve.name.Name;
045    import org.jetbrains.org.objectweb.asm.*;
046    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
047    import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
048    import org.jetbrains.org.objectweb.asm.tree.InsnList;
049    import org.jetbrains.org.objectweb.asm.tree.MethodNode;
050    
051    import java.io.IOException;
052    import java.io.InputStream;
053    import java.util.Arrays;
054    import java.util.ListIterator;
055    
056    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
057    import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
058    
059    public class InlineCodegenUtil {
060        public static final int API = Opcodes.ASM5;
061        public static final String INVOKE = "invoke";
062        public static final boolean DEFAULT_INLINE_FLAG = true;
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    
074        @Nullable
075        public static MethodNode getMethodNode(
076                InputStream classData,
077                final String methodName,
078                final String methodDescriptor
079        ) throws ClassNotFoundException, IOException {
080            ClassReader cr = new ClassReader(classData);
081            final MethodNode[] methodNode = new MethodNode[1];
082            cr.accept(new ClassVisitor(API) {
083    
084                @Override
085                public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
086                    if (methodName.equals(name) && methodDescriptor.equals(desc)) {
087                        return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
088                    }
089                    return null;
090                }
091            }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
092    
093            return methodNode[0];
094        }
095    
096    
097        @NotNull
098        public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) {
099            VirtualFile file;
100            DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration();
101            if (parentDeclaration instanceof PackageFragmentDescriptor) {
102                ProtoBuf.Callable proto = deserializedDescriptor.getProto();
103                if (!proto.hasExtension(JavaProtoBuf.implClassName)) {
104                    throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor);
105                }
106                Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName));
107                FqName packagePartFqName =
108                        PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child(
109                                name);
110                file = findVirtualFileWithHeader(state.getProject(), packagePartFqName);
111            } else {
112                file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor);
113            }
114    
115            if (file == null) {
116                throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName());
117            }
118    
119            return file;
120        }
121    
122        @Nullable
123        public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) {
124            VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
125            return fileFinder.findVirtualFileWithHeader(containerFqName);
126        }
127    
128        @Nullable
129        public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) {
130            VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
131            return fileFinder.findVirtualFile(internalName);
132        }
133    
134        //TODO: navigate to inner classes
135        @Nullable
136        public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) {
137            ClassOrPackageFragmentDescriptor
138                    containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
139            if (containerDescriptor instanceof PackageFragmentDescriptor) {
140                return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe());
141            }
142            if (containerDescriptor instanceof ClassDescriptor) {
143                FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor));
144                if (isTrait(containerDescriptor)) {
145                    return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX));
146                }
147                return fqName;
148            }
149            return null;
150        }
151    
152        public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) {
153            return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper);
154        }
155    
156        private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) {
157            if (currentDescriptor instanceof PackageFragmentDescriptor) {
158                PsiFile file = getContainingFile(codegenContext);
159    
160                Type packagePartType;
161                if (file == null) {
162                    //in case package fragment clinit
163                    assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext;
164                    packagePartType = ((PackageContext) codegenContext).getPackagePartType();
165                } else {
166                    packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file);
167                }
168    
169                if (packagePartType == null) {
170                    DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
171                    //noinspection ConstantConditions
172                    throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() );
173                }
174    
175                return packagePartType.getInternalName();
176            }
177            else if (currentDescriptor instanceof ClassifierDescriptor) {
178                Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
179                return type.getInternalName();
180            } else if (currentDescriptor instanceof FunctionDescriptor) {
181                ClassDescriptor descriptor =
182                        typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor);
183                if (descriptor != null) {
184                    Type type = typeMapper.mapType(descriptor);
185                    return type.getInternalName();
186                }
187            }
188    
189            //TODO: add suffix for special case
190            String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
191    
192            //noinspection ConstantConditions
193            return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix;
194        }
195    
196        @Nullable
197        private static VirtualFile findVirtualFileContainingDescriptor(
198                @NotNull Project project,
199                @NotNull DeclarationDescriptor referencedDescriptor
200        ) {
201            FqName containerFqName = getContainerFqName(referencedDescriptor);
202            if (containerFqName == null) {
203                return null;
204            }
205            return findVirtualFileWithHeader(project, containerFqName);
206        }
207    
208    
209        public static boolean isInvokeOnLambda(String owner, String name) {
210            if (!INVOKE.equals(name)) {
211                return false;
212            }
213    
214            for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) {
215                if (owner.startsWith(prefix)) {
216                    String suffix = owner.substring(prefix.length());
217                    if (isInteger(suffix)) {
218                        return true;
219                    }
220                }
221            }
222            return false;
223        }
224    
225        public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
226            return "<init>".equals(methodName) && isAnonymousClass(internalName);
227        }
228    
229        public static boolean isAnonymousClass(String internalName) {
230            String shortName = getLastNamePart(internalName);
231            int index = shortName.lastIndexOf("$");
232    
233            if (index < 0) {
234                return false;
235            }
236    
237            String suffix = shortName.substring(index + 1);
238            return isInteger(suffix);
239        }
240    
241        @NotNull
242        private static String getLastNamePart(@NotNull String internalName) {
243            int index = internalName.lastIndexOf("/");
244            return index < 0 ? internalName : internalName.substring(index + 1);
245        }
246    
247        @Nullable
248        public static PsiFile getContainingFile(CodegenContext codegenContext) {
249            DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
250            PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
251            if (psiElement != null) {
252                return psiElement.getContainingFile();
253            }
254            return null;
255        }
256    
257        @NotNull
258        public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
259            return new MaxCalcNode(methodNode);
260        }
261    
262        private static boolean isInteger(@NotNull String string) {
263            if (string.isEmpty()) {
264                return false;
265            }
266    
267            for (int i = 0; i < string.length(); i++) {
268                 if (!Character.isDigit(string.charAt(i))) {
269                     return false;
270                 }
271            }
272    
273            return true;
274        }
275    
276        public static boolean isCapturedFieldName(@NotNull String fieldName) {
277            // TODO: improve this heuristic
278            return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) ||
279                   THIS$0.equals(fieldName) ||
280                   RECEIVER$0.equals(fieldName);
281        }
282    
283        public static boolean isReturnOpcode(int opcode) {
284            return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
285        }
286    
287        public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
288            iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
289        }
290    
291        public static Type getReturnType(int opcode) {
292            switch (opcode) {
293                case Opcodes.RETURN: return Type.VOID_TYPE;
294                case Opcodes.IRETURN: return Type.INT_TYPE;
295                case Opcodes.DRETURN: return Type.DOUBLE_TYPE;
296                case Opcodes.FRETURN: return Type.FLOAT_TYPE;
297                case Opcodes.LRETURN: return Type.LONG_TYPE;
298                default: return Type.getObjectType("object");
299            }
300        }
301    
302        public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode afterNode) {
303            InsnList instructions = to.instructions;
304            ListIterator<AbstractInsnNode> iterator = from.instructions.iterator();
305            while (iterator.hasNext()) {
306                AbstractInsnNode next = iterator.next();
307                instructions.insertBefore(afterNode, next);
308            }
309        }
310    
311    
312        public static MethodNode createEmptyMethodNode() {
313            return new MethodNode(API, 0, "fake", "()V", null, null);
314        }
315    }