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