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