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