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