All Downloads are FREE. Search and download functionalities are using the official Maven repository.

proguard.classfile.editor.MemberAdder Maven / Gradle / Ivy

Go to download

ProGuardCORE is a free library to read, analyze, modify, and write Java class files.

There is a newer version: 9.1.6
Show newest version
/*
 * ProGuardCORE -- library to process Java bytecode.
 *
 * Copyright (c) 2002-2020 Guardsquare NV
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package proguard.classfile.editor;

import java.io.*;
import proguard.classfile.*;
import proguard.classfile.attribute.Attribute;
import proguard.classfile.io.*;
import proguard.classfile.visitor.*;
import proguard.util.*;

/**
 * This {@link MemberVisitor} copies all class members that it visits to the given target class.
 * Their processing info is set to the class members from which they were copied.
 *
 * @author Eric Lafortune
 */
public class MemberAdder implements MemberVisitor {
  // *
  private static final boolean DEBUG = false;
  /*/
  private static       boolean DEBUG = true;
  //*/

  private static final Attribute[] EMPTY_ATTRIBUTES = new Attribute[0];

  private final ProgramClass targetClass;
  private final StringFunction nameTransformer;
  private final MemberVisitor extraMemberVisitor;

  private final ClassEditor classEditor;
  private final ConstantPoolEditor constantPoolEditor;

  /**
   * Creates a new MemberAdder that will copy methods into the given target class.
   *
   * @param targetClass the class to which all visited class members will be added.
   */
  public MemberAdder(ProgramClass targetClass) {
    this(targetClass, null);
  }

  /**
   * Creates a new MemberAdder that will copy methods into the given target class.
   *
   * @param targetClass the class to which all visited class members will be added.
   * @param extraMemberVisitor an optional member visitor that visits each new member right after it
   *     has been added. This allows changing the processing info, for instance.
   */
  public MemberAdder(ProgramClass targetClass, MemberVisitor extraMemberVisitor) {
    this(targetClass, null, extraMemberVisitor);
  }

  /**
   * Creates a new MemberAdder that will copy methods into the given target class.
   *
   * @param targetClass the class to which all visited class members will be added.
   * @param nameTransformer the transformer to be applied to each member's name before copying. If
   *     null, the original name will be used.
   * @param extraMemberVisitor an optional member visitor that visits each new member right after it
   *     has been added. This allows changing the processing info, for instance.
   */
  public MemberAdder(
      ProgramClass targetClass, StringFunction nameTransformer, MemberVisitor extraMemberVisitor) {
    this.targetClass = targetClass;
    this.nameTransformer = nameTransformer;
    this.extraMemberVisitor = extraMemberVisitor;

    classEditor = new ClassEditor(targetClass);
    constantPoolEditor = new ConstantPoolEditor(targetClass);
  }

  // Implementations for MemberVisitor.

  public void visitProgramField(ProgramClass programClass, ProgramField programField) {
    String name = programField.getName(programClass);
    String descriptor = programField.getDescriptor(programClass);
    int accessFlags = programField.getAccessFlags();

    if (nameTransformer != null) {
      name = nameTransformer.transform(name);
    }

    // TODO: Handle field with the same name and descriptor in the target class.
    // We currently avoid this case, since renaming the identical field
    // still causes confused field references.
    //// Does the target class already have such a field?
    // ProgramField targetField = (ProgramField)targetClass.findField(name, descriptor);
    // if (targetField != null)
    // {
    //    // Is the field private or static?
    //    int targetAccessFlags = targetField.getAccessFlags();
    //    if ((targetAccessFlags &
    //         (AccessConstants.PRIVATE |
    //          AccessConstants.STATIC)) != 0)
    //    {
    //        // Rename the private or static field.
    //        String newName = newUniqueMemberName(name, targetClass.getName());
    //
    //        if (DEBUG)
    //        {
    //            System.out.println("MemberAdder: renaming field
    // ["+targetClass.getName()+"."+name+" "+descriptor+"] to ["+newName+"]");
    //        }
    //
    //        targetField.u2nameIndex = constantPoolEditor.addUtf8Constant(newName);
    //    }
    //    else
    //    {
    //        // Keep the non-private and non-static field, but update its
    //        // contents, in order to keep any references to it valid.
    //        if (DEBUG)
    //        {
    //            System.out.println("MemberAdder: updating field
    // ["+programClass+"."+programField.getName(programClass)+"
    // "+programField.getDescriptor(programClass)+"] into ["+targetClass.getName()+"]");
    //        }
    //
    //        // Combine the access flags.
    //        targetField.u2accessFlags = accessFlags | targetAccessFlags;
    //
    //        // Add and replace any attributes.
    //        programField.attributesAccept(programClass,
    //                                      new AttributeAdder(targetClass,
    //                                                         targetField,
    //                                                         true));
    //
    //        // Don't add a new field.
    //        return;
    //    }
    // }

    if (DEBUG) {
      System.out.println(
          "MemberAdder: copying field ["
              + programClass
              + "."
              + programField.getName(programClass)
              + " "
              + programField.getDescriptor(programClass)
              + "] into ["
              + targetClass.getName()
              + "]");
    }

    // Create a copy of the field, with the same processing flags and with
    // the processing info linking to this field.
    ProgramField newProgramField =
        new ProgramField(
            accessFlags,
            constantPoolEditor.addUtf8Constant(name),
            constantPoolEditor.addUtf8Constant(descriptor),
            0,
            programField.u2attributesCount > 0
                ? new Attribute[programField.u2attributesCount]
                : EMPTY_ATTRIBUTES,
            programField.referencedClass,
            programField.processingFlags,
            programField);

    // Copy its attributes.
    programField.attributesAccept(
        programClass, new AttributeAdder(targetClass, newProgramField, false));

    // Add the completed field.
    classEditor.addField(newProgramField);

    // Visit the newly added field, if necessary.
    if (extraMemberVisitor != null) {
      extraMemberVisitor.visitProgramField(targetClass, newProgramField);
    }
  }

