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