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.openapi.vfs.VirtualFile;
021    import com.intellij.util.ArrayUtil;
022    import org.jetbrains.annotations.NotNull;
023    import org.jetbrains.jet.OutputFile;
024    import org.jetbrains.jet.codegen.AsmUtil;
025    import org.jetbrains.jet.codegen.ClassBuilder;
026    import org.jetbrains.jet.codegen.FieldInfo;
027    import org.jetbrains.jet.codegen.StackValue;
028    import org.jetbrains.jet.codegen.state.GenerationState;
029    import org.jetbrains.jet.codegen.state.JetTypeMapper;
030    import org.jetbrains.org.objectweb.asm.*;
031    import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
032    import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode;
033    import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode;
034    import org.jetbrains.org.objectweb.asm.tree.MethodNode;
035    import org.jetbrains.org.objectweb.asm.tree.VarInsnNode;
036    
037    import java.io.IOException;
038    import java.util.*;
039    
040    public class AnonymousObjectTransformer {
041    
042        protected final GenerationState state;
043    
044        protected final JetTypeMapper typeMapper;
045    
046        private MethodNode constructor;
047    
048        private final InliningContext inliningContext;
049    
050        private final Type oldObjectType;
051    
052        private final Type newLambdaType;
053    
054        private final ClassReader reader;
055    
056        private final boolean isSameModule;
057    
058        private final Map<String, List<String>> fieldNames = new HashMap<String, List<String>>();
059    
060        public AnonymousObjectTransformer(
061                @NotNull String objectInternalName,
062                @NotNull InliningContext inliningContext,
063                boolean isSameModule,
064                @NotNull Type newLambdaType
065        ) {
066            this.isSameModule = isSameModule;
067            this.state = inliningContext.state;
068            this.typeMapper = state.getTypeMapper();
069            this.inliningContext = inliningContext;
070            this.oldObjectType = Type.getObjectType(objectInternalName);
071            this.newLambdaType = newLambdaType;
072    
073            //try to find just compiled classes then in dependencies
074            try {
075                OutputFile outputFile = state.getFactory().get(objectInternalName + ".class");
076                if (outputFile != null) {
077                    reader = new ClassReader(outputFile.asByteArray());
078                } else {
079                    VirtualFile file = InlineCodegenUtil.findVirtualFile(state.getProject(), objectInternalName);
080                    if (file == null) {
081                        throw new RuntimeException("Couldn't find virtual file for " + objectInternalName);
082                    }
083                    reader = new ClassReader(file.getInputStream());
084                }
085            }
086            catch (IOException e) {
087                throw new RuntimeException(e);
088            }
089        }
090    
091        private void buildInvokeParamsFor(@NotNull ParametersBuilder builder, @NotNull MethodNode node) {
092            builder.addThis(oldObjectType, false);
093    
094            Type[] types = Type.getArgumentTypes(node.desc);
095            for (Type type : types) {
096                builder.addNextParameter(type, false, null);
097            }
098        }
099    
100        @NotNull
101        public InlineResult doTransform(@NotNull ConstructorInvocation invocation, @NotNull FieldRemapper parentRemapper) {
102            ClassBuilder classBuilder = createClassBuilder();
103            final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>();
104            reader.accept(new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
105    
106                @Override
107                public void visitOuterClass(@NotNull String owner, String name, String desc) {
108                    InliningContext parent = inliningContext.getParent();
109                    assert parent != null : "Context for transformer should have parent one: " + inliningContext;
110    
111                    //we don't write owner info for lamdbas and SAMs just only for objects
112                    if (parent.isRoot() || parent.isInliningLambdaRootContext()) {
113                        //TODO: think about writing method info - there is some problem with new constructor desc calculation
114                        super.visitOuterClass(inliningContext.getParent().getClassNameToInline(), null, null);
115                        return;
116                    }
117    
118                    super.visitOuterClass(owner, name, desc);
119                }
120    
121                @Override
122                public MethodVisitor visitMethod(
123                        int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
124                ) {
125                    MethodNode node = new MethodNode(access, name, desc, signature, exceptions);
126                    if (name.equals("<init>")){
127                        if (constructor != null)
128                            throw new RuntimeException("Lambda, SAM or anonymous object should have only one constructor");
129    
130                        constructor = node;
131                    } else {
132                        methodsToTransform.add(node);
133                    }
134                    return node;
135                }
136    
137                @Override
138                public FieldVisitor visitField(
139                        int access, @NotNull String name, @NotNull String desc, String signature, Object value
140                ) {
141                    addUniqueField(name);
142                    if (InlineCodegenUtil.isCapturedFieldName(name)) {
143                        return null;
144                    } else {
145                        return super.visitField(access, name, desc, signature, value);
146                    }
147                }
148            }, ClassReader.SKIP_FRAMES);
149    
150            ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
151            ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
152            List<CapturedParamInfo> additionalFakeParams =
153                    extractParametersMappingAndPatchConstructor(constructor, allCapturedParamBuilder, constructorParamBuilder, invocation);
154    
155            InlineResult result = InlineResult.create();
156            for (MethodNode next : methodsToTransform) {
157                MethodVisitor visitor = newMethod(classBuilder, next);
158                InlineResult funResult = inlineMethod(invocation, parentRemapper, visitor, next, allCapturedParamBuilder);
159                result.addAllClassesToRemove(funResult);
160            }
161    
162            InlineResult constructorResult =
163                    generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, invocation, parentRemapper, additionalFakeParams);
164    
165            result.addAllClassesToRemove(constructorResult);
166    
167            classBuilder.done();
168    
169            invocation.setNewLambdaType(newLambdaType);
170            return result;
171        }
172    
173        @NotNull
174        private InlineResult inlineMethod(
175                @NotNull ConstructorInvocation invocation,
176                @NotNull FieldRemapper parentRemapper,
177                @NotNull MethodVisitor resultVisitor,
178                @NotNull MethodNode sourceNode,
179                @NotNull ParametersBuilder capturedBuilder
180        ) {
181    
182            Parameters parameters = getMethodParametersWithCaptured(capturedBuilder, sourceNode);
183    
184            RegeneratedLambdaFieldRemapper remapper =
185                    new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
186                                                       parameters, invocation.getCapturedLambdasToInline(),
187                                                       parentRemapper);
188    
189            MethodInliner inliner = new MethodInliner(sourceNode, parameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
190                                                      remapper, isSameModule, "Transformer for " + invocation.getOwnerInternalName());
191            InlineResult result = inliner.doInline(resultVisitor, new LocalVarRemapper(parameters, 0), false);
192            resultVisitor.visitMaxs(-1, -1);
193            return result;
194        }
195    
196        private InlineResult generateConstructorAndFields(
197                @NotNull ClassBuilder classBuilder,
198                @NotNull ParametersBuilder allCapturedBuilder,
199                @NotNull ParametersBuilder constructorInlineBuilder,
200                @NotNull ConstructorInvocation invocation,
201                @NotNull FieldRemapper parentRemapper,
202                @NotNull List<CapturedParamInfo> constructorAdditionalFakeParams
203        ) {
204            List<Type> descTypes = new ArrayList<Type>();
205    
206            Parameters constructorParams = constructorInlineBuilder.buildParameters();
207            int [] capturedIndexes = new int [constructorParams.totalSize()];
208            int index = 0;
209            int size = 0;
210    
211            //complex processing cause it could have super constructor call params
212            for (ParameterInfo info : constructorParams) {
213                if (!info.isSkipped()) { //not inlined
214                    if (info.isCaptured() || info instanceof CapturedParamInfo) {
215                        capturedIndexes[index] = size;
216                        index++;
217                    }
218    
219                    if (size != 0) { //skip this
220                        descTypes.add(info.getType());
221                    }
222                    size += info.getType().getSize();
223                }
224            }
225    
226            List<Pair<String, Type>> capturedFieldsToGenerate = new ArrayList<Pair<String, Type>>();
227            for (CapturedParamInfo capturedParamInfo : allCapturedBuilder.listCaptured()) {
228                if (capturedParamInfo.getLambda() == null) { //not inlined
229                    capturedFieldsToGenerate.add(new Pair<String, Type>(capturedParamInfo.getNewFieldName(), capturedParamInfo.getType()));
230                }
231            }
232    
233            String constructorDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, descTypes.toArray(new Type[descTypes.size()]));
234    
235            MethodVisitor constructorVisitor = classBuilder.newMethod(null,
236                                                                      AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
237                                                                      "<init>", constructorDescriptor,
238                                                                      null, ArrayUtil.EMPTY_STRING_ARRAY);
239    
240            //initialize captured fields
241            List<FieldInfo> fields = AsmUtil.transformCapturedParams(capturedFieldsToGenerate, newLambdaType);
242            int paramIndex = 0;
243            InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor);
244            for (FieldInfo fieldInfo : fields) {
245                AsmUtil.genAssignInstanceFieldFromParam(fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer);
246                paramIndex++;
247            }
248    
249            //then transform constructor
250            //HACK: in inlinining into constructor we access original captured fields with field access not local var
251            //but this fields added to general params (this assumes local var access) not captured one,
252            //so we need to add them to captured params
253            for (CapturedParamInfo info : constructorAdditionalFakeParams) {
254                CapturedParamInfo fake = constructorInlineBuilder.addCapturedParamCopy(info);
255    
256                if (fake.getLambda() != null) {
257                    //set remap value to skip this fake (captured with lambda already skipped)
258                    StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType),
259                                                              StackValue.field(fake.getType(),
260                                                                               oldObjectType,
261                                                                               fake.getNewFieldName(), false)
262                    );
263                    fake.setRemapValue(composed);
264                }
265            }
266    
267            Parameters constructorParameters = constructorInlineBuilder.buildParameters();
268    
269            RegeneratedLambdaFieldRemapper remapper =
270                    new RegeneratedLambdaFieldRemapper(oldObjectType.getInternalName(), newLambdaType.getInternalName(),
271                                                       constructorParameters, invocation.getCapturedLambdasToInline(),
272                                                       parentRemapper);
273    
274            MethodInliner inliner = new MethodInliner(constructor, constructorParameters, inliningContext.subInline(inliningContext.nameGenerator.subGenerator("lambda")),
275                                                      remapper, isSameModule, "Transformer for constructor of " + invocation.getOwnerInternalName());
276            InlineResult result = inliner.doInline(capturedFieldInitializer, new LocalVarRemapper(constructorParameters, 0), false);
277            constructorVisitor.visitMaxs(-1, -1);
278    
279            AsmUtil.genClosureFields(capturedFieldsToGenerate, classBuilder);
280            //TODO for inline method make public class
281            invocation.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(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                    null,
308                    original.access,
309                    original.name,
310                    original.desc,
311                    original.signature,
312                    original.exceptions.toArray(new String [original.exceptions.size()])
313            );
314        }
315    
316        private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
317                @NotNull MethodNode constructor,
318                @NotNull ParametersBuilder capturedParamBuilder,
319                @NotNull ParametersBuilder constructorParamBuilder,
320                @NotNull final ConstructorInvocation invocation
321        ) {
322    
323            CapturedParamOwner owner = new CapturedParamOwner() {
324                @Override
325                public Type getType() {
326                    return Type.getObjectType(invocation.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 = invocation.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            Type [] types = Type.getArgumentTypes(invocation.getDesc());
374            for (Type type : types) {
375                LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex());
376                ParameterInfo parameterInfo = constructorParamBuilder.addNextParameter(type, info != null, null);
377                parameterInfo.setLambda(info);
378                if (capturedParams.contains(parameterInfo.getIndex())) {
379                    parameterInfo.setCaptured(true);
380                } else {
381                    //otherwise it's super constructor parameter
382                }
383            }
384    
385            //For all inlined lambdas add their captured parameters
386            //TODO: some of such parameters could be skipped - we should perform additional analysis
387            Map<String, LambdaInfo> capturedLambdasToInline = new HashMap<String, LambdaInfo>(); //captured var of inlined parameter
388            List<CapturedParamInfo> allRecapturedParameters = new ArrayList<CapturedParamInfo>();
389            for (LambdaInfo info : capturedLambdas) {
390                for (CapturedParamInfo var : info.getCapturedVars()) {
391                    CapturedParamInfo recapturedParamInfo = capturedParamBuilder.addCapturedParam(var,
392                                                                                                  getNewFieldName(var.getOriginalFieldName()));
393                    StackValue composed = StackValue.composed(StackValue.local(0, oldObjectType),
394                                                              StackValue.field(var.getType(),
395                                                                               oldObjectType, /*TODO owner type*/
396                                                                               recapturedParamInfo.getNewFieldName(), false)
397                    );
398                    recapturedParamInfo.setRemapValue(composed);
399                    allRecapturedParameters.add(var);
400    
401                    constructorParamBuilder.addCapturedParam(var, recapturedParamInfo.getNewFieldName()).setRemapValue(composed);
402                }
403                capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
404            }
405    
406    
407    
408            invocation.setAllRecapturedParameters(allRecapturedParameters);
409            invocation.setCapturedLambdasToInline(capturedLambdasToInline);
410    
411            return constructorAdditionalFakeParams;
412        }
413    
414        @NotNull
415        public String getNewFieldName(@NotNull String oldName) {
416            if (InlineCodegenUtil.THIS$0.equals(oldName)) {
417                //"this$0" couldn't clash and we should keep this name invariant for further transformations
418                return oldName;
419            }
420            return addUniqueField(oldName + "$inlined");
421        }
422    
423        @NotNull
424        private String addUniqueField(@NotNull String name) {
425            List<String> existNames = fieldNames.get(name);
426            if (existNames == null) {
427                existNames = new LinkedList<String>();
428                fieldNames.put(name, existNames);
429            }
430            String suffix = existNames.isEmpty() ? "" : "$" + existNames.size();
431            String newName = name + suffix;
432            existNames.add(newName);
433            return newName;
434        }
435    }