001 /*
002 * Copyright 2010-2013 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.jet.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 org.jetbrains.annotations.NotNull;
024 import org.jetbrains.annotations.Nullable;
025 import org.jetbrains.annotations.TestOnly;
026 import org.jetbrains.jet.codegen.binding.CodegenBinding;
027 import org.jetbrains.jet.codegen.context.CodegenContext;
028 import org.jetbrains.jet.codegen.context.PackageContext;
029 import org.jetbrains.jet.codegen.state.GenerationState;
030 import org.jetbrains.jet.codegen.state.JetTypeMapper;
031 import org.jetbrains.jet.descriptors.serialization.JavaProtoBuf;
032 import org.jetbrains.jet.descriptors.serialization.ProtoBuf;
033 import org.jetbrains.jet.descriptors.serialization.descriptors.DeserializedSimpleFunctionDescriptor;
034 import org.jetbrains.jet.lang.descriptors.*;
035 import org.jetbrains.jet.lang.psi.JetFile;
036 import org.jetbrains.jet.lang.resolve.DescriptorToSourceUtils;
037 import org.jetbrains.jet.lang.resolve.DescriptorUtils;
038 import org.jetbrains.jet.lang.resolve.java.AsmTypeConstants;
039 import org.jetbrains.jet.lang.resolve.java.JvmAbi;
040 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
041 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils;
042 import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
043 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder;
044 import org.jetbrains.jet.lang.resolve.name.FqName;
045 import org.jetbrains.jet.lang.resolve.name.Name;
046 import org.jetbrains.org.objectweb.asm.*;
047 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
048 import org.jetbrains.org.objectweb.asm.tree.*;
049 import org.jetbrains.org.objectweb.asm.util.Textifier;
050 import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
051
052 import java.io.IOException;
053 import java.io.InputStream;
054 import java.io.PrintWriter;
055 import java.io.StringWriter;
056 import java.util.Arrays;
057 import java.util.ListIterator;
058
059 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
060 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
061
062 public class InlineCodegenUtil {
063 public static final int API = Opcodes.ASM5;
064 public static final String INVOKE = "invoke";
065
066 public static final String CAPTURED_FIELD_PREFIX = "$";
067
068 public static final String THIS$0 = "this$0";
069
070 public static final String RECEIVER$0 = "receiver$0";
071
072 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
073
074 public static final String ROOT_LABEL = "$$$$$ROOT$$$$$";
075 public static final String INLINE_MARKER_CLASS_NAME = "kotlin/jvm/internal/InlineMarker";
076 public static final String INLINE_MARKER_BEFORE_METHOD_NAME = "beforeInlineCall";
077 public static final String INLINE_MARKER_AFTER_METHOD_NAME = "afterInlineCall";
078
079 @Nullable
080 public static MethodNode getMethodNode(
081 byte[] classData,
082 final String methodName,
083 final String methodDescriptor
084 ) throws ClassNotFoundException, IOException {
085 ClassReader cr = new ClassReader(classData);
086 final MethodNode[] methodNode = new MethodNode[1];
087 cr.accept(new ClassVisitor(API) {
088
089 @Override
090 public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
091 if (methodName.equals(name) && methodDescriptor.equals(desc)) {
092 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
093 }
094 return null;
095 }
096 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
097
098 return methodNode[0];
099 }
100
101
102 @NotNull
103 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) {
104 VirtualFile file;
105 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration();
106 if (parentDeclaration instanceof PackageFragmentDescriptor) {
107 ProtoBuf.Callable proto = deserializedDescriptor.getProto();
108 if (!proto.hasExtension(JavaProtoBuf.implClassName)) {
109 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor);
110 }
111 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName));
112 FqName packagePartFqName =
113 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child(
114 name);
115 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName);
116 } else {
117 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor);
118 }
119
120 if (file == null) {
121 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName());
122 }
123
124 return file;
125 }
126
127 @Nullable
128 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) {
129 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project);
130 return fileFinder.findVirtualFileWithHeader(containerFqName);
131 }
132
133 @Nullable
134 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) {
135 VirtualFileFinder fileFinder = VirtualFileFinder.SERVICE.getInstance(project);
136 return fileFinder.findVirtualFile(internalName);
137 }
138
139 //TODO: navigate to inner classes
140 @Nullable
141 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) {
142 ClassOrPackageFragmentDescriptor
143 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
144 if (containerDescriptor instanceof PackageFragmentDescriptor) {
145 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe());
146 }
147 if (containerDescriptor instanceof ClassDescriptor) {
148 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor));
149 if (isTrait(containerDescriptor)) {
150 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX));
151 }
152 return fqName;
153 }
154 return null;
155 }
156
157 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) {
158 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper);
159 }
160
161 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) {
162 if (currentDescriptor instanceof PackageFragmentDescriptor) {
163 PsiFile file = getContainingFile(codegenContext);
164
165 Type packagePartType;
166 if (file == null) {
167 //in case package fragment clinit
168 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext;
169 packagePartType = ((PackageContext) codegenContext).getPackagePartType();
170 } else {
171 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file);
172 }
173
174 if (packagePartType == null) {
175 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
176 //noinspection ConstantConditions
177 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() );
178 }
179
180 return packagePartType.getInternalName();
181 }
182 else if (currentDescriptor instanceof ClassifierDescriptor) {
183 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
184 return type.getInternalName();
185 } else if (currentDescriptor instanceof FunctionDescriptor) {
186 ClassDescriptor descriptor =
187 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor);
188 if (descriptor != null) {
189 Type type = typeMapper.mapType(descriptor);
190 return type.getInternalName();
191 }
192 }
193
194 //TODO: add suffix for special case
195 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
196
197 //noinspection ConstantConditions
198 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix;
199 }
200
201 @Nullable
202 private static VirtualFile findVirtualFileContainingDescriptor(
203 @NotNull Project project,
204 @NotNull DeclarationDescriptor referencedDescriptor
205 ) {
206 FqName containerFqName = getContainerFqName(referencedDescriptor);
207 if (containerFqName == null) {
208 return null;
209 }
210 return findVirtualFileWithHeader(project, containerFqName);
211 }
212
213
214 public static boolean isInvokeOnLambda(String owner, String name) {
215 if (!INVOKE.equals(name)) {
216 return false;
217 }
218
219 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) {
220 if (owner.startsWith(prefix)) {
221 String suffix = owner.substring(prefix.length());
222 if (isInteger(suffix)) {
223 return true;
224 }
225 }
226 }
227 return false;
228 }
229
230 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
231 return "<init>".equals(methodName) && isAnonymousClass(internalName);
232 }
233
234 public static boolean isAnonymousClass(String internalName) {
235 String shortName = getLastNamePart(internalName);
236 int index = shortName.lastIndexOf("$");
237
238 if (index < 0) {
239 return false;
240 }
241
242 String suffix = shortName.substring(index + 1);
243 return isInteger(suffix);
244 }
245
246 @NotNull
247 private static String getLastNamePart(@NotNull String internalName) {
248 int index = internalName.lastIndexOf("/");
249 return index < 0 ? internalName : internalName.substring(index + 1);
250 }
251
252 @Nullable
253 public static PsiFile getContainingFile(CodegenContext codegenContext) {
254 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
255 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
256 if (psiElement != null) {
257 return psiElement.getContainingFile();
258 }
259 return null;
260 }
261
262 @NotNull
263 public static MethodVisitor wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
264 return new MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode);
265 }
266
267 private static boolean isInteger(@NotNull String string) {
268 if (string.isEmpty()) {
269 return false;
270 }
271
272 for (int i = 0; i < string.length(); i++) {
273 if (!Character.isDigit(string.charAt(i))) {
274 return false;
275 }
276 }
277
278 return true;
279 }
280
281 public static boolean isCapturedFieldName(@NotNull String fieldName) {
282 // TODO: improve this heuristic
283 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) ||
284 THIS$0.equals(fieldName) ||
285 RECEIVER$0.equals(fieldName);
286 }
287
288 public static boolean isReturnOpcode(int opcode) {
289 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
290 }
291
292 //marked return could be either non-local or local in case of labeled lambda self-returns
293 public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
294 assert isReturnOpcode(returnIns.getOpcode()) : "Should be called on return instruction, but " + returnIns;
295 AbstractInsnNode globalFlag = returnIns.getPrevious();
296 return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner);
297 }
298
299 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
300 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
301 }
302
303 public static Type getReturnType(int opcode) {
304 switch (opcode) {
305 case Opcodes.RETURN: return Type.VOID_TYPE;
306 case Opcodes.IRETURN: return Type.INT_TYPE;
307 case Opcodes.DRETURN: return Type.DOUBLE_TYPE;
308 case Opcodes.FRETURN: return Type.FLOAT_TYPE;
309 case Opcodes.LRETURN: return Type.LONG_TYPE;
310 default: return AsmTypeConstants.OBJECT_TYPE;
311 }
312 }
313
314 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode beforeNode) {
315 InsnList instructions = to.instructions;
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
324 public static MethodNode createEmptyMethodNode() {
325 return new MethodNode(API, 0, "fake", "()V", null, null);
326 }
327
328 private static boolean isLastGoto(@NotNull AbstractInsnNode insnNode, @NotNull AbstractInsnNode stopAt) {
329 if (insnNode.getOpcode() == Opcodes.GOTO) {
330 insnNode = insnNode.getNext();
331 while (insnNode != stopAt && isLineNumberOrLabel(insnNode)) {
332 insnNode = insnNode.getNext();
333 }
334 return stopAt == insnNode;
335 }
336 return false;
337 }
338
339 static boolean isLineNumberOrLabel(@Nullable AbstractInsnNode node) {
340 return node instanceof LineNumberNode || node instanceof LabelNode;
341 }
342
343
344 @NotNull
345 public static LabelNode firstLabelInChain(@NotNull LabelNode node) {
346 LabelNode curNode = node;
347 while (curNode.getPrevious() instanceof LabelNode) {
348 curNode = (LabelNode) curNode.getPrevious();
349 }
350 return curNode;
351 }
352
353 @NotNull
354 public static String getNodeText(@Nullable MethodNode node) {
355 return getNodeText(node, new Textifier());
356 }
357
358 @NotNull
359 public static String getNodeText(@Nullable MethodNode node, @NotNull Textifier textifier) {
360 if (node == null) {
361 return "Not generated";
362 }
363 node.accept(new TraceMethodVisitor(textifier));
364 StringWriter sw = new StringWriter();
365 textifier.print(new PrintWriter(sw));
366 sw.flush();
367 return node.name + " " + node.desc + ": \n " + sw.getBuffer().toString();
368 }
369
370 public static class LabelTextifier extends Textifier {
371
372 public LabelTextifier() {
373 super(API);
374 }
375
376 @Nullable
377 @TestOnly
378 @SuppressWarnings("UnusedDeclaration")
379 public String getLabelNameIfExists(@NotNull Label l) {
380 return labelNames == null ? null : labelNames.get(l);
381 }
382 }
383
384 public static void addInlineMarker(
385 @NotNull InstructionAdapter v,
386 boolean isStartNotEnd
387 ) {
388 v.visitMethodInsn(Opcodes.INVOKESTATIC, INLINE_MARKER_CLASS_NAME,
389 (isStartNotEnd ? INLINE_MARKER_BEFORE_METHOD_NAME : INLINE_MARKER_AFTER_METHOD_NAME),
390 "()V", false);
391 }
392 }