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