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 kotlin.StringsKt;
024    import org.jetbrains.annotations.NotNull;
025    import org.jetbrains.annotations.Nullable;
026    import org.jetbrains.annotations.TestOnly;
027    import org.jetbrains.kotlin.backend.common.output.OutputFile;
028    import org.jetbrains.kotlin.codegen.MemberCodegen;
029    import org.jetbrains.kotlin.codegen.binding.CodegenBinding;
030    import org.jetbrains.kotlin.codegen.context.CodegenContext;
031    import org.jetbrains.kotlin.codegen.context.CodegenContextUtil;
032    import org.jetbrains.kotlin.codegen.context.MethodContext;
033    import org.jetbrains.kotlin.codegen.state.GenerationState;
034    import org.jetbrains.kotlin.codegen.state.JetTypeMapper;
035    import org.jetbrains.kotlin.descriptors.*;
036    import org.jetbrains.kotlin.fileClasses.FileClasses;
037    import org.jetbrains.kotlin.fileClasses.JvmFileClassesProvider;
038    import org.jetbrains.kotlin.load.java.JvmAbi;
039    import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder;
040    import org.jetbrains.kotlin.name.ClassId;
041    import org.jetbrains.kotlin.name.FqName;
042    import org.jetbrains.kotlin.name.Name;
043    import org.jetbrains.kotlin.psi.KtFile;
044    import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils;
045    import org.jetbrains.kotlin.resolve.jvm.AsmTypes;
046    import org.jetbrains.kotlin.resolve.jvm.JvmClassName;
047    import org.jetbrains.kotlin.util.OperatorNameConventions;
048    import org.jetbrains.org.objectweb.asm.*;
049    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
050    import org.jetbrains.org.objectweb.asm.tree.*;
051    import org.jetbrains.org.objectweb.asm.util.Textifier;
052    import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
053    
054    import java.io.IOException;
055    import java.io.PrintWriter;
056    import java.io.StringWriter;
057    import java.util.ListIterator;
058    
059    public class InlineCodegenUtil {
060        public static final boolean GENERATE_SMAP = true;
061        public static final int API = Opcodes.ASM5;
062    
063        public static final String CAPTURED_FIELD_PREFIX = "$";
064        public static final String THIS$0 = "this$0";
065        public static final String RECEIVER$0 = "receiver$0";
066        public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
067        public static final String FIRST_FUN_LABEL = "$$$$$ROOT$$$$$";
068        public static final String NUMBERED_FUNCTION_PREFIX = "kotlin/jvm/functions/Function";
069        public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker";
070        public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall";
071        public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall";
072        public static final String INLINE_MARKER_FINALLY_START = "finallyStart";
073        public static final String INLINE_MARKER_FINALLY_END = "finallyEnd";
074    
075        @Nullable
076        public static SMAPAndMethodNode getMethodNode(
077                byte[] classData,
078                final String methodName,
079                final String methodDescriptor,
080                ClassId classId
081        ) throws ClassNotFoundException, IOException {
082            ClassReader cr = new ClassReader(classData);
083            final MethodNode[] node = new MethodNode[1];
084            final String[] debugInfo = new String[2];
085            final int[] lines = new int[2];
086            lines[0] = Integer.MAX_VALUE;
087            lines[1] = Integer.MIN_VALUE;
088            cr.accept(new ClassVisitor(API) {
089    
090                @Override
091                public void visitSource(String source, String debug) {
092                    super.visitSource(source, debug);
093                    debugInfo[0] = source;
094                    debugInfo[1] = debug;
095                }
096    
097                @Override
098                public MethodVisitor visitMethod(
099                        int access,
100                        @NotNull String name,
101                        @NotNull String desc,
102                        String signature,
103                        String[] exceptions
104                ) {
105                    if (methodName.equals(name) && methodDescriptor.equals(desc)) {
106                        node[0] = new MethodNode(API, access, name, desc, signature, exceptions) {
107                            @Override
108                            public void visitLineNumber(int line, @NotNull Label start) {
109                                super.visitLineNumber(line, start);
110                                lines[0] = Math.min(lines[0], line);
111                                lines[1] = Math.max(lines[1], line);
112                            }
113                        };
114                        return node[0];
115                    }
116                    return null;
117                }
118            }, ClassReader.SKIP_FRAMES | (GENERATE_SMAP ? 0 : ClassReader.SKIP_DEBUG));
119    
120            SMAP smap = SMAPParser.parseOrCreateDefault(debugInfo[1], debugInfo[0], classId.toString(), lines[0], lines[1]);
121            return new SMAPAndMethodNode(node[0], smap);
122        }
123    
124        public static void initDefaultSourceMappingIfNeeded(@NotNull CodegenContext context, @NotNull MemberCodegen codegen, @NotNull GenerationState state) {
125            if (state.isInlineEnabled()) {
126                CodegenContext<?> parentContext = context.getParentContext();
127                while (parentContext != null) {
128                    if (parentContext instanceof MethodContext) {
129                        if (((MethodContext) parentContext).isInlineFunction()) {
130                            //just init default one to one mapping
131                            codegen.getOrCreateSourceMapper();
132                            break;
133                        }
134                    }
135                    parentContext = parentContext.getParentContext();
136                }
137            }
138        }
139    
140        @NotNull
141        public static VirtualFile getVirtualFileForCallable(@NotNull ClassId containerClassId, @NotNull GenerationState state) {
142            JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(state.getProject());
143            VirtualFile file = fileFinder.findVirtualFileWithHeader(containerClassId);
144            if (file == null) {
145                throw new IllegalStateException("Couldn't find declaration file for " + containerClassId);
146            }
147            return file;
148        }
149    
150        @Nullable
151        public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalClassName) {
152            FqName packageFqName = JvmClassName.byInternalName(internalClassName).getPackageFqName();
153            String classNameWithDollars = StringsKt.substringAfterLast(internalClassName, "/", internalClassName);
154            JvmVirtualFileFinder fileFinder = JvmVirtualFileFinder.SERVICE.getInstance(project);
155            //TODO: we cannot construct proper classId at this point, we need to read InnerClasses info from class file
156            // we construct valid.package.name/RelativeClassNameAsSingleName that should work in compiler, but fails for inner classes in IDE
157            return fileFinder.findVirtualFileWithHeader(new ClassId(packageFqName, Name.identifier(classNameWithDollars)));
158        }
159    
160        public static String getInlineName(
161                @NotNull CodegenContext codegenContext,
162                @NotNull JetTypeMapper typeMapper,
163                @NotNull JvmFileClassesProvider fileClassesManager
164        ) {
165            return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper, fileClassesManager);
166        }
167    
168        private static String getInlineName(
169                @NotNull CodegenContext codegenContext,
170                @NotNull DeclarationDescriptor currentDescriptor,
171                @NotNull JetTypeMapper typeMapper,
172                @NotNull JvmFileClassesProvider fileClassesProvider
173        ) {
174            if (currentDescriptor instanceof PackageFragmentDescriptor) {
175                PsiFile file = getContainingFile(codegenContext);
176    
177                Type implementationOwnerType;
178                if (file == null) {
179                    implementationOwnerType = CodegenContextUtil.getImplementationOwnerClassType(codegenContext);
180                } else {
181                    implementationOwnerType = FileClasses.getFileClassType(fileClassesProvider, (KtFile) file);
182                }
183    
184                if (implementationOwnerType == null) {
185                    DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
186                    //noinspection ConstantConditions
187                    throw new RuntimeException("Couldn't find declaration for " +
188                                               contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() +
189                                               "; context: " + codegenContext);
190                }
191    
192                return implementationOwnerType.getInternalName();
193            }
194            else if (currentDescriptor instanceof ClassifierDescriptor) {
195                Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
196                return type.getInternalName();
197            } else if (currentDescriptor instanceof FunctionDescriptor) {
198                ClassDescriptor descriptor =
199                        typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_CALLABLE, (FunctionDescriptor) currentDescriptor);
200                if (descriptor != null) {
201                    Type type = typeMapper.mapType(descriptor);
202                    return type.getInternalName();
203                }
204            }
205    
206            //TODO: add suffix for special case
207            String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
208    
209            //noinspection ConstantConditions
210            return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper, fileClassesProvider) + "$" + suffix;
211        }
212    
213        public static boolean isInvokeOnLambda(@NotNull String owner, @NotNull String name) {
214            return OperatorNameConventions.INVOKE.asString().equals(name) &&
215                   owner.startsWith(NUMBERED_FUNCTION_PREFIX) &&
216                   isInteger(owner.substring(NUMBERED_FUNCTION_PREFIX.length()));
217        }
218    
219        public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
220            return "<init>".equals(methodName) && isAnonymousClass(internalName);
221        }
222    
223        public static boolean isAnonymousSingletonLoad(@NotNull String internalName, @NotNull String fieldName) {
224            return JvmAbi.INSTANCE_FIELD.equals(fieldName) && isAnonymousClass(internalName);
225        }
226    
227        public static boolean isAnonymousClass(String internalName) {
228            String shortName = getLastNamePart(internalName);
229            int index = shortName.lastIndexOf("$");
230    
231            if (index < 0) {
232                return false;
233            }
234    
235            String suffix = shortName.substring(index + 1);
236            return isInteger(suffix);
237        }
238    
239        @NotNull
240        private static String getLastNamePart(@NotNull String internalName) {
241            int index = internalName.lastIndexOf("/");
242            return index < 0 ? internalName : internalName.substring(index + 1);
243        }
244    
245        @Nullable
246        public static PsiFile getContainingFile(CodegenContext codegenContext) {
247            DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
248            PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
249            if (psiElement != null) {
250                return psiElement.getContainingFile();
251            }
252            return null;
253        }
254    
255        @NotNull
256        public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
257            return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
258        }
259    
260        private static boolean isInteger(@NotNull String string) {
261            if (string.isEmpty()) {
262                return false;
263            }
264    
265            for (int i = 0; i < string.length(); i++) {
266                 if (!Character.isDigit(string.charAt(i))) {
267                     return false;
268                 }
269            }
270    
271            return true;
272        }
273    
274        public static boolean isCapturedFieldName(@NotNull String fieldName) {
275            // TODO: improve this heuristic
276            return fieldName.startsWith(CAPTURED_FIELD_PREFIX) ||
277                   THIS$0.equals(fieldName) ||
278                   RECEIVER$0.equals(fieldName);
279        }
280    
281        public static boolean isReturnOpcode(int opcode) {
282            return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
283        }
284    
285        //marked return could be either non-local or local in case of labeled lambda self-returns
286        public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
287            if (!isReturnOpcode(returnIns.getOpcode())) {
288                return false;
289            }
290            AbstractInsnNode globalFlag = returnIns.getPrevious();
291            return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner);
292        }
293    
294        public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
295            iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
296        }
297    
298        public static Type getReturnType(int opcode) {
299            switch (opcode) {
300                case Opcodes.RETURN: return Type.VOID_TYPE;
301                case Opcodes.IRETURN: return Type.INT_TYPE;
302                case Opcodes.DRETURN: return Type.DOUBLE_TYPE;
303                case Opcodes.FRETURN: return Type.FLOAT_TYPE;
304                case Opcodes.LRETURN: return Type.LONG_TYPE;
305                default: return AsmTypes.OBJECT_TYPE;
306            }
307        }
308    
309        public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) {
310            InsnList instructions = to.instructions;
311            ListIterator<AbstractInsnNode> iterator = from.instructions.iterator();
312            while (iterator.hasNext()) {
313                AbstractInsnNode next = iterator.next();
314                instructions.insertBefore(beforeNode, next);
315            }
316        }
317    
318    
319        public static MethodNode createEmptyMethodNode() {
320            return new MethodNode(API, 0, "fake", "()V", null, null);
321        }
322    
323        @NotNull
324        public static LabelNode firstLabelInChain(@NotNull LabelNode node) {
325            LabelNode curNode = node;
326            while (curNode.getPrevious() instanceof LabelNode) {
327                curNode = (LabelNode) curNode.getPrevious();
328            }
329            return curNode;
330        }
331    
332        @NotNull
333        public static String getNodeText(@Nullable MethodNode node) {
334            return getNodeText(node, new Textifier());
335        }
336    
337        @NotNull
338        public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) {
339            if (node == null) {
340                return "Not generated";
341            }
342            node.accept(new TraceMethodVisitor(textifier));
343            StringWriter sw = new StringWriter();
344            textifier.print(new PrintWriter(sw));
345            sw.flush();
346            return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString();
347        }
348    
349        @NotNull
350        /* package */ static ClassReader buildClassReaderByInternalName(@NotNull GenerationState state, @NotNull String internalName) {
351            //try to find just compiled classes then in dependencies
352            try {
353                OutputFile outputFile = state.getFactory().get(internalName + ".class");
354                if (outputFile != null) {
355                    return new ClassReader(outputFile.asByteArray());
356                } else {
357                    VirtualFile file = findVirtualFile(state.getProject(), internalName);
358                    if (file == null) {
359                        throw new RuntimeException("Couldn't find virtual file for " + internalName);
360                    }
361                    return new ClassReader(file.contentsToByteArray());
362                }
363            }
364            catch (IOException e) {
365                throw new RuntimeException(e);
366            }
367        }
368    
369        public static void generateFinallyMarker(@NotNull InstructionAdapter v, int depth, boolean start) {
370            v.iconst(depth);
371            v.invokestatic(INLINE_MARKER_CLASS_NAME, start ? INLINE_MARKER_FINALLY_START : INLINE_MARKER_FINALLY_END, "(I)V", false);
372        }
373    
374        public static boolean isFinallyEnd(@NotNull AbstractInsnNode node) {
375            return isFinallyMarker(node, INLINE_MARKER_FINALLY_END);
376        }
377    
378        public static boolean isFinallyStart(@NotNull AbstractInsnNode node) {
379            return isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
380        }
381    
382        public static boolean isFinallyMarker(@Nullable AbstractInsnNode node) {
383            return isFinallyMarker(node, INLINE_MARKER_FINALLY_END) || isFinallyMarker(node, INLINE_MARKER_FINALLY_START);
384        }
385    
386        public static boolean isFinallyMarker(@Nullable AbstractInsnNode node, String name) {
387            if (!(node instanceof MethodInsnNode)) return false;
388            MethodInsnNode method = (MethodInsnNode) node;
389            return INLINE_MARKER_CLASS_NAME.equals(method.owner) && name.equals(method.name);
390        }
391    
392        public static boolean isFinallyMarkerRequired(@NotNull MethodContext context) {
393            return context.isInlineFunction() || context.isInliningLambda();
394        }
395    
396        public static int getConstant(AbstractInsnNode ins) {
397            int opcode = ins.getOpcode();
398            Integer value;
399            if (opcode >= Opcodes.ICONST_0 && opcode <= Opcodes.ICONST_5) {
400                value = opcode - Opcodes.ICONST_0;
401            }
402            else if (opcode == Opcodes.BIPUSH || opcode == Opcodes.SIPUSH) {
403                IntInsnNode index = (IntInsnNode) ins;
404                value = index.operand;
405            }
406            else {
407                LdcInsnNode index = (LdcInsnNode) ins;
408                value = (Integer) index.cst;
409            }
410            return value;
411        }
412    
413        public static class LabelTextifier extends Textifier {
414    
415            public LabelTextifier() {
416                super(API);
417            }
418    
419            @Nullable
420            @TestOnly
421            @SuppressWarnings("UnusedDeclaration")
422            public String getLabelNameIfExists(@NotNull Label l) {
423                return labelNames == null ? null : labelNames.get(l);
424            }
425        }
426    
427        public static void addInlineMarker(
428                @NotNull InstructionAdapter v,
429                boolean isStartNotEnd
430        ) {
431            v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
432                              (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME),
433                              "()V", false);
434        }
435    
436        public static boolean isInlineMarker(AbstractInsnNode insn) {
437            return isInlineMarker(insn, null);
438        }
439    
440        public static boolean isInlineMarker(AbstractInsnNode insn, String name) {
441            if (insn instanceof MethodInsnNode) {
442                MethodInsnNode methodInsnNode = (MethodInsnNode) insn;
443                return insn.getOpcode() == Opcodes.INVOKESTATIC &&
444                       methodInsnNode.owner.equals(INLINE_MARKER_CLASS_NAME) &&
445                       (name != null ? methodInsnNode.name.equals(name)
446                                     : methodInsnNode.name.equals(INLINE_MARKER_BEFORE_METHOD_NAME) ||
447                                       methodInsnNode.name.equals(INLINE_MARKER_AFTER_METHOD_NAME));
448            }
449            else {
450                return false;
451            }
452        }
453    
454        public static boolean isBeforeInlineMarker(AbstractInsnNode insn) {
455            return isInlineMarker(insn, INLINE_MARKER_BEFORE_METHOD_NAME);
456        }
457    
458        public static boolean isAfterInlineMarker(AbstractInsnNode insn) {
459            return isInlineMarker(insn, INLINE_MARKER_AFTER_METHOD_NAME);
460        }
461    
462        public static int getLoadStoreArgSize(int opcode) {
463            return opcode == Opcodes.DSTORE || opcode == Opcodes.LSTORE || opcode == Opcodes.DLOAD || opcode == Opcodes.LLOAD ? 2 : 1;
464        }
465    
466        public static boolean isStoreInstruction(int opcode) {
467            return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE;
468        }
469    
470        public static int calcMarkerShift(Parameters parameters, MethodNode node) {
471            int markerShiftTemp = getIndexAfterLastMarker(node);
472            return markerShiftTemp - parameters.getRealArgsSizeOnStack() + parameters.getArgsSizeOnStack();
473        }
474    
475        protected static int getIndexAfterLastMarker(MethodNode node) {
476            int markerShiftTemp = -1;
477            for (LocalVariableNode variable : node.localVariables) {
478                if (isFakeLocalVariableForInline(variable.name)) {
479                    markerShiftTemp = Math.max(markerShiftTemp, variable.index + 1);
480                }
481            }
482            return markerShiftTemp;
483        }
484    
485        public static boolean isFakeLocalVariableForInline(@NotNull String name) {
486            return name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) || name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT);
487        }
488    }