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    }