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