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