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    }