Skip to content
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

Propagate check flags in patch mode #4699

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

Stephan202
Copy link
Contributor

No description provided.

Copy link
Contributor Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found this issue while working on PicnicSupermarket/error-prone-support#1426, where I noticed that a custom flag -XepOpt:Slf4jLogDeclaration:CanonicalStaticLoggerName=LOGGER was respected during regular compilation, but not when in patch mode.

Comment on lines 83 to 89
ErrorPronePlugins.loadPlugins(scannerSupplier, context);
ErrorPronePlugins.loadPlugins(scannerSupplier, context)
.applyOverrides(epOptions);
ImmutableSet<String> namedCheckers =
epOptions.patchingOptions().namedCheckers();
if (!namedCheckers.isEmpty()) {
toUse = toUse.filter(bci -> namedCheckers.contains(bci.canonicalName()));
} else {
toUse = toUse.applyOverrides(epOptions);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Besides that all existing tests still pass: this change seems reasonable, at least on a conceptual level. I didn't thoroughly assess whether the non-flag changes applied by applyOverrides are no-ops, though. Could have a closer look at that later.

Comment on lines 464 to 544
assertThat(Files.readString(location))
.isEqualTo(
"""
class StringConstantWrapper {
String s = "new-value";
}
""");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the fix above, this test fails with:

[ERROR] Failures: 
[ERROR]   ErrorProneJavaCompilerTest.patchWithBugPatternCustomization:465 value of:
    readString(...)
expected:
    class StringConstantWrapper {
      String s = "new-value";
    }
    
but was:
    class StringConstantWrapper {
      String s = "flag-not-set";
    }

@Stephan202
Copy link
Contributor Author

An issue that's likely fixed by this change was just reported against NullAway: uber/NullAway#1080. CC @cushon; might be worthy of a new release.

@Stephan202
Copy link
Contributor Author

Effectively this change reverts #4028, though the unit tests added there still pass. So I suppose that "something else" changed that made things compatible 👀

@Stephan202
Copy link
Contributor Author

Tested locally: this change does reintroduce the issue described by #3908 and fixed by #4028, so a more subtle fix is required. Unfortunately I have very limited availability in the coming days. Will cycle back to to this.

@msridhar
Copy link
Contributor

msridhar commented Dec 4, 2024

Would it be better for Error Prone to detect the case where a disabled check is passed to -XepPatchChecks and throw an understandable exception for that case? It seems that with #4028 such checks are just ignored, but that could lead to some unexpected behaviors. Not sure this idea makes sense or helps with finding a simpler overall fix.

@Stephan202
Copy link
Contributor Author

Stephan202 commented Dec 5, 2024

A better error message may indeed be acceptable in some cases, though both the reporter of #3908 and the developer of #4028 (back then a working student at Picnic) aim to see patching of disabled checks skipped for automation purposes. So hopefully we can find a way to have our cake and eat it too.

(Managing expectations: I'm currently moving apartments, so won't have time to take a closer look in the coming days. Though it's a bit surprising that it took nearly nine months for this issue to be surfaced, one could argue that not fixing this issue is worse than re-introducing the other bug (as this impacts at least one use case for "normal" users: NullAway), and that thus a release with a revert of #4028 (possibly including a revert of its tests, that apparently no longer guard against regression) is the right thing to do for now.)

@Stephan202 Stephan202 force-pushed the sschroevers/unconditionally-apply-overrides branch 2 times, most recently from c54e413 to 574cea1 Compare December 30, 2024 16:41
@Stephan202 Stephan202 force-pushed the sschroevers/unconditionally-apply-overrides branch from 574cea1 to 205c5a7 Compare December 30, 2024 17:00
Copy link
Contributor Author

@Stephan202 Stephan202 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rebased the PR and added a second commit. I think this PR is now ready for review. @cushon if deemed acceptable, it'd be nice to have a new release with this change. 🙏

Happy holidays!

Comment on lines -109 to -142
@Test
public void dontRunPatchForDisabledChecks() {
compilationHelper
.addSourceLines(
"Test.java",
"""
import com.google.errorprone.scanner.ScannerTest.Foo;

class Test {
Foo foo;
}
""")
.setArgs("-XepPatchLocation:IN_PLACE", "-XepPatchChecks:", "-Xep:ShouldNotUseFoo:OFF")
.doTest();
}

@Test
public void dontRunPatchUnlessRequested() {
compilationHelper
.addSourceLines(
"Test.java",
"""
import com.google.errorprone.scanner.ScannerTest.Foo;

class Test {
Foo foo;
}
""")
.setArgs(
"-XepPatchLocation:IN_PLACE",
"-Xep:ShouldNotUseFoo:WARN",
"-XepPatchChecks:DeadException")
.doTest();
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dropped these tests, introduced in #4028, as they do not actually guard against a regression of #3908. The new tests defined above do.

Comment on lines +489 to +516
@Test
public void patchSingleWithCheckDisabled() throws IOException {
JavaFileObject fileObject =
createOnDiskFileObject(
"StringConstantWrapper.java",
"""
class StringConstantWrapper {
String s = "old-value";
}
""");

CompilationResult result =
doCompile(
Collections.singleton(fileObject),
Arrays.asList(
"-XepPatchChecks:AssignmentUpdater",
"-XepPatchLocation:IN_PLACE",
"-Xep:AssignmentUpdater:OFF"),
Collections.singletonList(AssignmentUpdater.class));
assertThat(result.succeeded).isTrue();
assertThat(Files.readString(Path.of(fileObject.toUri())))
.isEqualTo(
"""
class StringConstantWrapper {
String s = "old-value";
}
""");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test covers against regression of #3908: without the latest commit, it fails.

❗ That said, my changes impact semantics ❗. Before the changes in this PR, Error Prone would ignore the -Xep:AssignmentUpdater:OFF flag and apply any suggestions by AssignmentUpdater. After my changes, AssignmentUpdater is ignored altogether.

I can see that both the old and new behavior are desirable in different contexts. However, any user that desires the previous behavior during patching can append -XepPatchChecks:AssignmentUpdater -Xep:AssignmentUpdater:ERROR instead of just -XepPatchChecks:AssignmentUpdater (because "last flag wins"). So the new semantics make it easier to write a script/integration that allows one to patch for some check C across a large set of projects, conditionally ignoring any projects that explicitly disable C.

Comment on lines +518 to +545
@Test
public void patchSingleWithBugPatternCustomization() throws IOException {
JavaFileObject fileObject =
createOnDiskFileObject(
"StringConstantWrapper.java",
"""
class StringConstantWrapper {
String s = "old-value";
}
""");

CompilationResult result =
doCompile(
Collections.singleton(fileObject),
Arrays.asList(
"-XepPatchChecks:AssignmentUpdater",
"-XepPatchLocation:IN_PLACE",
"-XepOpt:AssignmentUpdater:NewValue=new-value"),
Collections.singletonList(AssignmentUpdater.class));
assertThat(result.succeeded).isTrue();
assertThat(Files.readString(Path.of(fileObject.toUri())))
.isEqualTo(
"""
class StringConstantWrapper {
String s = "new-value";
}
""");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This new test covers the issue for which I opened the PR (and described by uber/NullAway#1080).

Comment on lines +436 to +487
@Test
public void patchAll() throws IOException {
JavaFileObject fileObject =
createOnDiskFileObject(
"StringConstantWrapper.java",
"""
class StringConstantWrapper {
String s = "old-value";
}
""");

CompilationResult result =
doCompile(
Collections.singleton(fileObject),
Arrays.asList("-XepPatchChecks:", "-XepPatchLocation:IN_PLACE"),
Collections.singletonList(AssignmentUpdater.class));
assertThat(result.succeeded).isTrue();
assertThat(Files.readString(Path.of(fileObject.toUri())))
.isEqualTo(
"""
class StringConstantWrapper {
String s = "flag-not-set";
}
""");
}

@Test
public void patchAllWithCheckDisabled() throws IOException {
JavaFileObject fileObject =
createOnDiskFileObject(
"StringConstantWrapper.java",
"""
class StringConstantWrapper {
String s = "old-value";
}
""");

CompilationResult result =
doCompile(
Collections.singleton(fileObject),
Arrays.asList(
"-XepPatchChecks:", "-XepPatchLocation:IN_PLACE", "-Xep:AssignmentUpdater:OFF"),
Collections.singletonList(AssignmentUpdater.class));
assertThat(result.succeeded).isTrue();
assertThat(Files.readString(Path.of(fileObject.toUri())))
.isEqualTo(
"""
class StringConstantWrapper {
String s = "old-value";
}
""");
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two tests describe the "common case": they pass both before and after the fix.

Comment on lines 80 to 96
.or(
Suppliers.memoize(
() -> {
ScannerSupplier toUse =
ErrorPronePlugins.loadPlugins(scannerSupplier, context);
ImmutableSet<String> namedCheckers =
epOptions.patchingOptions().namedCheckers();
if (!namedCheckers.isEmpty()) {
toUse = toUse.filter(bci -> namedCheckers.contains(bci.canonicalName()));
} else {
toUse = toUse.applyOverrides(epOptions);
}
ScannerSupplier toUse =
ErrorPronePlugins.loadPlugins(scannerSupplier, context)
.applyOverrides(epOptions)
.filter(
bci -> {
String name = bci.canonicalName();
return epOptions.getSeverityMap().get(name) != Severity.OFF
&& (namedCheckers.isEmpty()
|| namedCheckers.contains(name));
});
return ErrorProneScannerTransformer.create(toUse.get());
}));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So patching now ignores checks:

  • Whose severity is OFF, or
  • That are not explicitly enabled for patching, while other checks are.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants