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.util.Pair;
020 import com.intellij.openapi.vfs.VirtualFile;
021 import com.intellij.util.ArrayUtil;
022 import org.jetbrains.annotations.NotNull;
023 import org.jetbrains.jet.OutputFile;
024 import org.jetbrains.jet.codegen.AsmUtil;
025 import org.jetbrains.jet.codegen.ClassBuilder;
026 import org.jetbrains.jet.codegen.FieldInfo;
027 import org.jetbrains.jet.codegen.StackValue;
028 import org.jetbrains.jet.codegen.state.GenerationState;
029 import org.jetbrains.jet.codegen.state.JetTypeMapper;
030 import org.jetbrains.org.objectweb.asm.*;
031 import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
032 import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
033 import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode;
034 import org.jetbrains.org.objectweb.asm.tree.MethodNode;
035 import org.jetbrains.org.objectweb.asm.tree.VarInsnNode;
036
037 import java.io.IOException;
038 import java.util.*;
039
040 public class AnonymousObjectTransformer {
041
042 protected final GenerationState state;
043
044 protected final JetTypeMapper typeMapper;
045
046 private MethodNode constructor;
047
048 private final InliningContext inliningContext;
049
050 private final Type oldObjectType;
051
052 private final Type newLambdaType;
053
054 private final ClassReader reader;
055
056 private final boolean isSameModule;
057
058 private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
059
060 public AnonymousObjectTransformer(
061 @NotNull String objectInternalName,
062 @NotNull InliningContext inliningContext,
063 boolean isSameModule,
064 @NotNull Type newLambdaType
065 ) {
066 this.isSameModule = isSameModule;
067 this.state = inliningContext.state;
068 this.typeMapper = state.getTypeMapper();
069 this.inliningContext = inliningContext;
070 this.oldObjectType = Type.getObjectType(objectInternalName);
071 this.newLambdaType = newLambdaType;
072
073 //try to find just compiled classes then in dependencies
074 try {
075 OutputFile outputFile = state.getFactory().get(objectInternalName + ".class");
076 if (outputFile != null) {
077 reader = new ClassReader(outputFile.asByteArray());
078 } else {
079 VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), objectInternalName);
080 if (file == null) {
081 throw new RuntimeException("Couldn't find virtual file for " + objectInternalName);
082 }
083 reader = new ClassReader(file.getInputStream());
084 }
085 }
086 catch (IOException e) {
087 throw new RuntimeException(e);
088 }
089 }
090
091 private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) {
092 builder.addThis(oldObjectType, false);
093
094 Type[] types = Type.getArgumentTypes(node.desc);
095 for (Type type : types) {
096 builder.addNextParameter(type, false, null);
097 }
098 }
099
100 @NotNull
101 public InlineResult doTransform(@NotNull ConstructorInvocation invocation, @NotNull FieldRemapper parentRemapper) {
102 ClassBuilder classBuilder = createClassBuilder();
103 final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>();
104 reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
105
106 @Override
107 public void visitOuterClass(@NotNull String owner, String name, String desc) {
108 InliningContext parent = inliningContext.getParent();
109 assert parent != null : "Context for transformer should have parent one: " + inliningContext;
110
111 //we don't write owner info for lamdbas and SAMs just only for objects
112 if (parent.isRoot() || parent.isInliningLambdaRootContext()) {
113 //TODO: think about writing method info - there is some problem with new constructor desc calculation
114 super.visitOuterClass(inliningContext.getParent().getClassNameToInline(), null, null);
115 return;
116 }
117
118 super.visitOuterClass(owner, name, desc);
119 }
120
121 @Override
122 public MethodVisitor visitMethod(
123 int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
124 ) {
125 MethodNode node = new MethodNode(access, name, desc, signature, exceptions);
126 if (name.equals("<init>")){
127 if (constructor != null)
128 throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor");
129
130 constructor = node;
131 } else {
132 methodsToTransform.add(node);
133 }
134 return node;
135 }
136
137 @Override
138 public FieldVisitor visitField(
139 int access, @NotNull String name, @NotNull String desc, String signature, Object value
140 ) {
141 addUniqueField(name);
142 if (InlineCodegenUtil.isCapturedFieldName(name)) {
143 return null;
144 } else {
145 return super.visitField(access, name, desc, signature, value);
146 }
147 }
148 }, ClassReader.SKIP_FRAMES);
149
150 ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
151 ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
152 List<CapturedParamInfo> additionalFakeParams =
153 extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder, invocation);
154
155 InlineResult result = InlineResult.create();
156 for (MethodNode next : methodsToTransform) {
157 MethodVisitor visitor = newMethod(classBuilder, next);
158 InlineResult funResult = inlineMethod(invocation, parentRemapper, visitor, next, allCapturedParamBuilder);
159 result.addAllClassesToRemove(funResult);
160 }
161
162 InlineResult constructorResult =
163 generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, invocation, parentRemapper, additionalFakeParams);
164
165 result.addAllClassesToRemove(constructorResult);
166
167 classBuilder.done();
168
169 invocation.setNewLambdaType(newLambdaType);
170 return result;
171 }
172
173 @NotNull
174 private InlineResult inlineMethod(
175 @NotNull ConstructorInvocation invocation,
176 @NotNull FieldRemapper parentRemapper,
177 @NotNull MethodVisitor resultVisitor,
178 @NotNull MethodNode sourceNode,
179 @NotNull ParametersBuilder capturedBuilder
180 ) {
181
182 Parameters parameters = getMethodParametersWithCaptured(capturedBuilder, sourceNode);
183
184 RegeneratedLambdaFieldRemapper remapper =
185 new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
186 parameters, invocation.getCapturedLambdasToInline(),
187 parentRemapper);
188
189 MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
190 remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName());
191 InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false);
192 resultVisitor.visitMaxs(-1, -1);
193 return result;
194 }
195
196 private InlineResult generateConstructorAndFields(
197 @NotNull ClassBuilder classBuilder,
198 @NotNull ParametersBuilder allCapturedBuilder,
199 @NotNull ParametersBuilder constructorInlineBuilder,
200 @NotNull ConstructorInvocation invocation,
201 @NotNull FieldRemapper parentRemapper,
202 @NotNull List<CapturedParamInfo> constructorAdditionalFakeParams
203 ) {
204 List<Type> descTypes = new ArrayList<Type>();
205
206 Parameters constructorParams = constructorInlineBuilder.buildParameters();
207 int [] capturedIndexes = new int [constructorParams.totalSize()];
208 int index = 0;
209 int size = 0;
210
211 //complex processing cause it could have super constructor call params
212 for (ParameterInfo info : constructorParams) {
213 if (!info.isSkipped()) { //not inlined
214 if (info.isCaptured() || info instanceof CapturedParamInfo) {
215 capturedIndexes[index] = size;
216 index++;
217 }
218
219 if (size != 0) { //skip this
220 descTypes.add(info.getType());
221 }
222 size += info.getType().getSize();
223 }
224 }
225
226 List<Pair<String, Type>> capturedFieldsToGenerate = new ArrayList<Pair<String, Type>>();
227 for (CapturedParamInfo capturedParamInfo : allCapturedBuilder.listCaptured()) {
228 if (capturedParamInfo.getLambda() == null) { //not inlined
229 capturedFieldsToGenerate.add(new Pair<String, Type>(capturedParamInfo.getNewFieldName(), capturedParamInfo.getType()));
230 }
231 }
232
233 String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, descTypes.toArray(new Type[descTypes.size()]));
234
235 MethodVisitor constructorVisitor = classBuilder.newMethod(null,
236 AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
237 "<init>", constructorDescriptor,
238 null, ArrayUtil.EMPTY_STRING_ARRAY);
239
240 //initialize captured fields
241 List<FieldInfo> fields = AsmUtil.transformCapturedParams(capturedFieldsToGenerate, newLambdaType);
242 int paramIndex = 0;
243 InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor);
244 for (FieldInfo fieldInfo : fields) {
245 AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer);
246 paramIndex++;
247 }
248
249 //then transform constructor
250 //HACK: in inlinining into constructor we access original captured fields with field access not local var
251 //but this fields added to general params (this assumes local var access) not captured one,
252 //so we need to add them to captured params
253 for (CapturedParamInfo info : constructorAdditionalFakeParams) {
254 CapturedParamInfo fake = constructorInlineBuilder.addCapturedParamCopy(info);
255
256 if (fake.getLambda() != null) {
257 //set remap value to skip this fake (captured with lambda already skipped)
258 StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType),
259 StackValue.field(fake.getType(),
260 oldObjectType,
261 fake.getNewFieldName(), false)
262 );
263 fake.setRemapValue(composed);
264 }
265 }
266
267 Parameters constructorParameters = constructorInlineBuilder.buildParameters();
268
269 RegeneratedLambdaFieldRemapper remapper =
270 new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
271 constructorParameters, invocation.getCapturedLambdasToInline(),
272 parentRemapper);
273
274 MethodInliner inliner = new MethodInliner(constructor, constructorParameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
275 remapper, isSameModule, "Transformer for constructor of " + invocation.getOwnerInternalName());
276 InlineResult result = inliner.doInline(capturedFieldInitializer, new LocalVarRemapper(constructorParameters, 0), false);
277 constructorVisitor.visitMaxs(-1, -1);
278
279 AsmUtil.genClosureFields(capturedFieldsToGenerate, classBuilder);
280 //TODO for inline method make public class
281 invocation.setNewConstructorDescriptor(constructorDescriptor);
282 return result;
283 }
284
285 @NotNull
286 private Parameters getMethodParametersWithCaptured(
287 @NotNull ParametersBuilder capturedBuilder,
288 @NotNull MethodNode sourceNode
289 ) {
290 ParametersBuilder builder = ParametersBuilder.newBuilder();
291 buildInvokeParamsFor(builder, sourceNode);
292 for (CapturedParamInfo param : capturedBuilder.listCaptured()) {
293 builder.addCapturedParamCopy(param);
294 }
295 return builder.buildParameters();
296 }
297
298 @NotNull
299 private ClassBuilder createClassBuilder() {
300 ClassBuilder classBuilder = state.getFactory().newVisitor(newLambdaType, inliningContext.getRoot().callElement.getContainingFile());
301 return new RemappingClassBuilder(classBuilder, new TypeRemapper(inliningContext.typeMapping));
302 }
303
304 @NotNull
305 private static MethodVisitor newMethod(@NotNull ClassBuilder builder, @NotNull MethodNode original) {
306 return builder.newMethod(
307 null,
308 original.access,
309 original.name,
310 original.desc,
311 original.signature,
312 original.exceptions.toArray(new String [original.exceptions.size()])
313 );
314 }
315
316 private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
317 @NotNull MethodNode constructor,
318 @NotNull ParametersBuilder capturedParamBuilder,
319 @NotNull ParametersBuilder constructorParamBuilder,
320 @NotNull final ConstructorInvocation invocation
321 ) {
322
323 CapturedParamOwner owner = new CapturedParamOwner() {
324 @Override
325 public Type getType() {
326 return Type.getObjectType(invocation.getOwnerInternalName());
327 }
328 };
329
330 List<LambdaInfo> capturedLambdas = new ArrayList<LambdaInfo>(); //captured var of inlined parameter
331 List<CapturedParamInfo> constructorAdditionalFakeParams = new ArrayList<CapturedParamInfo>();
332 Map<Integer, LambdaInfo> indexToLambda = invocation.getLambdasToInline();
333 Set<Integer> capturedParams = new HashSet<Integer>();
334
335 //load captured parameters and patch instruction list (NB: there is also could be object fields)
336 AbstractInsnNode cur = constructor.instructions.getFirst();
337 while (cur != null) {
338 if (cur instanceof FieldInsnNode) {
339 FieldInsnNode fieldNode = (FieldInsnNode) cur;
340 if (fieldNode.getOpcode() == Opcodes.PUTFIELD && InlineCodegenUtil.isCapturedFieldName(fieldNode.name)) {
341
342 boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode;
343 boolean isPrevPrevVarNode = isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode;
344
345 if (isPrevPrevVarNode) {
346 VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious();
347 if (node.var == 0) {
348 VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
349 int varIndex = previous.var;
350 LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
351 CapturedParamInfo info = capturedParamBuilder.addCapturedParam(owner, fieldNode.name, Type.getType(fieldNode.desc), lambdaInfo != null, null);
352 if (lambdaInfo != null) {
353 info.setLambda(lambdaInfo);
354 capturedLambdas.add(lambdaInfo);
355 }
356 constructorAdditionalFakeParams.add(info);
357 capturedParams.add(varIndex);
358
359 constructor.instructions.remove(previous.getPrevious());
360 constructor.instructions.remove(previous);
361 AbstractInsnNode temp = cur;
362 cur = cur.getNext();
363 constructor.instructions.remove(temp);
364 continue;
365 }
366 }
367 }
368 }
369 cur = cur.getNext();
370 }
371
372 constructorParamBuilder.addThis(oldObjectType, false);
373 Type [] types = Type.getArgumentTypes(invocation.getDesc());
374 for (Type type : types) {
375 LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex());
376 ParameterInfo parameterInfo = constructorParamBuilder.addNextParameter(type, info != null, null);
377 parameterInfo.setLambda(info);
378 if (capturedParams.contains(parameterInfo.getIndex())) {
379 parameterInfo.setCaptured(true);
380 } else {
381 //otherwise it's super constructor parameter
382 }
383 }
384
385 //For all inlined lambdas add their captured parameters
386 //TODO: some of such parameters could be skipped - we should perform additional analysis
387 Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
388 List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
389 for (LambdaInfo info : capturedLambdas) {
390 for (CapturedParamInfo var : info.getCapturedVars()) {
391 CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(var,
392 getNewFieldName(var.getOriginalFieldName()));
393 StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType),
394 StackValue.field(var.getType(),
395 oldObjectType, /*TODO owner type*/
396 recapturedParamInfo.getNewFieldName(), false)
397 );
398 recapturedParamInfo.setRemapValue(composed);
399 allRecapturedParameters.add(var);
400
401 constructorParamBuilder.addCapturedParam(var, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
402 }
403 capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
404 }
405
406
407
408 invocation.setAllRecapturedParameters(allRecapturedParameters);
409 invocation.setCapturedLambdasToInline(capturedLambdasToInline);
410
411 return constructorAdditionalFakeParams;
412 }
413
414 @NotNull
415 public String getNewFieldName(@NotNull String oldName) {
416 if (InlineCodegenUtil.THIS$0.equals(oldName)) {
417 //"this$0" couldn't clash and we should keep this name invariant for further transformations
418 return oldName;
419 }
420 return addUniqueField(oldName + "$inlined");
421 }
422
423 @NotNull
424 private String addUniqueField(@NotNull String name) {
425 List<String> existNames = fieldNames.get(name);
426 if (existNames == null) {
427 existNames = new LinkedList<String>();
428 fieldNames.put(name, existNames);
429 }
430 String suffix = existNames.isEmpty() ? "" : "$" + existNames.size();
431 String newName = name + suffix;
432 existNames.add(newName);
433 return newName;
434 }
435 }