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