-
Notifications
You must be signed in to change notification settings - Fork 8
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
Add @NullUnmarked on classes #134
Changes from 40 commits
d465eda
2e8e65f
46d84ac
1fba197
3e2e8b4
2bf6459
70bf1ef
6046ebf
f98fc55
fc41c67
c201245
da8c98a
64d8c57
47998c0
01c679a
8a517d0
8c2c3ae
6b49f66
fcd2fe6
ab8d201
5f25f1b
27a2535
0d630c6
b27c14a
79b11b1
2b13142
4f18e80
9b729e8
dcf81b4
0550bb5
15fbbd4
9798ca4
084e9e6
cafef18
139c19a
1f39e96
a811889
71b998b
4fb60f1
344007b
45374b8
3e1bb7c
3a43655
ca510cc
95429c8
4eb1bbe
74e44b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
import edu.ucr.cs.riple.core.metadata.MetaData; | ||
import edu.ucr.cs.riple.injector.Helper; | ||
import edu.ucr.cs.riple.injector.exceptions.TargetClassNotFound; | ||
import edu.ucr.cs.riple.injector.location.OnClass; | ||
import edu.ucr.cs.riple.injector.location.OnField; | ||
import edu.ucr.cs.riple.scanner.AnnotatorScanner; | ||
import java.io.FileNotFoundException; | ||
|
@@ -90,7 +91,10 @@ protected FieldDeclarationInfo addNodeByLine(String[] values) { | |
.map(NodeWithSimpleName::getNameAsString) | ||
.collect(ImmutableSet.toImmutableSet())); | ||
})); | ||
return info.isEmpty() ? null : info; | ||
// We still want to keep the information about the class even if it has no field declarations, | ||
// so we can retrieve tha path to the file from the given class flat name. This information is | ||
// used in adding suppression annotations on class level. | ||
return info; | ||
} catch (FileNotFoundException e) { | ||
return null; | ||
} catch (IOException e) { | ||
|
@@ -136,4 +140,22 @@ public OnField getLocationOnField(String clazz, String field) { | |
} | ||
return new OnField(candidate.pathToSourceFile, candidate.clazz, fieldNames); | ||
} | ||
|
||
/** | ||
* Creates a {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed | ||
* classes flat name. | ||
* | ||
* @param clazz Enclosing class of the field. | ||
* @return {@link edu.ucr.cs.riple.injector.location.OnClass} instance targeting the passed | ||
* classes flat name. | ||
*/ | ||
public OnClass getLocationOnClass(String clazz) { | ||
FieldDeclarationInfo candidate = | ||
findNodeWithHashHint(node -> node.clazz.equals(clazz), FieldDeclarationInfo.hash(clazz)); | ||
if (candidate == null) { | ||
// field is on byte code. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, this comment (and the name of the class), reference a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this class has the required information which its main usage is to store information regarding existing classes and their fields. I have a plan for refactoring these data structures that store information regarding the structure of a code and combine them into one data structure. But the plan is to do that after the release of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, that makes sense, but even given that, this comment right above is incorrect here, because this method is not dealing with any fields. Either remove or change the comment? |
||
return null; | ||
} | ||
return new OnClass(candidate.pathToSourceFile, candidate.clazz); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -92,8 +92,7 @@ public Node(Fix root) { | |
*/ | ||
public void setOrigins(ErrorStore errorStore) { | ||
this.origins = | ||
errorStore.getRegionsForElements( | ||
error -> error.isSingleFix() && error.getResolvingFixes().contains(root)); | ||
errorStore.getRegionsForElements(error -> error.getResolvingFixes().contains(root)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Question: what is this change about? I don't understand either what the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Origins are set of regions that triggered root fix. The condition There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,7 @@ | |
import edu.ucr.cs.riple.injector.changes.AddAnnotation; | ||
import edu.ucr.cs.riple.injector.changes.AddMarkerAnnotation; | ||
import edu.ucr.cs.riple.injector.changes.AddSingleElementAnnotation; | ||
import edu.ucr.cs.riple.injector.location.OnClass; | ||
import edu.ucr.cs.riple.injector.location.OnField; | ||
import edu.ucr.cs.riple.injector.location.OnMethod; | ||
import edu.ucr.cs.riple.injector.location.OnParameter; | ||
|
@@ -421,7 +422,7 @@ public void initializationErrorWithMultipleConstructors() { | |
} | ||
|
||
@Test | ||
public void staticBlockLocalVariableInitializationTest() { | ||
public void staticAndInstanceInitializerBlockTest() { | ||
coreTestHelper | ||
.addInputLines( | ||
"A.java", | ||
|
@@ -441,6 +442,9 @@ public void staticBlockLocalVariableInitializationTest() { | |
" }", | ||
"}", | ||
"class B {", | ||
" {", | ||
" foo().hashCode();", | ||
" }", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to test static initializer block support and instance initializer block in the same test? (if so, the test case might need renaming). Or is it perhaps better to have two separate test cases for ease of reading/debugging? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
" @Nullable", | ||
" public static Object foo() { return null; }", | ||
" @Nullable", | ||
|
@@ -450,5 +454,18 @@ public void staticBlockLocalVariableInitializationTest() { | |
.addExpectedReports(new TReport(new OnField("A.java", "test.A", singleton("f")), -3)) | ||
.enableForceResolve() | ||
.start(); | ||
List<AddAnnotation> expectedAnnotations = | ||
List.of( | ||
new AddMarkerAnnotation( | ||
new OnField( | ||
coreTestHelper.getSourceRoot().resolve("A.java").toString(), | ||
"test.A", | ||
Set.of("f")), | ||
"javax.annotation.Nullable"), | ||
new AddMarkerAnnotation( | ||
new OnClass(coreTestHelper.getSourceRoot().resolve("A.java").toString(), "test.B"), | ||
"org.jspecify.nullness.NullUnmarked")); | ||
Assert.assertEquals( | ||
expectedAnnotations, coreTestHelper.getConfig().log.getInjectedAnnotations()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,8 +40,8 @@ public void publicMethodWithDownstreamDependencyEnabled() { | |
coreTestHelper | ||
.addExpectedReports( | ||
// Change reduces errors on target by -4, but increases them in downstream dependency | ||
// DepA by 3, DepB by 4 and DepC by 2. Hence, the total effect is: 5. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 5), | ||
// DepA by 3, DepB by 4 and DepC by 3. Hence, the total effect is: 6. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 6), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What changed that changed the effects here and why is it part of this PR about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
// Change reduces errors on target by -5, but increases them in downstream dependency | ||
// DepA by 0, DepB by 1 and DepC by 0. Hence, the total effect is: -4. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), -4), | ||
|
@@ -78,13 +78,13 @@ public void publicMethodWithDownstreamDependencyDisabled() { | |
public void lowerBoundComputationTest() { | ||
coreTestHelper | ||
.addExpectedReports( | ||
// Only returnNullableBad triggers new errors in this fix chain (+9), lower bound is 9. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 9), | ||
// Only returnNullableBad triggers new errors in this fix chain (+9), lower bound is 10. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 10), | ||
// Only returnNullableGood triggers new errors in this fix chain (+1), lower bound is 1. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), 1), | ||
// Root fix triggers 1 error on downstream dependency but returnNullableBad is | ||
// present in the fix tree, therefore the lower bound effect for the tree should be 9. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 9)) | ||
// present in the fix tree, therefore the lower bound effect for the tree should be 10. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 10)) | ||
.setPredicate( | ||
(expected, found) -> | ||
expected.root.equals(found.root) | ||
|
@@ -100,16 +100,17 @@ public void lowerBoundComputationTest() { | |
public void upperBoundComputationTest() { | ||
coreTestHelper | ||
.addExpectedReports( | ||
// Only returnNullableBad triggers new errors in this fix chain (+9) and upper bound | ||
// should be 9 | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 9), | ||
// Only returnNullableBad triggers new errors in this fix chain (+10) and upper bound | ||
// should be 10 | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableBad(int)"), 10), | ||
// Only returnNullableGood triggers new errors in this fix chain (+1) and upper bound | ||
// should be 1 | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "returnNullableGood(int)"), 1), | ||
// Root fix triggers 1 error on downstream dependency and returnNullableBad is | ||
// present in the fix tree and triggers 9 errors on downstream dependency, therefore the | ||
// upper bound effect for the tree should be 10. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 10)) | ||
// present in the fix tree and triggers 10 errors on downstream dependency, therefore | ||
// the | ||
// upper bound effect for the tree should be 11. | ||
new TReport(new OnMethod("Foo.java", "test.target.Foo", "bar()"), 11)) | ||
.setPredicate( | ||
(expected, found) -> | ||
expected.root.equals(found.root) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
* MIT License | ||
* | ||
* Copyright (c) 2023 Nima Karimipour | ||
* | ||
* Permission is hereby granted, free of charge, to any person obtaining a copy | ||
* of this software and associated documentation files (the "Software"), to deal | ||
* in the Software without restriction, including without limitation the rights | ||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
* copies of the Software, and to permit persons to whom the Software is | ||
* furnished to do so, subject to the following conditions: | ||
* | ||
* The above copyright notice and this permission notice shall be included in | ||
* all copies or substantial portions of the Software. | ||
* | ||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
* THE SOFTWARE. | ||
*/ | ||
|
||
package edu.ucr.cs.riple.injector.location; | ||
|
||
import com.github.javaparser.ast.Node; | ||
import com.github.javaparser.ast.NodeList; | ||
import com.github.javaparser.ast.body.BodyDeclaration; | ||
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations; | ||
import edu.ucr.cs.riple.injector.Helper; | ||
import edu.ucr.cs.riple.injector.changes.Change; | ||
import edu.ucr.cs.riple.injector.modifications.Modification; | ||
import java.nio.file.Path; | ||
import java.util.Optional; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.regex.Pattern; | ||
import org.json.simple.JSONObject; | ||
|
||
public class OnClass extends Location { | ||
|
||
/** | ||
* Pattern to detect if a class flat name is for an anonymous class. Anonymous classes flat names | ||
* ends with a $ and one or more digits. | ||
*/ | ||
public static final Pattern anonymousClassPattern = Pattern.compile(".*\\$\\d+$"); | ||
|
||
public OnClass(Path path, String clazz) { | ||
super(LocationType.CLASS, path, clazz); | ||
} | ||
|
||
public OnClass(String path, String clazz) { | ||
this(Helper.deserializePath(path), clazz); | ||
} | ||
|
||
@Override | ||
protected Modification applyToMember(NodeList<BodyDeclaration<?>> declarations, Change change) { | ||
if (isAnonymousClassFlatName(change.location.clazz)) { | ||
return null; | ||
} | ||
final AtomicReference<Modification> ans = new AtomicReference<>(); | ||
Optional<Node> clazz = declarations.getParentNode(); | ||
clazz.ifPresent( | ||
node -> | ||
node.getRange() | ||
.ifPresent(range -> ans.set(change.visit((NodeWithAnnotations<?>) node, range)))); | ||
return ans.get(); | ||
} | ||
|
||
/** | ||
* Checks if flat name is for an anonymous class. | ||
* | ||
* @return true, if flat name is for an anonymous class. | ||
*/ | ||
public static boolean isAnonymousClassFlatName(String flatName) { | ||
return anonymousClassPattern.matcher(flatName).matches(); | ||
} | ||
|
||
@Override | ||
protected void fillJsonInformation(JSONObject res) { | ||
// no op | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return "OnClass{" + "type=" + type + ", clazz='" + clazz + '\'' + ", path=" + 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.
Not for this PR, but long term, in theory, this should try to find the containing method/initializer and add
@NullUnmarked
there, no? (For anonymous inner classes). It can get complicated with nested anonymous classes and the places they can appear in, so definitely not asking for that in this PR, just wondering if that's in the roadmap for1.3.6
or after1.3.6
.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. But I think the plan is to implement that in after
1.3.6
.