Example #1
0
  @NotNull
  public static List<AbstractInsnNode> getCapturedFieldAccessChain(@NotNull VarInsnNode aload0) {
    List<AbstractInsnNode> fieldAccessChain = new ArrayList<AbstractInsnNode>();
    fieldAccessChain.add(aload0);
    AbstractInsnNode next = aload0.getNext();
    while (next != null && next instanceof FieldInsnNode || next instanceof LabelNode) {
      if (next instanceof LabelNode) {
        next = next.getNext();
        continue; // it will be delete on transformation
      }
      fieldAccessChain.add(next);
      if ("this$0".equals(((FieldInsnNode) next).name)) {
        next = next.getNext();
      } else {
        break;
      }
    }

    return fieldAccessChain;
  }
 @NotNull
 private String addUniqueField(@NotNull String name) {
   List<String> existNames = fieldNames.get(name);
   if (existNames == null) {
     existNames = new LinkedList<String>();
     fieldNames.put(name, existNames);
   }
   String suffix = existNames.isEmpty() ? "" : "$" + existNames.size();
   String newName = name + suffix;
   existNames.add(newName);
   return newName;
 }
Example #3
0
  @NotNull
  // process local and global returns (local substituted with goto end-label global kept unchanged)
  public static List<PointForExternalFinallyBlocks> processReturns(
      @NotNull MethodNode node,
      @NotNull LabelOwner labelOwner,
      boolean remapReturn,
      Label endLabel) {
    if (!remapReturn) {
      return Collections.emptyList();
    }
    List<PointForExternalFinallyBlocks> result = new ArrayList<PointForExternalFinallyBlocks>();
    InsnList instructions = node.instructions;
    AbstractInsnNode insnNode = instructions.getFirst();
    while (insnNode != null) {
      if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
        AbstractInsnNode previous = insnNode.getPrevious();
        MethodInsnNode flagNode;
        boolean isLocalReturn = true;
        String labelName = null;
        if (previous != null
            && previous instanceof MethodInsnNode
            && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
          flagNode = (MethodInsnNode) previous;
          labelName = flagNode.name;
        }

        if (labelName != null) {
          isLocalReturn = labelOwner.isMyLabel(labelName);
          // remove global return flag
          if (isLocalReturn) {
            instructions.remove(previous);
          }
        }

        if (isLocalReturn && endLabel != null) {
          LabelNode labelNode = (LabelNode) endLabel.info;
          JumpInsnNode jumpInsnNode = new JumpInsnNode(Opcodes.GOTO, labelNode);
          instructions.insert(insnNode, jumpInsnNode);
          instructions.remove(insnNode);
          insnNode = jumpInsnNode;
        }

        // genetate finally block before nonLocalReturn flag/return/goto
        result.add(
            new PointForExternalFinallyBlocks(
                isLocalReturn ? insnNode : insnNode.getPrevious(),
                getReturnType(insnNode.getOpcode())));
      }
      insnNode = insnNode.getNext();
    }
    return result;
  }
