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

Introduce EagerStringFormatting check #1139

Merged
merged 2 commits into from
Jan 11, 2025

Conversation

Stephan202
Copy link
Member

@Stephan202 Stephan202 commented Apr 14, 2024

❗ This PR is on top of #1138. ❗

This is a draft PR; a bit more work (especially around tests) is required. I'm opening the PR already, as I'd like to reference it.

Suggested commit message:

Introduce `EagerStringFormatting` check (#1139)

This new check flags code that can be simplified and/or optimized by
deferring certain string formatting operations.

@Stephan202 Stephan202 added this to the 0.17.0 milestone Apr 14, 2024
@Stephan202 Stephan202 marked this pull request as draft April 14, 2024 17:41
Copy link

Copy link

  • Surviving mutants in this change: 20
  • Killed mutants in this change: 69
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 13 26
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 7 43

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 modified the milestones: 0.17.0, 0.18.0 Jul 20, 2024
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-redundant-string-escape-check branch from 1b69726 to 96c8208 Compare August 4, 2024 10:39
@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 0caa780 to 9675a51 Compare August 4, 2024 10:52
Copy link

sonarqubecloud bot commented Aug 4, 2024

Copy link

github-actions bot commented Aug 4, 2024

  • Surviving mutants in this change: 20
  • Killed mutants in this change: 69
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 13 26
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 7 43

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 modified the milestones: 0.18.0, 0.19.0 Aug 11, 2024
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-redundant-string-escape-check branch from 96c8208 to 8ee3ce8 Compare September 21, 2024 15:00
@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 9675a51 to 459da80 Compare September 21, 2024 15:19
Copy link

Copy link

  • Surviving mutants in this change: 20
  • Killed mutants in this change: 69
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 13 26
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 7 43

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@rickie rickie modified the milestones: 0.19.0, 0.20.0 Oct 23, 2024
@rickie rickie force-pushed the sschroevers/introduce-redundant-string-escape-check branch from 8ee3ce8 to 4ce0955 Compare November 12, 2024 10:11
@Stephan202 Stephan202 force-pushed the sschroevers/introduce-redundant-string-escape-check branch 2 times, most recently from 3e30dc5 to f200a92 Compare November 18, 2024 19:06
Base automatically changed from sschroevers/introduce-redundant-string-escape-check to master November 18, 2024 19:33
@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 459da80 to 49a376b Compare December 14, 2024 14:19
Copy link

  • Surviving mutants in this change: 20
  • Killed mutants in this change: 69
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 13 26
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 7 43

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 49a376b to edb6cc7 Compare December 27, 2024 13:01
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 93
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 55
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 38

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 96
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 58
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 38

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 992e5fb to 4bc7f8b Compare December 27, 2024 18:27
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 98
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 58
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 40

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

@Stephan202 Stephan202 force-pushed the sschroevers/eager-string-formatting-check branch from 4bc7f8b to caff523 Compare December 27, 2024 18:51
@Stephan202 Stephan202 marked this pull request as ready for review December 27, 2024 18:51
@Stephan202
Copy link
Member Author

@rickie @mohamedsamehsalah this one is finally ready for review. :)

Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 98
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 58
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 40

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Contributor

@mohamedsamehsalah mohamedsamehsalah left a comment

Choose a reason for hiding this comment

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

🧠 Saves a lot of review comments ❤️ ❤️

