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