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