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.components.ServiceManager;
020 import com.intellij.openapi.project.Project;
021 import com.intellij.openapi.vfs.VirtualFile;
022 import com.intellij.psi.PsiElement;
023 import com.intellij.psi.PsiFile;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
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.JvmAbi;
039 import org.jetbrains.jet.lang.resolve.java.PackageClassUtils;
040 import org.jetbrains.jet.lang.resolve.kotlin.DeserializedResolverUtils;
041 import org.jetbrains.jet.lang.resolve.kotlin.PackagePartClassUtils;
042 import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder;
043 import org.jetbrains.jet.lang.resolve.name.FqName;
044 import org.jetbrains.jet.lang.resolve.name.Name;
045 import org.jetbrains.org.objectweb.asm.*;
046 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
047 import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
048 import org.jetbrains.org.objectweb.asm.tree.InsnList;
049 import org.jetbrains.org.objectweb.asm.tree.MethodNode;
050
051 import java.io.IOException;
052 import java.io.InputStream;
053 import java.util.Arrays;
054 import java.util.ListIterator;
055
056 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.getFqName;
057 import static org.jetbrains.jet.lang.resolve.DescriptorUtils.isTrait;
058
059 public class InlineCodegenUtil {
060 public static final int API = Opcodes.ASM5;
061 public static final String INVOKE = "invoke";
062 public static final boolean DEFAULT_INLINE_FLAG = true;
063
064 public static final String CAPTURED_FIELD_PREFIX = "$";
065
066 public static final String THIS$0 = "this$0";
067
068 public static final String RECEIVER$0 = "receiver$0";
069
070 public static final String NON_LOCAL_RETURN = "$$$$$NON_LOCAL_RETURN$$$$$";
071
072 public static final String ROOT_LABEL = "$$$$$ROOT$$$$$";
073
074 @Nullable
075 public static MethodNode getMethodNode(
076 InputStream classData,
077 final String methodName,
078 final String methodDescriptor
079 ) throws ClassNotFoundException, IOException {
080 ClassReader cr = new ClassReader(classData);
081 final MethodNode[] methodNode = new MethodNode[1];
082 cr.accept(new ClassVisitor(API) {
083
084 @Override
085 public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
086 if (methodName.equals(name) && methodDescriptor.equals(desc)) {
087 return methodNode[0] = new MethodNode(access, name, desc, signature, exceptions);
088 }
089 return null;
090 }
091 }, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
092
093 return methodNode[0];
094 }
095
096
097 @NotNull
098 public static VirtualFile getVirtualFileForCallable(@NotNull DeserializedSimpleFunctionDescriptor deserializedDescriptor, @NotNull GenerationState state) {
099 VirtualFile file;
100 DeclarationDescriptor parentDeclaration = deserializedDescriptor.getContainingDeclaration();
101 if (parentDeclaration instanceof PackageFragmentDescriptor) {
102 ProtoBuf.Callable proto = deserializedDescriptor.getProto();
103 if (!proto.hasExtension(JavaProtoBuf.implClassName)) {
104 throw new IllegalStateException("Function in namespace should have implClassName property in proto: " + deserializedDescriptor);
105 }
106 Name name = deserializedDescriptor.getNameResolver().getName(proto.getExtension(JavaProtoBuf.implClassName));
107 FqName packagePartFqName =
108 PackageClassUtils.getPackageClassFqName(((PackageFragmentDescriptor) parentDeclaration).getFqName()).parent().child(
109 name);
110 file = findVirtualFileWithHeader(state.getProject(), packagePartFqName);
111 } else {
112 file = findVirtualFileContainingDescriptor(state.getProject(), deserializedDescriptor);
113 }
114
115 if (file == null) {
116 throw new IllegalStateException("Couldn't find declaration file for " + deserializedDescriptor.getName());
117 }
118
119 return file;
120 }
121
122 @Nullable
123 public static VirtualFile findVirtualFileWithHeader(@NotNull Project project, @NotNull FqName containerFqName) {
124 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
125 return fileFinder.findVirtualFileWithHeader(containerFqName);
126 }
127
128 @Nullable
129 public static VirtualFile findVirtualFile(@NotNull Project project, @NotNull String internalName) {
130 VirtualFileFinder fileFinder = ServiceManager.getService(project, VirtualFileFinder.class);
131 return fileFinder.findVirtualFile(internalName);
132 }
133
134 //TODO: navigate to inner classes
135 @Nullable
136 public static FqName getContainerFqName(@NotNull DeclarationDescriptor referencedDescriptor) {
137 ClassOrPackageFragmentDescriptor
138 containerDescriptor = DescriptorUtils.getParentOfType(referencedDescriptor, ClassOrPackageFragmentDescriptor.class, false);
139 if (containerDescriptor instanceof PackageFragmentDescriptor) {
140 return PackageClassUtils.getPackageClassFqName(getFqName(containerDescriptor).toSafe());
141 }
142 if (containerDescriptor instanceof ClassDescriptor) {
143 FqName fqName = DeserializedResolverUtils.kotlinFqNameToJavaFqName(getFqName(containerDescriptor));
144 if (isTrait(containerDescriptor)) {
145 return fqName.parent().child(Name.identifier(fqName.shortName() + JvmAbi.TRAIT_IMPL_SUFFIX));
146 }
147 return fqName;
148 }
149 return null;
150 }
151
152 public static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull JetTypeMapper typeMapper) {
153 return getInlineName(codegenContext, codegenContext.getContextDescriptor(), typeMapper);
154 }
155
156 private static String getInlineName(@NotNull CodegenContext codegenContext, @NotNull DeclarationDescriptor currentDescriptor, @NotNull JetTypeMapper typeMapper) {
157 if (currentDescriptor instanceof PackageFragmentDescriptor) {
158 PsiFile file = getContainingFile(codegenContext);
159
160 Type packagePartType;
161 if (file == null) {
162 //in case package fragment clinit
163 assert codegenContext instanceof PackageContext : "Expected package context but " + codegenContext;
164 packagePartType = ((PackageContext) codegenContext).getPackagePartType();
165 } else {
166 packagePartType = PackagePartClassUtils.getPackagePartType((JetFile) file);
167 }
168
169 if (packagePartType == null) {
170 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
171 //noinspection ConstantConditions
172 throw new RuntimeException("Couldn't find declaration for " + contextDescriptor.getContainingDeclaration().getName() + "." + contextDescriptor.getName() );
173 }
174
175 return packagePartType.getInternalName();
176 }
177 else if (currentDescriptor instanceof ClassifierDescriptor) {
178 Type type = typeMapper.mapType((ClassifierDescriptor) currentDescriptor);
179 return type.getInternalName();
180 } else if (currentDescriptor instanceof FunctionDescriptor) {
181 ClassDescriptor descriptor =
182 typeMapper.getBindingContext().get(CodegenBinding.CLASS_FOR_FUNCTION, (FunctionDescriptor) currentDescriptor);
183 if (descriptor != null) {
184 Type type = typeMapper.mapType(descriptor);
185 return type.getInternalName();
186 }
187 }
188
189 //TODO: add suffix for special case
190 String suffix = currentDescriptor.getName().isSpecial() ? "" : currentDescriptor.getName().asString();
191
192 //noinspection ConstantConditions
193 return getInlineName(codegenContext, currentDescriptor.getContainingDeclaration(), typeMapper) + "$" + suffix;
194 }
195
196 @Nullable
197 private static VirtualFile findVirtualFileContainingDescriptor(
198 @NotNull Project project,
199 @NotNull DeclarationDescriptor referencedDescriptor
200 ) {
201 FqName containerFqName = getContainerFqName(referencedDescriptor);
202 if (containerFqName == null) {
203 return null;
204 }
205 return findVirtualFileWithHeader(project, containerFqName);
206 }
207
208
209 public static boolean isInvokeOnLambda(String owner, String name) {
210 if (!INVOKE.equals(name)) {
211 return false;
212 }
213
214 for (String prefix : Arrays.asList("kotlin/Function", "kotlin/ExtensionFunction")) {
215 if (owner.startsWith(prefix)) {
216 String suffix = owner.substring(prefix.length());
217 if (isInteger(suffix)) {
218 return true;
219 }
220 }
221 }
222 return false;
223 }
224
225 public static boolean isAnonymousConstructorCall(@NotNull String internalName, @NotNull String methodName) {
226 return "<init>".equals(methodName) && isAnonymousClass(internalName);
227 }
228
229 public static boolean isAnonymousClass(String internalName) {
230 String shortName = getLastNamePart(internalName);
231 int index = shortName.lastIndexOf("$");
232
233 if (index < 0) {
234 return false;
235 }
236
237 String suffix = shortName.substring(index + 1);
238 return isInteger(suffix);
239 }
240
241 @NotNull
242 private static String getLastNamePart(@NotNull String internalName) {
243 int index = internalName.lastIndexOf("/");
244 return index < 0 ? internalName : internalName.substring(index + 1);
245 }
246
247 @Nullable
248 public static PsiFile getContainingFile(CodegenContext codegenContext) {
249 DeclarationDescriptor contextDescriptor = codegenContext.getContextDescriptor();
250 PsiElement psiElement = DescriptorToSourceUtils.descriptorToDeclaration(contextDescriptor);
251 if (psiElement != null) {
252 return psiElement.getContainingFile();
253 }
254 return null;
255 }
256
257 @NotNull
258 public static MaxCalcNode wrapWithMaxLocalCalc(@NotNull MethodNode methodNode) {
259 return new MaxCalcNode(methodNode);
260 }
261
262 private static boolean isInteger(@NotNull String string) {
263 if (string.isEmpty()) {
264 return false;
265 }
266
267 for (int i = 0; i < string.length(); i++) {
268 if (!Character.isDigit(string.charAt(i))) {
269 return false;
270 }
271 }
272
273 return true;
274 }
275
276 public static boolean isCapturedFieldName(@NotNull String fieldName) {
277 // TODO: improve this heuristic
278 return (fieldName.startsWith(CAPTURED_FIELD_PREFIX) && !fieldName.equals(JvmAbi.KOTLIN_CLASS_FIELD_NAME)) ||
279 THIS$0.equals(fieldName) ||
280 RECEIVER$0.equals(fieldName);
281 }
282
283 public static boolean isReturnOpcode(int opcode) {
284 return opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN;
285 }
286
287 public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) {
288 iv.invokestatic(NON_LOCAL_RETURN, labelName, "()V", false);
289 }
290
291 public static Type getReturnType(int opcode) {
292 switch (opcode) {
293 case Opcodes.RETURN: return Type.VOID_TYPE;
294 case Opcodes.IRETURN: return Type.INT_TYPE;
295 case Opcodes.DRETURN: return Type.DOUBLE_TYPE;
296 case Opcodes.FRETURN: return Type.FLOAT_TYPE;
297 case Opcodes.LRETURN: return Type.LONG_TYPE;
298 default: return Type.getObjectType("object");
299 }
300 }
301
302 public static void insertNodeBefore(@NotNull MethodNode from, @NotNull MethodNode to, @NotNull AbstractInsnNode afterNode) {
303 InsnList instructions = to.instructions;
304 ListIterator<AbstractInsnNode> iterator = from.instructions.iterator();
305 while (iterator.hasNext()) {
306 AbstractInsnNode next = iterator.next();
307 instructions.insertBefore(afterNode, next);
308 }
309 }
310
311
312 public static MethodNode createEmptyMethodNode() {
313 return new MethodNode(API, 0, "fake", "()V", null, null);
314 }
315 }