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