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