  public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) {
    String name = programMethod.getName(programClass);
    String descriptor = programMethod.getDescriptor(programClass);
    int accessFlags = programMethod.getAccessFlags();

    if (nameTransformer != null) {
      name = nameTransformer.transform(name);
    }

    // Does the target class already have such a method?
    ProgramMethod targetMethod = (ProgramMethod) targetClass.findMethod(name, descriptor);
    if (targetMethod != null) {
      // is this source method abstract?
      if ((accessFlags & AccessConstants.ABSTRACT) != 0) {
        // Keep the target method.
        if (DEBUG) {
          System.out.println(
              "MemberAdder: skipping abstract method ["
                  + programClass.getName()
                  + "."
                  + name
                  + descriptor
                  + "] into ["
                  + targetClass.getName()
                  + "]");
        }

        // Don't add a new method.
        return;
      }

      // Is the target method abstract?
      int targetAccessFlags = targetMethod.getAccessFlags();
      if ((targetAccessFlags & AccessConstants.ABSTRACT) != 0) {
        // Keep the abstract method, but update its contents, in order
        // to keep any references to it valid.
        if (DEBUG) {
          System.out.println(
              "MemberAdder: updating method ["
                  + programClass.getName()
                  + "."
                  + name
                  + descriptor
                  + "] into ["
                  + targetClass.getName()
                  + "]");
        }

        // Replace the access flags.
        targetMethod.u2accessFlags = accessFlags & ~AccessConstants.FINAL;

        // Add and replace the attributes.
        programMethod.attributesAccept(
            programClass, new AttributeAdder(targetClass, targetMethod, true));

        // Don't add a new method.
        return;
      }

      if (DEBUG) {
        System.out.println(
            "MemberAdder: renaming method ["
                + targetClass.getName()
                + "."
                + name
                + descriptor
                + "]");
      }

      // TODO: Handle non-abstract method with the same name and descriptor in the target class.
      // We currently avoid this case, since renaming the identical method
      // still causes confused method references.
      //// Rename the private (non-abstract) or static method.
      // targetMethod.u2nameIndex =
      //    constantPoolEditor.addUtf8Constant(newUniqueMemberName(name, descriptor));
    }

    if (DEBUG) {
      System.out.println(
          "MemberAdder: copying method ["
              + programClass.getName()
              + "."
              + name
              + descriptor
              + "] into ["
              + targetClass.getName()
              + "]");
    }

    // Create a copy of the method, with the same processing flags and with
    // the processing info linking to this method.
    ProgramMethod newProgramMethod =
        new ProgramMethod(
            accessFlags & ~AccessConstants.FINAL,
            constantPoolEditor.addUtf8Constant(name),
            constantPoolEditor.addUtf8Constant(descriptor),
            0,
            programMethod.u2attributesCount > 0
                ? new Attribute[programMethod.u2attributesCount]
                : EMPTY_ATTRIBUTES,
            ArrayUtil.cloneOrNull(programMethod.referencedClasses),
            programMethod.processingFlags,
            programMethod);

    // Copy its attributes.
    programMethod.attributesAccept(
        programClass, new AttributeAdder(targetClass, newProgramMethod, false));

    // Add the completed method.
    classEditor.addMethod(newProgramMethod);

    // Visit the newly added method, if necessary.
    if (extraMemberVisitor != null) {
      extraMemberVisitor.visitProgramMethod(targetClass, newProgramMethod);
    }
  }

  // Small utility methods.

  /** Returns a unique class member name, based on the given name and descriptor. */
  private String newUniqueMemberName(String name, String descriptor) {
    return name.equals(ClassConstants.METHOD_NAME_INIT)
        ? ClassConstants.METHOD_NAME_INIT
        : name
            + TypeConstants.SPECIAL_MEMBER_SEPARATOR
            + Long.toHexString(Math.abs((descriptor).hashCode()));
  }

  /**
   * This main method illustrates and tests the class, by reading an input class file and copying
   * its class members into a new class that it writes to an output class file.
   */
  public static void main(String[] args) {
    String inputClassFileName = args[0];
    String outputClassFileName = args[1];

    try {
      DataInputStream dataInputStream =
          new DataInputStream(new FileInputStream(inputClassFileName));

      try {
        // Read the input class.
        ProgramClass inputProgramClass = new ProgramClass();

        inputProgramClass.accept(new ProgramClassReader(dataInputStream));

        // Create an empty output class.
        ProgramClass outputProgramClass =
            new ClassBuilder(
                    VersionConstants.CLASS_VERSION_1_8,
                    AccessConstants.PUBLIC,
                    "com/example/Test",
                    ClassConstants.NAME_JAVA_LANG_OBJECT)
                .getProgramClass();

        // Copy over the class members.
        MemberAdder memberAdder = new MemberAdder(outputProgramClass);

        inputProgramClass.fieldsAccept(memberAdder);
        inputProgramClass.methodsAccept(memberAdder);

        // Print out the output class.
        // outputProgramClass.accept(new ClassPrinter());

        // Write out the output class.
        DataOutputStream dataOutputStream =
            new DataOutputStream(new FileOutputStream(outputClassFileName));

        try {
          outputProgramClass.accept(new ProgramClassWriter(dataOutputStream));
        } finally {
          dataOutputStream.close();
        }
      } finally {
        dataInputStream.close();
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy