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