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