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