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

Fix missing format strings in calls to DruidException.build #15056

Merged
merged 3 commits into from
Sep 30, 2023

Conversation

pranavbhole
Copy link
Contributor

@pranavbhole pranavbhole commented Sep 29, 2023

Fixes #15055

Description

PR fix exception eating duck and helps to surface the original exception,

String.format(Locale.ENGLISH, null, 1); or String.format(Locale.ENGLISH, null);

throws NPE and I safeguarding the nonStrictFormat function with null check.

Exception in thread "main" java.lang.NullPointerException
	at java.util.regex.Matcher.getTextLength(Matcher.java:1283)
	at java.util.regex.Matcher.reset(Matcher.java:309)
	at java.util.regex.Matcher.<init>(Matcher.java:229)
	at java.util.regex.Pattern.matcher(Pattern.java:1093)
	at java.util.Formatter.parse(Formatter.java:2547)
	at java.util.Formatter.format(Formatter.java:2501)
	at java.util.Formatter.format(Formatter.java:2455)
	at java.lang.String.format(String.java:2981)

Fixed the bug ...

Renamed the class ...

Added a forbidden-apis entry ...

This PR has:

  • been self-reviewed.
  • added documentation for new or modified features or behaviors.
  • a release note entry in the PR description.
  • added Javadocs for most classes and all non-trivial methods. Linked related entities via Javadoc links.
  • added or updated version, license, or notice information in licenses.yaml
  • added comments explaining the "why" and the intent of the code wherever would not be obvious for an unfamiliar reader.
  • added unit tests or modified existing tests to cover new code paths, ensuring the threshold for code coverage is met.
  • added integration tests.
  • been tested in a test Druid cluster.

@pranavbhole pranavbhole marked this pull request as ready for review September 29, 2023 03:46
@@ -373,16 +373,24 @@ public static String nonStrictFormat(String message, Object... formatArgs)
if (formatArgs == null || formatArgs.length == 0) {
return message;
}
if (message == null) {
Copy link
Contributor

@kfaraz kfaraz Sep 29, 2023

Choose a reason for hiding this comment

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

Instead of these changes, it would be simpler to just do a Preconditions.checkNotNull(message) at the start of this method.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Preconditions check will throw an exception again. Some of the exceptions are not having message at all and we are calling nonStrictFormat while making DruidException, esp exception from third party library. As the name says, expectation from nonStrictFormat in this case is that it should just give back some non null string. Best case effort is to append the args to string and give it back.

If we add precondition then we need to safeguard the invoker lines with not null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Based on comments, I found the instances where we could potentially pass null as formatString. I fixed all of them I could find in this PR. I am still doubtful about adding precondition check to nonStrictFormat as it is used at tons of places. I removed the change here and fixing the invokers.

@gianm
Copy link
Contributor

gianm commented Sep 29, 2023

Anything that is calling StringUtils.nonStrictFormat with a null format string is a bug. In the case of #15055, the bug is that QueryExceptionCompat#makeException calls druidException.build(exception, exception.getMessage()). The build method expects a format string in the second parameter. The call should instead be .build(exception, "%s", exception.getMessage()).

IMO, better to fix the above bug than to work around it in StringUtils#nonStrictFormat.

Btw, looking at other calls to DruidException#build I see a bunch of places that have the same problem, where they pass a regular string instead of a format string. Many of them should be changed from build(e, s) to build(e, "%s", s). We should fix these too. Even if s is not null, it is problematic if it contains % characters that will be interpreted in special ways by the formatter.

Copy link
Member

@kgyrtkirk kgyrtkirk left a comment

Choose a reason for hiding this comment

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

not sure how null could have been passed as message ; but maybe it would be good to mark that arg as @NonNull in case something could process and uncover its source in the future

or possibly enable:
https://errorprone.info/api/latest/com/google/errorprone/annotations/FormatString.html
so that -DstrictCompile could possibly catch formatting issues
not sure if it that would have worked in this case...

@github-actions github-actions bot added Area - Batch Ingestion Area - Querying Area - MSQ For multi stage queries - https://github.com/apache/druid/issues/12262 labels Sep 29, 2023
@pranavbhole
Copy link
Contributor Author

Anything that is calling StringUtils.nonStrictFormat with a null format string is a bug. In the case of #15055, the bug is that QueryExceptionCompat#makeException calls druidException.build(exception, exception.getMessage()). The build method expects a format string in the second parameter. The call should instead be .build(exception, "%s", exception.getMessage()).

IMO, better to fix the above bug than to work around it in StringUtils#nonStrictFormat.

Btw, looking at other calls to DruidException#build I see a bunch of places that have the same problem, where they pass a regular string instead of a format string. Many of them should be changed from build(e, s) to build(e, "%s", s). We should fix these too. Even if s is not null, it is problematic if it contains % characters that will be interpreted in special ways by the formatter.

Thank you, I was able to find the potential null at multiple places, fixed all of them I could find. Added test to StringUtilsTest to ensure formatting with string containing % char.

@kgyrtkirk
Copy link
Member

I've played around a bit with adding @FormatMethod/@FormatString annotations; it uncovers a few more real and theoretical issues - but it will take some time to address all of them in the whole project :)

https://github.com/kgyrtkirk/druid/tree/formatmethod-formatstring

@pranavbhole
Copy link
Contributor Author

I've played around a bit with adding @FormatMethod/@FormatString annotations; it uncovers a few more real and theoretical issues - but it will take some time to address all of them in the whole project :)

https://github.com/kgyrtkirk/druid/tree/formatmethod-formatstring

great, it would be helpful to open another followup PR after this with annotation.

@pranavbhole
Copy link
Contributor Author

@gianm @kfaraz
Addressed comments, please review again.
Thank you

Copy link
Contributor

@somu-imply somu-imply left a comment

Choose a reason for hiding this comment

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

Nice find ! LGTM

@gianm
Copy link
Contributor

gianm commented Sep 29, 2023

I've played around a bit with adding @FormatMethod/@FormatString annotations; it uncovers a few more real and theoretical issues - but it will take some time to address all of them in the whole project :)

Neat, nice idea!

Copy link
Contributor

@gianm gianm left a comment

Choose a reason for hiding this comment

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

Changes LGTM, although the title of the PR is no longer correct. Please update it to something like "Fix missing format strings in calls to DruidException.build"

@pranavbhole pranavbhole changed the title Fix the NPE bug in nonStrictFormat Fix missing format strings in calls to DruidException.build Sep 29, 2023
@@ -386,4 +386,19 @@ public void testJavaStringCompare()
}
}
}

@Test(expected = NullPointerException.class)
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe use Assert.assertThrows instead of expected exceptions

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

@gianm gianm merged commit 07c28f1 into apache:master Sep 30, 2023
74 checks passed
@pranavbhole pranavbhole deleted the nonStrictFormatFix branch September 30, 2023 00:11
@LakshSingla LakshSingla added this to the 28.0 milestone Oct 12, 2023
ektravel pushed a commit to ektravel/druid that referenced this pull request Oct 16, 2023
…5056)

* Fix the NPE bug in nonStrictFormat

* using non null format string

* using Assert.assertThrows
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area - Batch Ingestion Area - MSQ For multi stage queries - https://github.com/apache/druid/issues/12262 Area - Querying
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Exception Eating duck in query failures flow
7 participants