Example #4
0
  @NotNull
  protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node) {
    node = prepareNode(node);

    Analyzer<SourceValue> analyzer =
        new Analyzer<SourceValue>(new SourceInterpreter()) {
          @NotNull
          @Override
          protected Frame<SourceValue> newFrame(int nLocals, int nStack) {
            return new Frame<SourceValue>(nLocals, nStack) {
              @Override
              public void execute(
                  @NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter)
                  throws AnalyzerException {
                if (insn.getOpcode() == Opcodes.RETURN) {
                  // there is exception on void non local return in frame
                  return;
                }
                super.execute(insn, interpreter);
              }
            };
          }
        };

    Frame<SourceValue>[] sources;
    try {
      sources = analyzer.analyze("fake", node);
    } catch (AnalyzerException e) {
      throw wrapException(e, node, "couldn't inline method call");
    }

    AbstractInsnNode cur = node.instructions.getFirst();
    int index = 0;

    boolean awaitClassReification = false;
    Set<LabelNode> possibleDeadLabels = new HashSet<LabelNode>();

    while (cur != null) {
      Frame<SourceValue> frame = sources[index];

      if (frame != null) {
        if (ReifiedTypeInliner.isNeedClassReificationMarker(cur)) {
          awaitClassReification = true;
        } else if (cur.getType() == AbstractInsnNode.METHOD_INSN) {
          MethodInsnNode methodInsnNode = (MethodInsnNode) cur;
          String owner = methodInsnNode.owner;
          String desc = methodInsnNode.desc;
          String name = methodInsnNode.name;
          // TODO check closure
          int paramLength = Type.getArgumentTypes(desc).length + 1; // non static
          if (isInvokeOnLambda(owner, name) /*&& methodInsnNode.owner.equals(INLINE_RUNTIME)*/) {
            SourceValue sourceValue = frame.getStack(frame.getStackSize() - paramLength);

            LambdaInfo lambdaInfo = null;
            int varIndex = -1;

            if (sourceValue.insns.size() == 1) {
              AbstractInsnNode insnNode = sourceValue.insns.iterator().next();

              lambdaInfo = getLambdaIfExists(insnNode);
              if (lambdaInfo != null) {
                // remove inlinable access
                node.instructions.remove(insnNode);
              }
            }

            invokeCalls.add(new InvokeCall(varIndex, lambdaInfo));
          } else if (isAnonymousConstructorCall(owner, name)) {
            Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
            int paramStart = frame.getStackSize() - paramLength;

            for (int i = 0; i < paramLength; i++) {
              SourceValue sourceValue = frame.getStack(paramStart + i);
              if (sourceValue.insns.size() == 1) {
                AbstractInsnNode insnNode = sourceValue.insns.iterator().next();
                LambdaInfo lambdaInfo = getLambdaIfExists(insnNode);
                if (lambdaInfo != null) {
                  lambdaMapping.put(i, lambdaInfo);
                  node.instructions.remove(insnNode);
                }
              }
            }

            anonymousObjectGenerations.add(
                buildConstructorInvocation(owner, desc, lambdaMapping, awaitClassReification));
            awaitClassReification = false;
          }
        } else if (cur.getOpcode() == Opcodes.GETSTATIC) {
          FieldInsnNode fieldInsnNode = (FieldInsnNode) cur;
          String owner = fieldInsnNode.owner;
          if (isAnonymousSingletonLoad(owner, fieldInsnNode.name)) {
            anonymousObjectGenerations.add(
                new AnonymousObjectGeneration(
                    owner, isSameModule, awaitClassReification, isAlreadyRegenerated(owner), true));
            awaitClassReification = false;
          }
        }
      }
      AbstractInsnNode prevNode = cur;
      cur = cur.getNext();
      index++;

      // given frame is <tt>null</tt> if and only if the corresponding instruction cannot be reached
      // (dead code).
      if (frame == null) {
        // clean dead code otherwise there is problems in unreachable finally block, don't touch
        // label it cause try/catch/finally problems
        if (prevNode.getType() == AbstractInsnNode.LABEL) {
          // NB: Cause we generate exception table for default handler using gaps (see
          // ExpressionCodegen.visitTryExpression)
          // it may occurs that interval for default handler starts before catch start label, so
          // this label seems as dead,
          // but as result all this labels will be merged into one (see KT-5863)
        } else {
          node.instructions.remove(prevNode);
        }
      }
    }

    // clean dead try/catch blocks
    List<TryCatchBlockNode> blocks = node.tryCatchBlocks;
    for (Iterator<TryCatchBlockNode> iterator = blocks.iterator(); iterator.hasNext(); ) {
      TryCatchBlockNode block = iterator.next();
      if (isEmptyTryInterval(block)) {
        iterator.remove();
      }
    }

    return node;
  }
  @NotNull
  public InlineResult doTransform(
      @NotNull AnonymousObjectGeneration anonymousObjectGen,
      @NotNull FieldRemapper parentRemapper) {
    final List<InnerClassNode> innerClassNodes = new ArrayList<InnerClassNode>();
    ClassBuilder classBuilder = createClassBuilder();
    final List<MethodNode> methodsToTransform = new ArrayList<MethodNode>();

    reader.accept(
        new ClassVisitor(InlineCodegenUtil.API, classBuilder.getVisitor()) {
          @Override
          public void visit(
              int version,
              int access,
              @NotNull String name,
              String signature,
              String superName,
              String[] interfaces) {
            InlineCodegenUtil.assertVersionNotGreaterThanJava6(version, name);
            super.visit(version, access, name, signature, superName, interfaces);
          }

          @Override
          public void visitInnerClass(
              @NotNull String name, String outerName, String innerName, int access) {
            innerClassNodes.add(new InnerClassNode(name, outerName, innerName, access));
          }

          @Override
          public MethodVisitor visitMethod(
              int access,
              @NotNull String name,
              @NotNull String desc,
              String signature,
              String[] exceptions) {
            MethodNode node = new MethodNode(access, name, desc, signature, exceptions);
            if (name.equals("<init>")) {
              if (constructor != null)
                throw new RuntimeException(
                    "Lambda, SAM or anonymous object should have only one constructor");

              constructor = node;
            } else {
              methodsToTransform.add(node);
            }
            return node;
          }

          @Override
          public FieldVisitor visitField(
              int access,
              @NotNull String name,
              @NotNull String desc,
              String signature,
              Object value) {
            addUniqueField(name);
            if (InlineCodegenUtil.isCapturedFieldName(name)) {
              return null;
            } else {
              return super.visitField(access, name, desc, signature, value);
            }
          }

          @Override
          public void visitSource(String source, String debug) {
            sourceInfo = source;
            debugInfo = debug;
          }

          @Override
          public void visitEnd() {}
        },
        ClassReader.SKIP_FRAMES);

    if (!inliningContext.isInliningLambda) {
      if (debugInfo != null && !debugInfo.isEmpty()) {
        sourceMapper = SourceMapper.Companion.createFromSmap(SMAPParser.parse(debugInfo));
      } else {
        // seems we can't do any clever mapping cause we don't know any about original class name
        sourceMapper = IdenticalSourceMapper.INSTANCE;
      }
      if (sourceInfo != null && !InlineCodegenUtil.GENERATE_SMAP) {
        classBuilder.visitSource(sourceInfo, debugInfo);
      }
    } else {
      if (sourceInfo != null) {
        classBuilder.visitSource(sourceInfo, debugInfo);
      }
      sourceMapper = IdenticalSourceMapper.INSTANCE;
    }

    ParametersBuilder allCapturedParamBuilder = ParametersBuilder.newBuilder();
    ParametersBuilder constructorParamBuilder = ParametersBuilder.newBuilder();
    List<CapturedParamInfo> additionalFakeParams =
        extractParametersMappingAndPatchConstructor(
            constructor,
            allCapturedParamBuilder,
            constructorParamBuilder,
            anonymousObjectGen,
            parentRemapper);
    List<MethodVisitor> deferringMethods = new ArrayList<MethodVisitor>();

    for (MethodNode next : methodsToTransform) {
      MethodVisitor deferringVisitor = newMethod(classBuilder, next);
      InlineResult funResult =
          inlineMethodAndUpdateGlobalResult(
              anonymousObjectGen,
              parentRemapper,
              deferringVisitor,
              next,
              allCapturedParamBuilder,
              false);

      Type returnType = Type.getReturnType(next.desc);
      if (!AsmUtil.isPrimitive(returnType)) {
        String oldFunReturnType = returnType.getInternalName();
        String newFunReturnType = funResult.getChangedTypes().get(oldFunReturnType);
        if (newFunReturnType != null) {
          inliningContext.typeRemapper.addAdditionalMappings(oldFunReturnType, newFunReturnType);
        }
      }
      deferringMethods.add(deferringVisitor);
    }

    for (MethodVisitor method : deferringMethods) {
      method.visitEnd();
    }

    generateConstructorAndFields(
        classBuilder,
        allCapturedParamBuilder,
        constructorParamBuilder,
        anonymousObjectGen,
        parentRemapper,
        additionalFakeParams);

    SourceMapper.Companion.flushToClassBuilder(sourceMapper, classBuilder);

    ClassVisitor visitor = classBuilder.getVisitor();
    for (InnerClassNode node : innerClassNodes) {
      visitor.visitInnerClass(node.name, node.outerName, node.innerName, node.access);
    }

    writeOuterInfo(visitor);

    classBuilder.done();

    anonymousObjectGen.setNewLambdaType(newLambdaType);
    return transformationResult;
  }
  private List<CapturedParamInfo> extractParametersMappingAndPatchConstructor(
      @NotNull MethodNode constructor,
      @NotNull ParametersBuilder capturedParamBuilder,
      @NotNull ParametersBuilder constructorParamBuilder,
      @NotNull final AnonymousObjectGeneration anonymousObjectGen,
      @NotNull FieldRemapper parentFieldRemapper) {

    CapturedParamOwner owner =
        new CapturedParamOwner() {
          @Override
          public Type getType() {
            return Type.getObjectType(anonymousObjectGen.getOwnerInternalName());
          }
        };

    Set<LambdaInfo> capturedLambdas =
        new LinkedHashSet<LambdaInfo>(); // captured var of inlined parameter
    List<CapturedParamInfo> constructorAdditionalFakeParams = new ArrayList<CapturedParamInfo>();
    Map<Integer, LambdaInfo> indexToLambda = anonymousObjectGen.getLambdasToInline();
    Set<Integer> capturedParams = new HashSet<Integer>();

    // load captured parameters and patch instruction list (NB: there is also could be object
    // fields)
    AbstractInsnNode cur = constructor.instructions.getFirst();
    while (cur != null) {
      if (cur instanceof FieldInsnNode) {
        FieldInsnNode fieldNode = (FieldInsnNode) cur;
        String fieldName = fieldNode.name;
        if (fieldNode.getOpcode() == Opcodes.PUTFIELD
            && InlineCodegenUtil.isCapturedFieldName(fieldName)) {

          boolean isPrevVarNode = fieldNode.getPrevious() instanceof VarInsnNode;
          boolean isPrevPrevVarNode =
              isPrevVarNode && fieldNode.getPrevious().getPrevious() instanceof VarInsnNode;

          if (isPrevPrevVarNode) {
            VarInsnNode node = (VarInsnNode) fieldNode.getPrevious().getPrevious();
            if (node.var == 0) {
              VarInsnNode previous = (VarInsnNode) fieldNode.getPrevious();
              int varIndex = previous.var;
              LambdaInfo lambdaInfo = indexToLambda.get(varIndex);
              String newFieldName =
                  isThis0(fieldName)
                          && shouldRenameThis0(parentFieldRemapper, indexToLambda.values())
                      ? getNewFieldName(fieldName, true)
                      : fieldName;
              CapturedParamInfo info =
                  capturedParamBuilder.addCapturedParam(
                      owner,
                      fieldName,
                      newFieldName,
                      Type.getType(fieldNode.desc),
                      lambdaInfo != null,
                      null);
              if (lambdaInfo != null) {
                info.setLambda(lambdaInfo);
                capturedLambdas.add(lambdaInfo);
              }
              constructorAdditionalFakeParams.add(info);
              capturedParams.add(varIndex);

              constructor.instructions.remove(previous.getPrevious());
              constructor.instructions.remove(previous);
              AbstractInsnNode temp = cur;
              cur = cur.getNext();
              constructor.instructions.remove(temp);
              continue;
            }
          }
        }
      }
      cur = cur.getNext();
    }

    constructorParamBuilder.addThis(oldObjectType, false);
    String constructorDesc = anonymousObjectGen.getConstructorDesc();

    if (constructorDesc == null) {
      // in case of anonymous object with empty closure
      constructorDesc = Type.getMethodDescriptor(Type.VOID_TYPE);
    }

    Type[] types = Type.getArgumentTypes(constructorDesc);
    for (Type type : types) {
      LambdaInfo info = indexToLambda.get(constructorParamBuilder.getNextValueParameterIndex());
      ParameterInfo parameterInfo =
          constructorParamBuilder.addNextParameter(type, info != null, null);
      parameterInfo.setLambda(info);
      if (capturedParams.contains(parameterInfo.getIndex())) {
        parameterInfo.setCaptured(true);
      } else {
        // otherwise it's super constructor parameter
      }
    }

    // For all inlined lambdas add their captured parameters
    // TODO: some of such parameters could be skipped - we should perform additional analysis
    Map<String, LambdaInfo> capturedLambdasToInline =
        new HashMap<String, LambdaInfo>(); // captured var of inlined parameter
    List<CapturedParamDesc> allRecapturedParameters = new ArrayList<CapturedParamDesc>();
    boolean addCapturedNotAddOuter =
        parentFieldRemapper.isRoot()
            || (parentFieldRemapper instanceof InlinedLambdaRemapper
                && parentFieldRemapper.getParent().isRoot());
    Map<String, CapturedParamInfo> alreadyAdded = new HashMap<String, CapturedParamInfo>();
    for (LambdaInfo info : capturedLambdas) {
      if (addCapturedNotAddOuter) {
        for (CapturedParamDesc desc : info.getCapturedVars()) {
          String key = desc.getFieldName() + "$$$" + desc.getType().getClassName();
          CapturedParamInfo alreadyAddedParam = alreadyAdded.get(key);

          CapturedParamInfo recapturedParamInfo =
              capturedParamBuilder.addCapturedParam(
                  desc,
                  alreadyAddedParam != null
                      ? alreadyAddedParam.getNewFieldName()
                      : getNewFieldName(desc.getFieldName(), false));
          StackValue composed =
              StackValue.field(
                  desc.getType(),
                  oldObjectType, /*TODO owner type*/
                  recapturedParamInfo.getNewFieldName(),
                  false,
                  StackValue.LOCAL_0);
          recapturedParamInfo.setRemapValue(composed);
          allRecapturedParameters.add(desc);

          constructorParamBuilder
              .addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName())
              .setRemapValue(composed);
          if (alreadyAddedParam != null) {
            recapturedParamInfo.setSkipInConstructor(true);
          }

          if (isThis0(desc.getFieldName())) {
            alreadyAdded.put(key, recapturedParamInfo);
          }
        }
      }
      capturedLambdasToInline.put(info.getLambdaClassType().getInternalName(), info);
    }

    if (parentFieldRemapper instanceof InlinedLambdaRemapper
        && !capturedLambdas.isEmpty()
        && !addCapturedNotAddOuter) {
      // lambda with non InlinedLambdaRemapper already have outer
      FieldRemapper parent = parentFieldRemapper.getParent();
      assert parent instanceof RegeneratedLambdaFieldRemapper;
      final Type ownerType = Type.getObjectType(parent.getLambdaInternalName());

      CapturedParamDesc desc =
          new CapturedParamDesc(
              new CapturedParamOwner() {
                @Override
                public Type getType() {
                  return ownerType;
                }
              },
              InlineCodegenUtil.THIS,
              ownerType);
      CapturedParamInfo recapturedParamInfo =
          capturedParamBuilder.addCapturedParam(
              desc, InlineCodegenUtil.THIS$0 /*outer lambda/object*/);
      StackValue composed = StackValue.LOCAL_0;
      recapturedParamInfo.setRemapValue(composed);
      allRecapturedParameters.add(desc);

      constructorParamBuilder
          .addCapturedParam(recapturedParamInfo, recapturedParamInfo.getNewFieldName())
          .setRemapValue(composed);
    }

    anonymousObjectGen.setAllRecapturedParameters(allRecapturedParameters);
    anonymousObjectGen.setCapturedLambdasToInline(capturedLambdasToInline);

    return constructorAdditionalFakeParams;
  }
  private void generateConstructorAndFields(
      @NotNull ClassBuilder classBuilder,
      @NotNull ParametersBuilder allCapturedBuilder,
      @NotNull ParametersBuilder constructorInlineBuilder,
      @NotNull AnonymousObjectGeneration anonymousObjectGen,
      @NotNull FieldRemapper parentRemapper,
      @NotNull List<CapturedParamInfo> constructorAdditionalFakeParams) {
    List<Type> descTypes = new ArrayList<Type>();

    Parameters constructorParams = constructorInlineBuilder.buildParameters();
    int[] capturedIndexes =
        new int[constructorParams.getReal().size() + constructorParams.getCaptured().size()];
    int index = 0;
    int size = 0;

    // complex processing cause it could have super constructor call params
    for (ParameterInfo info : constructorParams) {
      if (!info.isSkipped()) { // not inlined
        if (info.isCaptured() || info instanceof CapturedParamInfo) {
          capturedIndexes[index] = size;
          index++;
        }

        if (size != 0) { // skip this
          descTypes.add(info.getType());
        }
        size += info.getType().getSize();
      }
    }

    String constructorDescriptor =
        Type.getMethodDescriptor(Type.VOID_TYPE, descTypes.toArray(new Type[descTypes.size()]));
    // TODO for inline method make public class
    anonymousObjectGen.setNewConstructorDescriptor(constructorDescriptor);
    MethodVisitor constructorVisitor =
        classBuilder.newMethod(
            NO_ORIGIN,
            AsmUtil.NO_FLAG_PACKAGE_PRIVATE,
            "<init>",
            constructorDescriptor,
            null,
            ArrayUtil.EMPTY_STRING_ARRAY);

    // initialize captured fields
    List<NewJavaField> newFieldsWithSkipped =
        TransformationUtilsKt.getNewFieldsToGenerate(allCapturedBuilder.listCaptured());
    List<FieldInfo> fieldInfoWithSkipped =
        TransformationUtilsKt.transformToFieldInfo(newLambdaType, newFieldsWithSkipped);

    int paramIndex = 0;
    InstructionAdapter capturedFieldInitializer = new InstructionAdapter(constructorVisitor);
    for (int i = 0; i < fieldInfoWithSkipped.size(); i++) {
      FieldInfo fieldInfo = fieldInfoWithSkipped.get(i);
      if (!newFieldsWithSkipped.get(i).getSkip()) {
        AsmUtil.genAssignInstanceFieldFromParam(
            fieldInfo, capturedIndexes[paramIndex], capturedFieldInitializer);
      }
      paramIndex++;
    }

    // then transform constructor
    // HACK: in inlinining into constructor we access original captured fields with field access not
    // local var
    // but this fields added to general params (this assumes local var access) not captured one,
    // so we need to add them to captured params
    for (CapturedParamInfo info : constructorAdditionalFakeParams) {
      CapturedParamInfo fake = constructorInlineBuilder.addCapturedParamCopy(info);

      if (fake.getLambda() != null) {
        // set remap value to skip this fake (captured with lambda already skipped)
        StackValue composed =
            StackValue.field(
                fake.getType(), oldObjectType, fake.getNewFieldName(), false, StackValue.LOCAL_0);
        fake.setRemapValue(composed);
      }
    }

    inlineMethodAndUpdateGlobalResult(
        anonymousObjectGen,
        parentRemapper,
        capturedFieldInitializer,
        constructor,
        constructorInlineBuilder,
        true);
    constructorVisitor.visitEnd();
    AsmUtil.genClosureFields(
        TransformationUtilsKt.toNameTypePair(
            TransformationUtilsKt.filterSkipped(newFieldsWithSkipped)),
        classBuilder);
  }