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    }