-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Java: Improve and add predicates and classes for annotations #6246
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Java: Improve and add predicates and classes for annotations #6246
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks broadly good. Three top-level suggestions:
- Maintain backward compatibility: let the existing
getAnAnnotation
etc retain the current behaviour and introduce new methods that include inheritance - Some simple changes (e.g. introducing
getReferencedType
,getCheckedType
) are noisy and not very related to annotations. Let these have their own PR. - Let's have some simple tests to check the right methods do and don't see inheritance
* gets the enum constant used as value for that element. This includes default values in | ||
* case no explicit value is specified. | ||
*/ | ||
EnumConstant getValueEnumConstant(string name) { result = getValue(name).(FieldRead).getField() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really specific to annotations. Perhaps provide a class EnumConstantRead extends FieldRead
with relevant convenience methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't really specific to annotations.
It is, isn't it? Enums are one of the value types an annotation type element can have.
Consider this example for @Target
:
@Target(ElementType.ANNOTATION_TYPE)
public @interface MetaAnnotationType {
...
}
The added CodeQL predicate would allow easily getting ElementType.ANNOTATION_TYPE
here.
How would a EnumConstantRead
help for this use case? Or do you mean something different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean that the desire to interpret a FieldRead
as an enum constant read goes far beyond annotations, so we should provide something like
class EnumConstantRead extends FieldRead {
EnumConstantRead() { this.getField() instanceof EnumConstant }
EnumConstant getEnumConstant() { result = this.getField() }
}
Then use it in this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, ok I thought you meant this predicate should be removed.
Though I am not sure if EnumConstantRead
adds that much value; it probably only saves you one EnumConstant
cast for the result of getField()
in most cases.
* gets the referenced type used as value for that element. This includes default values | ||
* in case no explicit value is specified. | ||
*/ | ||
Type getValueClass(string name) { result = getValue(name).(TypeLiteral).getReferencedType() } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename getReferencedType
for consistency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Classes are only one possible value type of annotation type elements, I chose getValue...
for consistency with the other predicates retrieving an annotation value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have renamed it to getTypeValue
.
*/ | ||
// Note: Cannot use a predicate with string as result because annotation type without | ||
// explicit @Target can be applied to all targets, requiring to hardcode element types here | ||
bindingset[elementType] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there is a small set of possible values you can do something like class AnnotationTargetType extends string { AnnotationTargetType() { this = ["CLASS", "METHOD", ...] } }
and drop the bindingset
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that would be doable, but I wanted to avoid this (see "Note: .." comment a few lines above) because the set of possible target types would have to be adjusted then whenever a new constant is added to ElementType
(recents ones were MODULE
and RECORD_COMPONENT
).
Additionally it might cause confusing results when a Java database is not using the latest Java version. Consider a Java 8 project for which this predicate claims that RECORD_COMPONENT
(added for Java 16) is a target.
But maybe that is not actually a big issue, especially since this predicate already relies on JDK-8231435 which was fixed only for Java 14.
I would prefer not using bindingset
here as well.
result = this.getAValue() or | ||
result = this.getAValue().(ArrayInit).getAnInit() | ||
) | ||
result = this.getAValue(_) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keep a no-arg getAValue
method, since we seem to use it a lot?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not remove getAValue()
. But as mentioned in https://github.com/github/codeql/pull/6246/files#r668908394, getAValue()
and getAValue(string)
are actually for different use cases. The first one is for single values, the second one is for single and array values.
But while looking at it, I think the not result instanceof ArrayInit and
in the line above can now be removed with this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I have marked getAValue()
as deprecated now because it is error-prone. It could by accident match the wrong annotation type element, especially when in the future new elements are added to an annotation type.
However,
Fair point, I will create a separate pull request for them. |
Agreed that |
a82a672
to
5b5d837
Compare
Sorry for the long delay. Have rebased the changes onto |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks plausible, just some doc fixes here
} | ||
|
||
/** | ||
* If the annotation element with the specified `name` has a Java `Class` as value type, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* If the annotation element with the specified `name` has a Java `Class` as value type, | |
* If the annotation element with the specified `name` is of type `java.lang.Class`, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have chosen now the following to be consistent with the documentation of the other predicates:
If the value type of the annotation element with the specified
name
isjava.lang.Class
I hope that is alright.
then elementType = getADeclaredAnnotation().(TargetAnnotation).getATargetElementType() | ||
else | ||
// No Target annotation means "applicable to all contexts" since JDK 14, see https://bugs.openjdk.java.net/browse/JDK-8231435 | ||
// The compiler does not completely implement that, but pretend it did |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand on this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It think I was referring to some bugs with module declarations, such as JDK-8254023; I will expand the comment.
However, the behavior for a missing @Target
annotation changed again in Java 17, now it only includes declaration contexts, but excludes type usage contexts (JDK-8261610). I will mention this in the comment as well.
I will probably still keep the predicate as isATargetType
for now but exclude TYPE_USE
if no @Target
annotation is present, this seems slightly less error-prone than a getATargetType()
predicate which is more likely to become outdated as the JDK evolves and new declaration contexts are added.
Or what do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about naming the predicate mayBeTargetType
, and note in qldoc that the meaning of no-@Target
has changed over time, with this predicate taking the broadest interpretation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mayBeTargetType
(or rather mayBeATargetType
?) sounds a bit weird to me, but if you want I can rename it nonetheless.
Though extending the documentation sounds like a good idea, but I think I will rather mention "latest Java version" or similar, since "broadest" might sound like applicable in all contexts (including type contexts), which is no longer the case since Java 17.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getALikelyTargetType
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getA...
would have to be a predicate with result then, right? That is not possible with the current implementation.
My main concerns with mayBeTargetType
is that personally I find that "may be" sounds a bit weird (and too close to "maybe", especially as predicate / method prefix), but this is probably rather subjective; and that this name expresses some uncertainty (I think), even though the predicate is fairly reliable (unless you provide it with strings which are not ElementType
values).
} | ||
|
||
/** Holds if this annotation type is annotated with the meta-annotation `@Repeatable`. */ | ||
predicate isRepeatable() { getADeclaredAnnotation() instanceof RepeatableAnnotation } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getADeclared... this isn't inheritable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes getADeclaredAnnotation()
does not include inherited annotations, but @Repeatable
is not inheritable, so this is correct, isn't it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't know, was just asking you to ensure that's what you intended. Sounds like it's fine.
I think this pull request is now complete and ready for a full review. Thanks already for the previous comments! |
* The predicate `Annotation.getAValue()` has been deprecated because it might lead to obtaining the value of the wrong annotation element by accident. `getValue(string)` (or one of the value type specific predicates) should be used to explicitly specify the name of the annotation element. | ||
* The predicate `Annotation.getAValue(string)` has been renamed to `getAnArrayValue(string)`. | ||
* The predicate `SuppressWarningsAnnotation.getASuppressedWarningLiteral()` has been deprecated because it unnecessarily restricts the result type; `getASuppressedWarning()` should be used instead. | ||
* The predicates `TargetAnnotation.getATargetExpression()` and `RetentionAnnotation.getRetentionPolicyExpression()` have been deprecated because getting the enum constant read expression is rarely useful, instead the corresponding predicates for getting the name of the referenced enum constants should be used. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, maybe there are use cases where one wants to get the location of the expression, e.g. to include it in the query result set. But those use cases might still be rare and rather apply to custom annotations and not these JDK default ones.
54985a6
to
31ea7a5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing autoformat.
java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll
Outdated
Show resolved
Hide resolved
java/ql/lib/semmle/code/java/frameworks/spring/SpringController.qll
Outdated
Show resolved
Hide resolved
|
@smowton Looks like we hit an extractor error here. |
…tions Additionally changes `hasAnnotation()` to consider inherited annotations for consistency.
Predicate name could lead to confusion with non-array predicate getAValue()
As suggested by smowton during review.
As mentioned by smowton during review, the predicate only has a single result due to being restricted by the index and therefore its name should not start with "getA...". Also remove deprecated `getAValue(string, int)` because it never existed on the `main` branch.
Autoformat
…ion handling has been fixed
209686d
to
0ab5d46
Compare
I just merged a fix to the Java extractor that resolves the database inconsistency seen above. I've rebased this PR on latest @Marcono1234 I don't think this should impact your work, but the modified extractor records an |
Thanks! I guess that is fine and I assume you had your reasons for implementing it like this in the extractor.
One would be the source expression and one the non-source expression, right? Is the "Java Language Tests" check failure due to a failing test, or have your extractor changes simply not been included in the CodeQL version running the "Java Language Tests" yet? |
It failed because the deprecation for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Proxying Schack's previous approval
Tries to improve the CodeQL classes and predicates for working with annotations.
The most important changes are in:
java/ql/src/semmle/code/java/Annotation.qll
java/ql/src/semmle/code/java/JDKAnnotations.qll