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