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