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