-
Notifications
You must be signed in to change notification settings - Fork 5
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
Include generated code in determining effective regions. #118
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.
I think I get the high-level idea. I have a few comments, mostly around use of enum
s instead of String
s
@@ -141,6 +141,8 @@ public class Config { | |||
*/ | |||
private NullAwayVersionAdapter adapter; | |||
|
|||
public final ImmutableSet<String> generatedCodeDetectors; |
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.
Rather than String
s it'd be better to create an enum
of supported detectors. You could then create the set using Guava's Sets.immutableEnumSet
https://guava.dev/releases/snapshot/api/docs/com/google/common/collect/Sets.html#immutableEnumSet(E,E...)
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.
+ values.length); | ||
String regionMember = | ||
!Region.getType(values[1]).equals(Region.Type.METHOD) ? "null" : values[1]; | ||
return new TrackerNode(new Region(values[0], regionMember), values[2], values[3]); | ||
return new TrackerNode(new Region(values[0], regionMember, values[4]), values[2], values[3]); |
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 change relates to changed output from annotator-scanner, correct?
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 exactly.
* Type of region. If region exists in source code, this value is {@code "SOURCE"}, otherwise it | ||
* will be the name of the processor created this region. (e.g. {"LOMBOK"}). | ||
*/ | ||
public final String sourceType; |
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.
Again an enum would be better than a String here. Maybe SOURCE
should be in the enum I suggested above?
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.
* @return Name of the generator that produced the code and {@code "SOURCE"} if element exists in | ||
* source code. | ||
*/ | ||
String getSourceForSymbolAtPath(TreePath path); |
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.
Again use enum for return
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.
public class LombokGeneratedCodeDetector implements GeneratedCodeDetector { | ||
|
||
/** Associated name for Lombok in region serializations in <i>SOURCE_TYPE</i> column. */ | ||
private static final String NAME = "LOMBOK"; |
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.
use enum
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.
@Override | ||
public Set<Region> extendWithGeneratedRegions(Set<Region> regions) { | ||
return regions.stream() | ||
// filter regions where are created by lombok |
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 think the idea here is that for any Lombok-generated method in regions
, we also add all uses of that method to the impacted regions. Is this the right idea? If so, let's add some comments / Javadoc documenting the logic here. Also we should state briefly why we think this is sufficient to get all the relevant regions, given the documented assumptions on how Lombok propagates annotations (see previous comment)
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.
...-core/src/main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/LombokTracker.java
Show resolved
Hide resolved
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.
Did a pass. Also, again, I think the description could use a bit of an edit/proofread. It's useful, but there are a few sentences that are harder to parse than they need to be.
* @return Associated Source type of the generator that produced the code. If element exists in | ||
* source code and not produced by any processor, it will return {@link SourceType#SOURCE} | ||
*/ | ||
SourceType getSourceForSymbolAtPath(TreePath path); |
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.
Wondering if this should be a method of Config
or if it makes more sense to have Config
merely return a separate object with this method (SymbolSourceResolver
?), just to avoid config doing work not related to parsing configuration options.
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.
...ator-scanner/src/main/java/edu/ucr/cs/riple/scanner/generatedcode/GeneratedCodeDetector.java
Outdated
Show resolved
Hide resolved
path.getLeaf() instanceof MethodTree | ||
? (MethodTree) path.getLeaf() | ||
: ASTHelpers.findEnclosingNode(path, MethodTree.class); | ||
if (enclosingMethod == null && path.getLeaf() instanceof MethodTree) { |
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 is this case not covered by the conditional expression above? Seems like an exact repetition of the first case of the conditional...
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 it true, thank you for noticing this.
enclosingMethod = (MethodTree) path.getLeaf(); | ||
} | ||
if (enclosingMethod == null) { | ||
return false; |
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.
So, this method is incomplete... no? What happens with e.g generated inner classes or generated fields?
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 was only focusing on generated methods which can be called outside of the class and extend the potentially impacted regions. But, you are right, I don't see any harm in having a comprehensive check on the generated code of any type. Please see 3621feb. Used the same logic for detecting region member which already covers all cases.
check("NullAway", CheckSeverity.ERROR) | ||
check("AnnotatorScanner", CheckSeverity.ERROR) | ||
option("NullAway:AnnotatedPackages", "test") | ||
option("NullAway:SerializeFixMetadata", "true") |
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.
Note that, internally we also run with the following related configuration flags, not sure if this makes lombok detection harder or easier for the auto-annotator, but worth keeping in mind:
-XepOpt:NullAway:ExcludedClassAnnotations=[...],lombok.Generated
-XepOpt:NullAway:ExternalInitAnnotations=[...],lombok.Builder
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.
Thank you for noting this. I will add these flags, in the followup PR for adding more unit tests.
new Option( | ||
"ardl", | ||
"activate-region-detection-lombok", | ||
false, |
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.
Any reason why we want this to be false
by default? What's the cost on non-Lombok code? (Btw, I absolutely agree there should be a flag to control this, but maybe this should be on by default and possible to turn off via a flag?)
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.
94f67a7 activated it by default.
new FieldRegionTracker(config, info), | ||
methodRegionTracker, | ||
new ParameterRegionTracker(tree, methodRegionTracker)); | ||
Set<GeneratedRegionTracker> generatedRegionTrackers = new HashSet<>(); |
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.
Why not build with ImmutableSet.builder()
rather than hashset and then copyOf
?
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.
Set<Region> regions = new HashSet<>(); | ||
this.trackers.forEach(tracker -> tracker.getRegions(location).ifPresent(regions::addAll)); | ||
this.generatedRegionsTrackers.forEach( | ||
tracker -> regions.addAll(tracker.extendWithGeneratedRegions(regions))); |
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.
Minor note here: This is safe as long as there aren't cases where generated code from one processor is derived from generated code from another, or if the GeneratedRegionTracker
s are all listed in the same order as that in which processors run (assuming no cycles and no concurrency of annotation processors? Not sure how they actually work here, cc: @msridhar ). Otherwise this would need to be run to a fix-point. That said, I think it's very rare for generated code from one processor to be then fed to another, so I wouldn't worry about handle that case for now...
...-core/src/main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/LombokTracker.java
Outdated
Show resolved
Hide resolved
Optional<Set<Region>> ans = tracker.getRegions(onMethod); | ||
return ans.isPresent() ? ans.get().stream() : Stream.of(); | ||
}) | ||
.collect(Collectors.toSet()); |
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 works on methods only and one hop, right? I.e. if our normal exploration adds @Nullable
to field Foo.bar
and Foo
is @Data
, our initial affected regions (before this fix) will include getBar()
. Then this chance makes sure we add all methods/fields/etc that would be affected by getBar()
itself automatically becoming @Nullable
due to Lombok. Correct?
This might not be enough to track all the changes to fields and methods of the inner class Foo.Builder
or their impacted regions if the class is also annotated @Builder
. Right?
Happy if the answer is no and we can do that other case as a follow up PR, btw 🙂
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 looked at generated code by @Builder
and I did not see any generated code getting reused again. But I think as discussed in the follow up PR for more tests, we can also resolve this issue if it comes up 🙂
…atedcode/GeneratedCodeDetector.java Co-authored-by: Lázaro Clapp <lazaro@uber.com>
…ackers/generatedcode/LombokTracker.java Co-authored-by: Lázaro Clapp <lazaro@uber.com>
@lazaroclapp Thank you for the review. I resolved all your comments and this is ready for another round. Thank you 🙂. |
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.
Only minor comments this time. Approving assuming they will be addressed before landing
.../main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/GeneratedRegionTracker.java
Show resolved
Hide resolved
...-core/src/main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/LombokTracker.java
Outdated
Show resolved
Hide resolved
// add potentially impacted regions for the collected methods. | ||
.flatMap( | ||
onMethod -> { | ||
Optional<Set<Region>> ans = tracker.getRegions(onMethod); |
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.
Not related to this PR, but it seems strange that the getRegions()
method returns an Optional<Set<Region>>
rather than just a Set<Region>
that could be empty. Maybe fix in a follow up
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.
@msridhar I wanted to distinguish between trackers "not having any knowledge about the passed element" and "having data but not having any actual usage". But this was required in the old designs and should be updated in follow up PRs.
@Nullable | ||
public static Symbol locateRegionMemberForSymbolAtPath( | ||
TreePath path, Symbol.ClassSymbol enclosingClass) { | ||
// Class is not generated by lombok. Let's find the region member, can be a field, method or |
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.
Why is there a comment about lombok here? Feels somewhat unrelated to what this method is doing
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.
Sorry thanks for noticing this. This was copied from existing method. 3738766
public class LombokGeneratedCodeDetector implements GeneratedCodeDetector { | ||
|
||
/** Associated source type for Lombok in region serializations in <i>SOURCE_TYPE</i> column. */ | ||
private static final SourceType sourceType = SourceType.LOMBOK; |
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 don't think we need this constant, since it's only used once. Just return SourceType.LOMBOK
from the 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.
…ackers/generatedcode/LombokTracker.java Co-authored-by: Manu Sridharan <msridhar@gmail.com>
annotator-scanner/src/main/java/edu/ucr/cs/riple/scanner/ErrorProneCLIFlagsConfig.java
Outdated
Show resolved
Hide resolved
* impacted as well. | ||
* | ||
* @param regions Set of potentially impacted regions. | ||
* @return Set of potentially impacted regions including all knowing potentially impacted |
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.
* @return Set of potentially impacted regions including all knowing potentially impacted | |
* @return Set of potentially impacted regions including all known potentially impacted |
But also this is both hard to parse and - I think - inaccurate.
You are not extending the list exclusively with generated regions, but, rather whenever a region in the initial set (a region affected by a fix) is a generated region, we are also adding every region that would be impacted by a fix within the generated region, since the annotation processor might propagate changes from the original fix to this generated region independent of the annotator's own logic. Is this correct? Is there a clearer way to explain this? (cc: @msridhar )
…ProneCLIFlagsConfig.java Co-authored-by: Lázaro Clapp <lazaro@uber.com>
…ackers/generatedcode/GeneratedRegionTracker.java Co-authored-by: Lázaro Clapp <lazaro@uber.com>
.../main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/GeneratedRegionTracker.java
Outdated
Show resolved
Hide resolved
.../main/java/edu/ucr/cs/riple/core/metadata/trackers/generatedcode/GeneratedRegionTracker.java
Outdated
Show resolved
Hide resolved
…ackers/generatedcode/GeneratedRegionTracker.java Co-authored-by: Manu Sridharan <msridhar@gmail.com>
This PR is built on #116
This PR enhances logic for selecting the potentially impacted regions to include generated code by processors (e.g. Lombok).
Trackers
are used to detect potentially impacted regions for a fix. These trackers only include the regions where the element was used directly. But since some processors, will copy the annotations on the element over some generated methods, these trackers will miss the real impacted regions because of the copied annotation. This PR tries to address this issue.Changes in this PR:
GeneratedCodeDetector
interface inannotator-scanner
module which is responsible for determining if aTreePath
is pointing to a generated code.LombokGeneratedCodeDetector
which implementsGeneratedCodeDetector
and can detect if a path is pointing to aLombok
generated code. For this feature to work properly (lombok.addLombokGeneratedAnnotation = true
must be set)"SOURCE"
, otherwise, it will have the name of the processor that created it.GeneratedRegionTracker
inannotator-core
module which is responsible for extending the list of potentially impacted regions to include users of generated code that uses the target element.LombokTracker
which implementsGeneratedRegionTracker
and can extend regions with other regions that Lombok has created.To enable handling regions created by Lombok, the flag below must be passed: