ProGuard ignoring annotation default values - java

The Issue
I'm having a problem with ProGuard 4.11 (An application that can optimize, shrink and obfuscate Java code), using the proguard-maven-plugin (github.com/wvengen/proguard-maven-plugin) (Although that shouldn't matter that much, since the error is occurring at runtime and the maven plugin just calls the binary with some arguments as far as I know).
I have an annotation class which is stuctured like this (nothing special):
#Retention(RetentionPolicy.RUNTIME) #Target(ElementType.FIELD)
public #interface SqlValueCache {
String value();
SqlValueCache.Type type() default Type.OBJECT_UPDATE; //Type is just an ordinary enum
}
I also have some fields annotated with that annotation, but I'm skipping the type() parameter because I want to use the default value:
#SqlValueCache("nickname")
protected SqlValueHolder<String> nick;
Now I want to process that annotation at runtime:
SqlValueCache annotation = field.getAnnotation(SqlValueCache.class); //Skipped the validation
annotation.value(); //Works fine, because specified
annotation.type(); //java.lang.annotation.IncompleteAnnotationException, *not* using the default
As stated in the comment above, I get an IncompleteAnnotationException, stating that my annotation declaration is missing the type() value. But that value should be implied by the default Type.OBJECT_UPDATE! So now I'm wondering, why is that happening?
Assumptions
I assume that the default thing is stored in some kind of attribute that I need to specify in -keepattributes, but I haven't been able to figure out if this is true or which one it is.
Reproduction
It does work just as intended when not using ProGuard.
I have also made sure that the problem is in fact the missing implied value - The code runs as intended when using ProGuard, but explicitly specifying the type(), like so:
#SqlValueCache(value = "nickname", type = Type.OBJECT_UPDATE)
protected SqlValueHolder<String> nick;
I am using this method as a temporary workaround, but this isn't the prettiest solution in my opinion. Also, as stated above, I still want to know why this error is happening.
Thanks in advance for reading and investigating my question! :)
Appendix/Meta
Yes, I did search the web for solutions and did also use the StackOverflow searchbox. Using the title of this question and various other search queries, I could only find questions complaining about no annotations being kept at all or asking to keep subclasses of classes annotated, etc. I also searched for the exception, but the only useful result was the JavaDoc (docs.oracle.com/javase/7/docs/api/java/lang/annotation/IncompleteAnnotationException.html) of the exception I am encountering.
I'm sorry about the links, but I apparently need 10 reputation to post more than two, although I really like linking stuff :/
Stacktrace
I have attached the Stacktrace I got in console (class names won't be useful at all in my opinion - I can attach them if really necessary; My classes are also loaded by a custom class loader, but I doubt that that makes any difference):
java.lang.ExceptionInInitializerError
at <class with annotated field>
(...)
Caused by: java.lang.annotation.IncompleteAnnotationException: io.github.xxyy.common.sql.builder.annotation.SqlValueCache missing element type
at sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:72) ~[?:1.7.0_51]
at com.sun.proxy.$Proxy18.type(Unknown Source)
at <line accessing type() of SqlValueCache>
(...)
ProGuard config
Here's my ProGuard config, should that be of any help (That's the part I find relevant to this question - Full file here: pastebin.com/u6wt00cj):
-dontskipnonpubliclibraryclassmembers
-target 1.7
-dontshrink
-dontoptimize
-repackageclasses io.github.xxyy.obf.towerdefense.client
-keepattributes SourceFile,LineNumberTable,*Annotations*,LocalVariable*Table
-keep,allowobfuscation class ** {
<fields>;
<methods>;
}
-keep,allowshrinking class **.annotation** {
<fields>;
<methods>;
}
# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum ** {
public static **[] values();
public static ** valueOf(java.lang.String);
}

You also need to keep the AnnotationDefault attribute:
-keepattributes AnnotationDefault
You can get the same effect by changing *Annotations* to *Annotation* in your current -keepattributes option, so the wildcards match AnnotationDefault.

Related

Binary/Qualified name is wrong? Begins with: <any?>$

I am running an annotation processor that I have wrote. It ran fine on JDK 8 and now I am experiencing a problem on JDK 12.
I have a TypeElement and I want to retrieve its binary name to pass to Class.forName.
I use javax.lang.model.util.Elements.getBinaryName(TypeElement) and it returns a garbage value <any?>$OuterClass.InnerClass instead of the expected example3.OuterClass$InnerClass.
I attempted to replace getBinaryName with TypeElement.getQualifiedName (even though it would not quite work for an inner class) but it gives me the same garbage result. I have tried searching for this issue but most search engines strip all the special characters and give me useless results.
The TypeElement was obtained by catching a MirroredTypeException like so:
try {
exampleAnnotation.value();
throw new IllegalStateException("Expected a MirroredTypeException.");
} catch (MirroredTypeException ex) {
return (TypeElement) types.asElement(ex.getTypeMirror());
}
And here is the definition of ExampleAnnotation:
package example1;
#Target(PACKAGE)
#Retention(RUNTIME)
#Documented
public #interface ExampleAnnotation {
Class<? extends Derived> value() default Derived.class;
interface Derived<A extends Annotation> extends Base<A> {
String foo();
}
}
And here is the instance of the annotation that the processor is accessing in package-info.java:
#ExampleAnnotation(OuterClass.InnerClass.class)
package example2;
import example1.ExampleAnnotation;
I have also tried the fully qualified name example3.OuterClass.InnerClass.class but that also results in garbage: <any?>$example3.OuterClass.InnerClass.
I doubt it matters but the annotation processors are still marked #SupportedSourceVersion(SourceVersion.RELEASE_8) and I am running this on Gradle 5.3.1.
I've verified the processorpath contains the jars for packages example1 and example3, including the annotation processors.
I've made no changes to account for the module system so I was thinking maybe that's somehow affecting the code.
Just tried creating a Maven project and am currently unable to reproduce the problem, so there may be an issue with my Gradle configuration, similar to what #Colin Alworth has suggested.
I had recently upgraded to a new version of Gradle and started using the "annotationProcessor" dependencies.
It appears that <any?>$ is prepended to binary/qualified class names (as it appears in the source) if the class isn't on the classpath (or if it isn't imported, or is spelled wrong). I only had the annotation's jar on the processorpath.
To alert consumers of my annotation processor of this mistake, I was able to detect it by comparing TypeElement.asType().getKind() == TypeKind.ERROR immediately after catching the MirroredTypeException.

Proguard warning with [java/lang/String]

I'm facing issue with proguard. I've some rules in proguard and one of them is:
-keep class org.jmrtd.** { *; }
-dontwarn org.jmrtd.**
But I've getting this warning when building
Unexpected error while performing partial evaluation:
Class = [org/jmrtd/MRTDFileSystem]
Method = [readBinary(II)[B]
Exception = [java.lang.IllegalArgumentException] (Can't find common super class of [java/lang/String] (with 2 known super classes) and [org/jmrtd/MRTDFileSystem$MRTDFileInfo] (with 1 known super classes))
Unexpected error while preverifying:
Class = [org/jmrtd/MRTDFileSystem]
Method = [readBinary(II)[B]
Exception = [java.lang.IllegalArgumentException] (Can't find common super class of [java/lang/String] (with 2 known super classes) and [org/jmrtd/MRTDFileSystem$MRTDFileInfo] (with 1 known super classes))
Can anybody help me?
Thank you.
It looks like a problem with library jar. Are you sure all libraries is configured properly? MRTDFileInfo is an inner class of MRTDFileSystem, which might be referenced internally.
The processing steps are as follows:
Input Jar --> Strink --> Optimize --> Obfuscate --> Preverify --> output jar
Based on your error trace you are getting an error during the Preverify step: Unexpected error while preverifying
If you want to skip this erorr you can simply use prevent Proguard from preverifying:
-dontpreverify
It will skip preverifying, but I doubt that the jar will run properly
I would suggest you provide your library jars correctly , so that proguard can refer them.
Also apart from -dontwarn , you can also use -ignorewarnings
hope this help in debugging issue.

Class '...' must be declared as 'abstract'. Checkstyle

I have this warning on most of my classes and not sure why is that. This happens on both public normal classes and final classes which have private constructors, some no constructor at all. I tried changing my private class methods to protected, doesn't help. Any suggestions on how to turn this off?
Here's a class example
public final class PlanBenefitManagerAssembler {
private static final Logger LOGGER = Logger.getLogger(PlanBenefitManagerAssembler.class);
/**
* No Instance of the this class is allowed.
*/
private PlanBenefitManagerAssembler() {
}
public static List<BenefitDecisionDetailsBean> assembleBenefitDecisionDetailsBean(
List<BenefitDetails> benefitDecisionDetailsList, int relationalSequenceNumber) {
LOGGER.debug("Enter assembleBenefitDecisionDetailsBean");
List<BenefitDecisionDetailsBean> benefitDecisionDetailsBeanList = new ArrayList<BenefitDecisionDetailsBean>();
for (BenefitDetails benefitDecisionDetails : benefitDecisionDetailsList) {
BenefitDecisionDetailsBean benefitDecisionDetailsBean = new BenefitDecisionDetailsBean();
benefitDecisionDetailsBean.setBenefitTypeCode(benefitDecisionDetails.getBenefitTypeCode());
benefitDecisionDetailsBean.setRelationSequenceNumber(relationalSequenceNumber);
benefitDecisionDetailsBean.setBenefitStatusDescription(
benefitDecisionDetails.getBenefitStatusDescription());
benefitDecisionDetailsBean.setBenefitStatusCode(benefitDecisionDetails.getBenefitStatusCode());
benefitDecisionDetailsBean.setBenefitUnderwritingStatusCode(
benefitDecisionDetails.getBenefitUnderwritingStatusCode());
benefitDecisionDetailsBean.setBenefitUnderwritingStatusDescription(
benefitDecisionDetails.getBenefitUnderwritingStatusDescription());
benefitDecisionDetailsBean.setBenefitChangeReasonCode(
String.valueOf(benefitDecisionDetails.getBenefitChangeReasonCode()));
benefitDecisionDetailsBean.setBenefitChangeReasonDescription(
benefitDecisionDetails.getBenefitChangeReasonDescription());
benefitDecisionDetailsBean.setComponentNumber(benefitDecisionDetails.getBenefitNumber());
benefitDecisionDetailsBean.setBenefitVisible(benefitDecisionDetails.isExplicitBenefitDecisionRequired());
benefitDecisionDetailsBean.setModelChanged(false);
// * Set BenefitLoading and BenefitExclusion
List<ExclusionDetailsBean> exclusionDetailsBeanList =
PlanBenefitManagerAssembler.assembleExclusionDetailsList(benefitDecisionDetails
.getBenefitExclusionsDetailsList().getBenefitExclusionsDetailsList());
List<LoadingDetailsBean> loadingDetailsBeanList =
PlanBenefitManagerAssembler.assembleLoadingDetailsList(benefitDecisionDetails
.getBenefitLoadingsDetailsList().getBenefitLoadingsDetailsList());
benefitDecisionDetailsBean.setExclusionDetailsBeanList(exclusionDetailsBeanList);
benefitDecisionDetailsBean.setLoadingDetailsBeanList(loadingDetailsBeanList);
benefitDecisionDetailsBeanList.add(benefitDecisionDetailsBean);
}
LOGGER.debug("Exit assembleBenefitDecisionDetailsBean");
return benefitDecisionDetailsBeanList;
}
}
When Checkstyle produces a warning the warning text should include a short rule name which will allow you to look up the exact rule that is being triggered. "DesignForExtension", for example.
Given the rule name, you can look up more detail on what it means in the Checkstyle documentation: http://checkstyle.sourceforge.net/availablechecks.html
Post the full details of the rule being triggered and someone might be able to help.
You can always turn the warnings off, but they generally are here for a reason :)
Do you intend to make them abstract classes ? If so, declare them that way.
Will you need to instantiate them at some point ? If so, add a public constructor.
I'm pretty sure this will solve your problem.
On sourceforge it says that the AbstractClassName rule uses the following regex:
^Abstract.*$|^.*Factory$
This causes classes with a name starting with 'Abstract' or ending with 'Factory' to be flagged. I get the 'Abstract..' part of that, but why should all '..Factory' classes be abstract? Sometimes I create factories which use dependencies to do their work so I need an instance to inject into.
This however does not explain your case. I tried your example class and did not get any Checkstyle warning (I am using the Eclipse Checkstyle Plug-in version 5.3.0.201012121300).
Are you sure you are getting the AbstractClassName warning for this class? Which version of Checkstyle are you using?

AspectJ Load time weaver doesn't detect all classes

I am using Spring's declarative transactions (the #Transactional annotation) in "aspectj" mode. It works in most cases exactly like it should, but for one it doesn't. We can call it Lang (because that's what it's actually called).
I have been able to pinpoint the problem to the load time weaver. By turning on debug and verbose logging in aop.xml, it lists all classes being woven. The problematic class Lang is indeed not mentioned in the logs at all.
Then I put a breakpoint at the top of Lang, causing Eclipse to suspend the thread when the Lang class is loaded. This breakpoint is hit while the LTW weaving other classes! So I am guessing it either tries to weave Lang and fails and doesn't output that, or some other class has a reference that forces it to load Lang before it actually gets a chance to weave it.
I am unsure however how to continue to debug this, since I am not able to reproduce it in smaller scale. Any suggestions on how to go on?
Update: Other clues are also welcome. For example, how does the LTW actually work? There appears to be a lot of magic happening. Are there any options to get even more debug output from the LTW? I currently have:
<weaver options="-XnoInline -Xreweavable -verbose -debug -showWeaveInfo">
I forgot tom mention it before: spring-agent is being used to allow LTW, i.e., the InstrumentationLoadTimeWeaver.
Based on the suggestions of Andy Clement I decided to inspect whether the AspectJ transformer is ever even passed the class. I put a breakpoint in ClassPreProcessorAgent.transform(..), and it seems that the Lang class never even reaches that method, despite it being loaded by the same class loader as other classes (an instance of Jetty's WebAppClassLoader).
I then went on to put a breakpoint in InstrumentationLoadTimeWeaver$FilteringClassFileTransformer.transform(..). Not even that one is hit for Lang. And I believe that method should be invoked for all loaded classes, regardless of what class loader they are using. This is starting to look like:
A problem with my debugging. Possibly Lang is not loaded at the time when Eclipse reports it is
Java bug? Far-fetched, but I suppose it does happen.
Next clue: I turned on -verbose:class and it appears as if Lang is being loaded prematurely - probably before the transformer is added to Instrumentation. Oddly, my Eclipse breakpoint does not catch this loading.
This means that Spring is new suspect. there appears to be some processing in ConfigurationClassPostProcessor that loads classes to inspect them. This could be related to my problem.
These lines in ConfigurationClassBeanDefinitionReader causes the Lang class to be read:
else if (metadata.isAnnotated(Component.class.getName()) ||
metadata.hasAnnotatedMethods(Bean.class.getName())) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
return true;
}
In particular, metadata.hasAnnotatedMethods() calls getDeclaredMethods() on the class, which loads all parameter classes of all methods in that class. I am guessing that this might not be the end of the problem though, because I think the classes are supposed to be unloaded. Could the JVM be caching the class instance for unknowable reasons?
OK, I have solved the problem. Essentially, it is a Spring problem in conjunction with some custom extensions. If anyone comes across something similar, I will try to explain step by step what is happening.
First of all, we have a custom BeanDefintionParser in our project. This class had the following definition:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected Class<?> getBeanClass(Element element) {
try {
return Class.forName(element.getAttribute("class"));
} catch (ClassNotFoundException e) {
throw new RuntimeException("Class " + element.getAttribute("class") + "not found.", e);
}
}
// code to parse XML omitted for brevity
}
Now, the problem occurs after all bean definition have been read and BeanDefinitionRegistryPostProcessor begins to kick in. At this stage, a class called ConfigurationClassPostProcessor starts looking through all bean definitions, to search for bean classes annotated with #Configuration or that have methods with #Bean.
In the process of reading annotations for a bean, it uses the AnnotationMetadata interface. For most regular beans, a subclass called AnnotationMetadataVisitor is used. However, when parsing the bean definitions, if you have overriden the getBeanClass() method to return a class instance, like we had, instead a StandardAnnotationMetadata instance is used. When StandardAnnotationMetadata.hasAnnotatedMethods(..) is invoked, it calls Class.getDeclaredMethods(), which in turn causes the class loader to load all classes used as parameters in that class. Classes loaded this way are not correctly unloaded, and thus never weaved, since this happens before the AspectJ transformer registered.
Now, my problem was that I had a class like so:
public class Something {
private Lang lang;
public void setLang(Lang lang) {
this.lang = lang;
}
}
Then, I had a bean of class Something that was parsed using our custom ControllerBeanDefinitionParser. This triggered the wrong annotation detection procedure, which triggered unexpected class loading, which meant that AspectJ never got a chance to weave Lang.
The solution was to not override getBeanClass(..), but instead override getBeanClassName(..), which according to the documentation is preferable:
private static class ControllerBeanDefinitionParser extends AbstractSingleBeanDefinitionParser {
protected String getBeanClassName(Element element) {
return element.getAttribute("class");
}
// code to parse XML omitted for brevity
}
Lesson of the day: Do not override getBeanClass unless you really mean it. Actually, don't try to write your own BeanDefinitionParser unless you know what you're doing.
Fin.
If your class is not mentioned in the -verbose/-debug output, that suggests to me it is not being loaded by the loader you think it is. Can you be 100% sure that 'Lang' isn't on the classpath of a classloader higher in the hierarchy? Which classloader is loading Lang at the point in time when you trigger your breakpoint?
Also, you don't mention AspectJ version - if you are on 1.6.7 that had issues with ltw for anything but a trivial aop.xml. You should be on 1.6.8 or 1.6.9.
How does ltw actually work?
Put simply, an AspectJ weaver is created for each classloader that may want to weave code. AspectJ is asked if it wants to modify the bytes for a class before it is defined to the VM. AspectJ looks at any aop.xml files it can 'see' (as resources) through the classloader in question and uses them to configure itself. Once configured it weaves the aspects as specified, taking into account all include/exclude clauses.
Andy Clement
AspectJ Project Lead
Option 1) Aspect J is open source. Crack it open and see what is going on.
Option 2) Rename your class to Bang, see if it starts working
I would not be surprised if there is hard coding to skip "lang' in there, though I can't say why.
Edit -
Seeing code like this in the source
if (superclassnameIndex > 0) { // May be zero -> class is java.lang.Object
superclassname = cpool.getConstantString(superclassnameIndex, Constants.CONSTANT_Class);
superclassname = Utility.compactClassName(superclassname, false);
} else {
superclassname = "java.lang.Object";
}
Looks like they are trying to skip weaving of java.lang.stuff.... don't see anything for just "lang" but it may be there (or a bug)

