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