@Test
void identification() {
CompilationTestHelper.newInstance(EagerStringFormatting.class, getClass())
.expectErrorMessage("DEFER", m -> m.contains("String formatting can be deferred\n"))
Copy link
Contributor

Choose a reason for hiding this comment

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

is the \n character necessary? 👀

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, cause otherwise this is a strictly weaker predicate than the DEFER_* predicates below.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense 😄

import tech.picnic.errorprone.utils.SourceCode;

/**
* A {@link BugChecker} that flags {@link String#format(String, Object...)}, {@link
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe either just something along the lines of "String#format(String) and it's overloaded siblings", or format (heh) in a <ul> tag.

(No strong opinion, this is also readable 👍 )

Copy link
Member Author

Choose a reason for hiding this comment

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

I could do {@link String#format} and {@link String#formatted} :)

import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;

final class EagerStringFormattingTest {
Copy link
Contributor

@mohamedsamehsalah mohamedsamehsalah Dec 27, 2024

Choose a reason for hiding this comment

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

Shouldn't we also check for flag the case when the potential String#format invocations is stored and passed in a variable to the "parent" method?

Index: error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/EagerStringFormattingTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>ISO-8859-1
===================================================================
diff --git a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/EagerStringFormattingTest.java b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/EagerStringFormattingTest.java
--- a/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/EagerStringFormattingTest.java	(revision caff523200614c14f23100fe4957dbf3d020bb4a)
+++ b/error-prone-contrib/src/test/java/tech/picnic/errorprone/bugpatterns/EagerStringFormattingTest.java	(date 1735330311287)
@@ -65,6 +65,9 @@
             "",
             "    requireNonNull(\"never-null\");",
             "    requireNonNull(\"never-null\", () -> String.format(\"Format string: %s\", nonFinalField));",
+            "    // BUG: Diagnostic matches: DEFER_PARAM",
+            "    String loggedMessage = String.format(\"Never-null format string: %s\", nonFinalField)",
+            "    requireNonNull(loggedMessage);",
             "    // BUG: Diagnostic matches: VACUOUS",
             "    requireNonNull(String.format(\"Never-null format string: %s\", nonFinalField));",
             "    // BUG: Diagnostic matches: VACUOUS",

Copy link
Contributor

Choose a reason for hiding this comment

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

is stored and passed in a variable to the "parent" method?

Or even go far and check if it's returned from another method invocation 👀

Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, none of these cases are currently covered. For now I'll call out these limitations in a comment; the check/PR is already complex enough as it is.

Copy link
Member 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.

Thanks for taking such a quick look @mohamedsamehsalah! I added a commit.

import tech.picnic.errorprone.utils.SourceCode;

/**
* A {@link BugChecker} that flags {@link String#format(String, Object...)}, {@link
Copy link
Member Author

Choose a reason for hiding this comment

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

I could do {@link String#format} and {@link String#formatted} :)

@Test
void identification() {
CompilationTestHelper.newInstance(EagerStringFormatting.class, getClass())
.expectErrorMessage("DEFER", m -> m.contains("String formatting can be deferred\n"))
Copy link
Member Author

Choose a reason for hiding this comment

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

Yep, cause otherwise this is a strictly weaker predicate than the DEFER_* predicates below.

import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;

final class EagerStringFormattingTest {
Copy link
Member Author

Choose a reason for hiding this comment

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

Indeed, none of these cases are currently covered. For now I'll call out these limitations in a comment; the check/PR is already complex enough as it is.

Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 98
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 58
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 40

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Contributor

@mohamedsamehsalah mohamedsamehsalah left a comment

Choose a reason for hiding this comment

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

🧯

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

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

Look at those IT 😍 , so nice! Flushing my two comments I have so far. It's a big one to get through with all the cases, but it will be such a nice one to release this.

linkType = CUSTOM,
severity = WARNING,
tags = {PERFORMANCE, SIMPLIFICATION})
public final class EagerStringFormatting extends BugChecker implements MethodInvocationTreeMatcher {
Copy link
Member

Choose a reason for hiding this comment

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

Could you elaborate on why we use "Eager" in the name of the check. In somewhat similar cases we use "Redundant", curious why we don't do that here :).

Copy link
Member Author

Choose a reason for hiding this comment

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

Yep. Basically this check flags cases where string formatting happens unconditionally, rather than happening only if some other condition is met (precondition failure, log statement at enabled log level, ...). So the check flags "eager" formatting operations that could be "deferred" instead.

"",
" checkArgument(true, String.format(\"Vacuous format string %%\"));",
" checkNotNull(\"never-null\", \"Format string: %s %s%%\".formatted(1, 2));",
" checkState(false, String.format(Locale.US, \"Format string with locale: %s\", 3));",
Copy link
Member

Choose a reason for hiding this comment

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

I'm curious, if we drop the Locale, that could be non-behavior preserving, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

IIUC this should actually be fine, because we explicitly handle java.util.Formattable arguments by not suggesting a rewrite when those are present.

This new check flags code that can be simplified and/or optimized by
deferring certain string formatting operations.
@rickie rickie force-pushed the sschroevers/eager-string-formatting-check branch from 3af3cf2 to fa59f63 Compare January 11, 2025 13:24
Copy link

  • Surviving mutants in this change: 2
  • Killed mutants in this change: 98
class surviving killed
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting 1 58
🧟tech.picnic.errorprone.bugpatterns.EagerStringFormatting$StringFormatExpression 1 40

Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed.

Copy link
Member

@rickie rickie left a comment

Choose a reason for hiding this comment

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

Nice work @Stephan202 , really curious to see this on our codebase!

@rickie rickie merged commit a42353b into master Jan 11, 2025
16 checks passed
@rickie rickie deleted the sschroevers/eager-string-formatting-check branch January 11, 2025 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

Successfully merging this pull request may close these issues.

3 participants