Eclipse warning: "<methodName> has non-API return type <parameterizedType>"

My co-worker and I have come across this warning message a couple times recently. For the below code:
package com.mycompany.product.data;
import com.mycompany.product.dao.GenericDAO;
public abstract class EntityBean {
public abstract GenericDAO<Object, Long> getDAO();
// ^^^^^^ <-- WARNING OCCURS HERE
}
the warning appears in the listed spot as
EntityBean.getDAO() has non-API return type GenericDAO<T, ID>
A Google search for "has non-API return type" only shows instances where this message appears in problem lists. I.e., there's no public explanation for it.
What does this mean? We can create a usage problem filter in Eclipse to make the message go away, but we don't want to do this if our usage is a legitimate problem.
Thanks!
EDIT: This warning doesn't have to do with the parameterization, as this declaration of getFactory() also results in the same warning:
public abstract class EntityBean {
protected DAOFactory getFactory() {
return DAOFactory.instance(DAOFactory.HIBERNATE);
}
}
Figured it out.
These classes (GenericDAO and DAOFactory as return types) and EntityBean were in different packages. One of the packages (the one containing EntityBean) was listed in the Export-Package: section of the manifest file, and the other package (DAOs) was not. The net effect is that the DAO classes were non-API and were being returned by an API type.
Thanks all, especially to JRL for orienting me in the right direction.
Have you looked at the following Eclipse docs: API rules of engagement and API Errors and Warnings Preferences ?

Categories