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