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

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

The newest version!
/*
 * ProGuard -- shrinking, optimization, obfuscation, and preverification
 *             of Java bytecode.
 *
 * Copyright (c) 2002-2019 Guardsquare NV
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package proguard.classfile.editor;

import proguard.classfile.*;
import proguard.classfile.attribute.*;
import proguard.classfile.attribute.visitor.*;
import proguard.classfile.constant.Constant;
import proguard.classfile.instruction.*;
import proguard.classfile.util.*;
import proguard.classfile.visitor.*;
import proguard.obfuscate.*;
import proguard.util.ProcessingFlags;

import java.util.*;

import static proguard.classfile.ClassConstants.*;

/**
 * This editor allows to build and/or edit classes (ProgramClass instances).
 * It provides methods to easily add fields and methods to classes.
 *
 * @author Johan Leys
 */
public class SimplifiedClassEditor
extends      SimplifiedVisitor
implements
             // Implementation interfaces.
             AttributeVisitor
{
    private static final String EXTRA_INIT_METHOD_NAME       = "init$";
    private static final String EXTRA_INIT_METHOD_DESCRIPTOR = "()V";

    private final ProgramClass       programClass;
    private final ClassEditor        classEditor;
    private final ConstantPoolEditor constantPoolEditor;
    private final NameFactory        nameFactory;

    private String superClassName;

    private final List methodComposers = new ArrayList();

    private Instruction[] instructions;

    /**
     * Creates a new SimplifiedClassEditor for the Java class with the given
     * name.
     *
     * @param u2accessFlags   access flags for the new class.
     * @param className       the fully qualified name of the new class.
     * @param processingFlags processing flags for the new class.
     *
     * @see ClassConstants
     */
    public SimplifiedClassEditor(int u2accessFlags, String className, int processingFlags)
    {
        this(u2accessFlags, className, null, processingFlags);
    }


    /**
     * Creates a new SimplifiedClassEditor for the Java class with the given
     * name and super class.
     *
     * @param u2accessFlags   access flags for the new class.
     * @param className       the fully qualified name of the new class.
     * @param superclassName  the fully qualified name of the super class.
     * @param processingFlags processing flags for the new class.
     *
     * @see ClassConstants
     */
    public SimplifiedClassEditor(int    u2accessFlags,
                                 String className,
                                 String superclassName,
                                 int    processingFlags)
    {
        this(new ProgramClass(ClassConstants.CLASS_VERSION_1_2,
                              1,
                              new Constant[10],
                              u2accessFlags,
                              0,
                              0,
                              processingFlags));

        programClass.u2thisClass =
            constantPoolEditor.addClassConstant(className, programClass);

        if (superclassName != null)
        {
            programClass.u2superClass =
                constantPoolEditor.addClassConstant(superclassName, null);
            this.superClassName = superclassName;
        }
    }


    /**
     * Creates a new SimplifiedClassEditor for the given class.
     *
     * @param programClass the class to be edited.
     */
    public SimplifiedClassEditor(ProgramClass programClass)
    {
        this.programClass  = programClass;
        classEditor        = new ClassEditor(programClass);
        constantPoolEditor = new ConstantPoolEditor(programClass);
        nameFactory        = UniqueMemberNameFactory.newInjectedMemberNameFactory(programClass);
    }


    /**
     * Finalizes the editing of the class. This method does not initialize
     * references to/from related classes.
     * At least one of the finishEditing methods should be called before
     * calling {@link #getProgramClass}.
     *
     * @see #finishEditing(ClassPool, ClassPool)
     */
    public void finishEditing()
    {
        for (CodeComposer composer : methodComposers)
        {
            composer.finishEditing();
        }

        methodComposers.clear();
    }

    /**
     * Finalizes the editing of the class, and initializes all references
     * of the edited class w.r.t. the given program and library class pool.
     * At least one of the finishEditing methods should be called before
     * calling {@link #getProgramClass}.
     *
     * @param programClassPool the program class pool
     * @param libraryClassPool the library class pool
     */
    public void finishEditing(ClassPool programClassPool,
                              ClassPool libraryClassPool)
    {
        finishEditing();

        // Initialize all references to/from the edited class.
        if (superClassName != null)
        {
            new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool, null, null).visitProgramClass(programClass);
            new ClassSubHierarchyInitializer().visitProgramClass(programClass);
        }
        new ClassReferenceInitializer(programClassPool, libraryClassPool).visitProgramClass(programClass);
    }

    /**
     * Returns the edited ProgramClass instance.
     * Make sure to call one of the finishEditing methods after finishing editing,
     * before calling this method.
     *
     * @return the edited ProgramClass instance.
     *
     * @see #finishEditing()
     * @see #finishEditing(ClassPool, ClassPool)
     */
    public ProgramClass getProgramClass()
    {
        return programClass;
    }


    /**
     * Adds the given class constant to the edited class.
     *
     * @param name            the class name to be added.
     * @param referencedClass the corresponding referenced class.
     *
     * @return the constant pool index of the ClassConstant.
     */
    public int addClassConstant(String name,
                                Clazz  referencedClass)
    {
        return constantPoolEditor.addClassConstant(name, referencedClass);
    }

    /**
     * Adds a new field to the edited class.
     *
     * @param u2accessFlags    acces flags for the new field.
     * @param fieldName        name of the new field.
     * @param fieldDescriptor  descriptor of the new field.
     *
     * @return the created field.
     */
    public Field addField(int    u2accessFlags,
                          String fieldName,
                          String fieldDescriptor)
    {
        return addField(u2accessFlags, fieldName, fieldDescriptor, 0);
    }


    /**
     * Adds a new field to the edited class.
     *
     * @param u2accessFlags    acces flags for the new field.
     * @param fieldName        name of the new field.
     * @param fieldDescriptor  descriptor of the new field.
     *
     * @return the created field.
     */
    public ProgramField addField(int    u2accessFlags,
                                 String fieldName,
                                 String fieldDescriptor,
                                 int    processingFlags )
    {
        ProgramField field = new ProgramField(u2accessFlags,
                                              constantPoolEditor.addUtf8Constant(fieldName),
                                              constantPoolEditor.addUtf8Constant(fieldDescriptor),
                                              null,
                                              processingFlags);
        classEditor.addField(field);
        return field;
    }


    /**
     * Adds a new method to the edited class. The returned composer can be used
     * to attach code to the method.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param maxCodeFragmentLength maximum length for the code fragment of the
     *                              new method.
     *
     * @return the composer for adding code to the created method.
     */
    public CompactCodeAttributeComposer addMethod(int    u2accessFlags,
                                                  String methodName,
                                                  String methodDescriptor,
                                                  int    maxCodeFragmentLength)
    {
        return addMethod(u2accessFlags, methodName, methodDescriptor, null, maxCodeFragmentLength, 0);
    }


    /**
     * Adds a new method to the edited class. The returned composer can be used
     * to attach code to the method.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param referencedClasses     the classes referenced by the method descriptor.
     * @param maxCodeFragmentLength maximum length for the code fragment of the
     *                              new method.
     *
     * @return the composer for adding code to the created method.
     */
    public CompactCodeAttributeComposer addMethod(int     u2accessFlags,
                                                  String  methodName,
                                                  String  methodDescriptor,
                                                  Clazz[] referencedClasses,
                                                  int     maxCodeFragmentLength)
    {
        return addMethod(u2accessFlags, methodName, methodDescriptor, referencedClasses, maxCodeFragmentLength, 0);
    }


    /**
     * Adds a new method to the edited class, with the given instructions array.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param instructions          the instructions of the new method.
     */
    public ProgramMethod addMethod(int           u2accessFlags,
                                   String        methodName,
                                   String        methodDescriptor,
                                   Instruction[] instructions)
    {
        return addMethod(u2accessFlags, methodName, methodDescriptor, 0, instructions, null, null);
    }


    /**
     * Adds a new method to the edited class, with the given instructions array.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param processingFlags       processing flags for the new method.
     * @param instructions          the instructions of the new method.
     */
    public ProgramMethod addMethod(int           u2accessFlags,
                                   String        methodName,
                                   String        methodDescriptor,
                                   int           processingFlags,
                                   Instruction[] instructions)
    {
        return addMethod(u2accessFlags, methodName, methodDescriptor, processingFlags, instructions, null, null);
    }



    /**
     * Adds a new method to the edited class. The returned composer can be used
     * to attach code to the method.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param referencedClasses     the classes referenced by the method descriptor.
     * @param maxCodeFragmentLength maximum length for the code fragment of the
     *                              new method.
     * @param processingFlags       processing flags for the new method.
     *
     * @return the composer for adding code to the created method.
     */
    public CompactCodeAttributeComposer addMethod(int     u2accessFlags,
                                                  String  methodName,
                                                  String  methodDescriptor,
                                                  Clazz[] referencedClasses,
                                                  int     maxCodeFragmentLength,
                                                  int     processingFlags)
    {
        ProgramMethod method = new ProgramMethod(u2accessFlags,
            constantPoolEditor.addUtf8Constant(methodName),
            constantPoolEditor.addUtf8Constant(methodDescriptor),
                                               referencedClasses, processingFlags);
        CodeComposer composer = new CodeComposer(method, maxCodeFragmentLength);
        methodComposers.add(composer);
        return composer;
    }

    /**
     * Adds the given static initializer instructions to the edited class.
     * If the class already contains a static initializer, the new instructions
     * will be appended to the existing initializer.
     *
     * @param instructions                 the instructions to be added.
     * @param mergeIntoExistingInitializer indicates whether the instructions should
     *                                     be added to the existing static initializer
     *                                     (if it exists), or if a new method should
     *                                     be created, which is then called from the
     *                                     existing initializer.
     */
    public void addStaticInitializerInstructions(Instruction[] instructions,
                                                 boolean       mergeIntoExistingInitializer)
    {
        Method method = programClass.findMethod(METHOD_NAME_CLINIT, METHOD_TYPE_CLINIT);

        if (method == null)
        {
            addMethod(ACC_STATIC,
                      METHOD_NAME_CLINIT,
                      METHOD_TYPE_CLINIT,
                      0,
                      instructions,
                      null,
                      new SimpleInstruction(InstructionConstants.OP_RETURN));
        }
        else
        {
            if (!mergeIntoExistingInitializer)
            {
                // Create a new static initializer.
                ProgramMethod newMethod =
                    addMethod(ACC_STATIC,
                              nameFactory.nextName(), "()V",
                              // Make sure that such methods are not optimized (inlined)
                              // to prevent potential overflow errors during conversion.
                              ProcessingFlags.DONT_OPTIMIZE,
                              instructions,
                              null,
                              new SimpleInstruction(InstructionConstants.OP_RETURN));

                // Call the new initializer from the existing one.
                InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClass);
                builder.invokestatic(programClass.getName(),
                                      newMethod.getName(programClass),
                                      "()V",
                                      programClass,
                                      newMethod);
                instructions = builder.instructions();
            }
            CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
            ((ProgramMethod) method).attributesAccept(programClass,
                                                      new CodeAttributeEditorResetter(codeAttributeEditor));
            codeAttributeEditor.insertBeforeOffset(0, instructions);
            ((ProgramMethod) method).attributesAccept(programClass,
                                                      codeAttributeEditor);
        }
    }

    /**
     * Adds the given initialization instructions to the edited class.
     *
     * - If the class doesn't contain a constructor yet, it will be created,
     *   and the instructions will be added to this constructor.
     * - If there is a single super-calling constructor, the instructions will
     *   be added at the beginning of it's code attribute.
     * - If there are multiple super-calling constructors, a new private
     *   parameterless helper method will be created, to which the instructions
     *   will be added. An invocation to this new method will be added at the
     *   beginning of the code attribute of all super-calling constructors.
     *
     * @param instructions the instructions to be added.
     */
    public void addInitializerInstructions(Instruction[] instructions)
    {
        Method method = programClass.findMethod(METHOD_NAME_INIT, null);

        if (method == null)
        {
            // First call the super constructor.
            Instruction[] firstInstruction =
            {
                new VariableInstruction(InstructionConstants.OP_ALOAD_0),
                new ConstantInstruction(
                    InstructionConstants.OP_INVOKESPECIAL,
                    constantPoolEditor.addMethodrefConstant(programClass.getSuperName(),
                                                            METHOD_NAME_INIT,
                                                            METHOD_TYPE_INIT,
                                                            null,
                                                            null))
            };

            // End by calling return.
            SimpleInstruction lastInstruction =
                new SimpleInstruction(InstructionConstants.OP_RETURN);

            addMethod(ACC_PUBLIC,
                      METHOD_NAME_INIT,
                      METHOD_TYPE_INIT,
                      0,
                      instructions,
                      firstInstruction,
                      lastInstruction);
        }
        else
        {
            // Find all super-calling constructors.
            Set constructors =  new HashSet();
            programClass.methodsAccept(
                new ConstructorMethodFilter(
                new MethodCollector(constructors), null, null));

            if (constructors.size() == 1)
            {
                // There is only one supper-calling constructor.
                // Add the code to this constructor.
                this.instructions = instructions;
                constructors.iterator().next().accept(programClass,
                    new AllAttributeVisitor(
                    this));
            }
            else
            {
                // There are multiple super-calling constructors. Add the
                // instructions to a separate, parameterless initialization
                // method, and invoke this method from all super-calling
                // constructors.
                ProgramMethod initMethod = (ProgramMethod) programClass.findMethod(EXTRA_INIT_METHOD_NAME,
                                                                                   EXTRA_INIT_METHOD_DESCRIPTOR);
                if (initMethod == null)
                {
                    // There is no init$ method yet. Create it now, and add the
                    // given instructions to it.
                    initMethod = addMethod(ACC_PRIVATE,
                                           EXTRA_INIT_METHOD_NAME,
                                           EXTRA_INIT_METHOD_DESCRIPTOR,
                                           // Make sure that such methods are not optimized (inlined)
                                           // to prevent potential overflow errors during conversion.
                                           ProcessingFlags.DONT_OPTIMIZE,
                                           instructions,
                                           null,
                                           new SimpleInstruction(InstructionConstants.OP_RETURN));

                    // Insert a call to the new init$ method in all super-calling constructors.
                    InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClass);
                    builder.aload_0();
                    builder.invokespecial(programClass.getName(),
                                          EXTRA_INIT_METHOD_NAME,
                                          EXTRA_INIT_METHOD_DESCRIPTOR,
                                          programClass,
                                          initMethod);
                    this.instructions = builder.instructions();
                    programClass.methodsAccept(
                        new ConstructorMethodFilter(
                        new AllAttributeVisitor(
                        this), null, null));
                }
                else
                {
                    // There is already an init$ method. Add the instructions to this method.
                    this.instructions = instructions;
                    initMethod.accept(programClass,
                        new AllAttributeVisitor(
                        this));
                }
            }
        }
    }


    /**
     * Adds a new method to the edited class, with the given instructions array.
     *
     * @param u2accessFlags         acces flags for the new method.
     * @param methodName            name of the new method.
     * @param methodDescriptor      descriptor of the new method.
     * @param processingFlags       processing flags for the new method.
     * @param instructions          the instructions of the new method.
     * @param firstInstructions     extra instructions to add in front of the
     *                              new method.
     * @param lastInstruction       extra instruction to add at the end of the
     *                              new method.
     */
    private ProgramMethod addMethod(int           u2accessFlags,
                                    String        methodName,
                                    String        methodDescriptor,
                                    int           processingFlags,
                                    Instruction[] instructions,
                                    Instruction[] firstInstructions,
                                    Instruction   lastInstruction)
    {
        ProgramMethod method = new ProgramMethod(u2accessFlags,
                                                 constantPoolEditor.addUtf8Constant(methodName),
                                                 constantPoolEditor.addUtf8Constant(methodDescriptor),
                                                 null,
                                                 processingFlags);

        CodeAttribute codeAttribute =
            new CodeAttribute(constantPoolEditor.addUtf8Constant(ClassConstants.ATTR_Code));

        CodeAttributeComposer composer = new CodeAttributeComposer();
        composer.reset();
        composer.beginCodeFragment(0);
        if (firstInstructions != null)
        {
            for (Instruction instruction : firstInstructions)
            {
                composer.appendInstruction(instruction);
            }
        }
        composer.appendInstructions(instructions);
        if (lastInstruction != null)
        {
            composer.appendInstruction(lastInstruction);
        }
        composer.endCodeFragment();
        composer.visitCodeAttribute(programClass, method, codeAttribute);

        new AttributesEditor(programClass, method, false).addAttribute(codeAttribute);

        classEditor.addMethod(method);

        return method;
    }


    // Implementations for AttributeVisitor.

    public void visitAnyAttribute(Clazz clazz, Attribute attribute) {}


    public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute)
    {
        CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor();
        ((ProgramMethod) method).attributesAccept(programClass, new CodeAttributeEditorResetter(codeAttributeEditor));
        codeAttributeEditor.insertBeforeOffset(0, instructions);
        codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute);
    }


    private class CodeComposer extends CompactCodeAttributeComposer
    {
        private final ProgramMethod method;

        public CodeComposer(ProgramMethod method,
                            int           maxCodeFragmentLength)
        {
            super(programClass);
            this.method = method;
            beginCodeFragment(maxCodeFragmentLength);
        }

        public void finishEditing()
        {
            endCodeFragment();

            CodeAttribute codeAttribute =
                new CodeAttribute(constantPoolEditor.addUtf8Constant(ClassConstants.ATTR_Code));

            visitCodeAttribute(programClass, method, codeAttribute);

            new AttributesEditor(programClass, method, false).addAttribute(codeAttribute);

            classEditor.addMethod(method);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy