Post-compilation removal of annotations from byte code
Asked Answered
I

4

12

We are using a library that contains beans that are annotated with JAXB annotations. nothing in the way we use these classes depends on JAXB. in other words, we don't need JAXB and do not depend on the annotations.

However, because the annotations exist, they end up being referenced by other classes that process annotations. This requires me to bundle JAXB in our application, which isn't allowed, because JAXB is in the javax.* package (Android doesn't allow "core libraries" to be included in your application).

So, with this in mind, I'm looking for a way to remove the annotations from the compiled byte code. I know there are utilities for manipulating byte code, but this is quite new to me. How can I get started?

Ify answered 18/6, 2012 at 23:40 Comment(4)
What other classes reference them? Why do you have those classes included in your Android app?Conlee
i don't have control over the classes the reference the annotations unfortunately. they are referenced from another library.Ify
I am still curious, what is the library that references these classes?Conlee
jackson. any class that iterates over the annotations of a class will cause the same problem.Ify
C
3

I recommend BCEL 6. You can also use ASM, but I hear BCEL is easier to use. Here is a quick test method for making a field final:

public static void main(String[] args) throws Exception {
    System.out.println(F.class.getField("a").getModifiers());
    JavaClass aClass = Repository.lookupClass(F.class);
    ClassGen aGen = new ClassGen(aClass);
    for (Field field : aGen.getFields()) {
        if (field.getName().equals("a")) {
            int mods = field.getModifiers();
            field.setModifiers(mods | Modifier.FINAL);
        }
    }
    final byte[] classBytes = aGen.getJavaClass().getBytes();
    ClassLoader cl = new ClassLoader(null) {
        @Override
        protected synchronized Class<?> findClass(String name) throws ClassNotFoundException {
            return defineClass("F", classBytes, 0, classBytes.length);
        }
    };
    Class<?> fWithoutDeprecated = cl.loadClass("F");
    System.out.println(fWithoutDeprecated.getField("a").getModifiers());
}

Of course, you would actually write your classes out to disk as files and then jar them up but this is easier for trying things out. I don't have BCEL 6 handy, so I can't modify this example to remove annotations, but I imagine the code would be something like:

public static void main(String[] args) throws Exception {
    ...
    ClassGen aGen = new ClassGen(aClass);
    aGen.setAttributes(cleanupAttributes(aGen.getAttributes()));
    aGen.getFields();
    for (Field field : aGen.getFields()) {
        field.setAttributes(cleanupAttributes(field.getAttributes()));
    }
    for (Method method : aGen.getMethods()) {
        method.setAttributes(cleanupAttributes(method.getAttributes()));
    }
    ...
}

private Attribute[] cleanupAttributes(Attribute[] attributes) {
    for (Attribute attribute : attributes) {
        if (attribute instanceof Annotations) {
            Annotations annotations = (Annotations) attribute;
            if (annotations.isRuntimeVisible()) {
                AnnotationEntry[] entries = annotations.getAnnotationEntries();
                List<AnnotationEntry> newEntries = new ArrayList<AnnotationEntry>();
                for (AnnotationEntry entry : entries) {
                    if (!entry.getAnnotationType().startsWith("javax")) {
                        newEntries.add(entry);
                    }
                }
                annotations.setAnnotationTable(newEntries);
            }
        }
    }
    return attributes;
}
Conchaconchie answered 19/6, 2012 at 3:2 Comment(1)
For anyone attempting to do this - I also had to modify the "ConstantPool" (it contained the annotation even after removing it from the attributes). It might be I did something wrong, but it works for me now.Darrondarrow
P
2

I have used ByteBuddy library to remove annotations. Unfortunately I was unable to remove annotation with high level api, so I used ASM api. Here is example, how you can remove @Deprecated annotation from field of a class:

import net.bytebuddy.ByteBuddy;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.jar.asm.AnnotationVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.jar.asm.Type;
import net.bytebuddy.matcher.ElementMatchers;

import java.lang.annotation.Annotation;
import java.util.Arrays;

public class Test {

    public static class Foo {
        @Deprecated
        public Integer bar;
    }

    public static void main(String[] args) throws Exception {
        System.out.println("Annotations before processing " + getAnnotationsString(Foo.class));
        Class<? extends Foo> modifiedClass = new ByteBuddy()
                .redefine(Foo.class)
                .visit(new AsmVisitorWrapper.ForDeclaredFields()
                        .field(ElementMatchers.isAnnotatedWith(Deprecated.class),
                                new AsmVisitorWrapper.ForDeclaredFields.FieldVisitorWrapper() {
                                    @Override
                                    public FieldVisitor wrap(TypeDescription instrumentedType,
                                                             FieldDescription.InDefinedShape fieldDescription,
                                                             FieldVisitor fieldVisitor) {
                                        return new FieldVisitor(Opcodes.ASM5, fieldVisitor) {
                                            @Override
                                            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
                                                if (Type.getDescriptor(Deprecated.class).equals(desc)) {
                                                    return null;
                                                }
                                                return super.visitAnnotation(desc, visible);
                                            }
                                        };
                                    }
                                }))
                // can't use the same name, because Test$Foo is already loaded
                .name("Test$Foo1")
                .make()
                .load(Test.class.getClassLoader())
                .getLoaded();
        System.out.println("Annotations after processing " + getAnnotationsString(modifiedClass));
    }

    private static String getAnnotationsString(Class<? extends  Foo> clazz) throws NoSuchFieldException {
        Annotation[] annotations = clazz.getDeclaredField("bar").getDeclaredAnnotations();
        return Arrays.toString(annotations);
    }
}
Parlin answered 16/2, 2017 at 9:23 Comment(0)
D
1

ProGuard will also do this, in addition to obfuscating your code.

Disrobe answered 19/6, 2012 at 4:19 Comment(2)
thanks. yes, but getting proguard to run against a wide and varied set of dependencies is a project in itself. i was looking for a quicker fix (if it existed).Ify
How can I do this with ProGuard? I see a -keepattributes option that can be used for Annotations, but not seeing much about removal.Baccy
L
1

There is an additional AntTask Purge Annotation References Ant Task, which

Purge references to annotations out of the java bytecode/classfiles (remove the @Anno tag from annotated elements). Now you can use annotations to check constellations in bytecode after compilation but remove the used annos before releasing the jars.

Lamination answered 18/8, 2012 at 14:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.