Nothing in the build catches a String.format call whose format string is not a literal, which is how #1244 survived: the message was built by concatenation and handed to String.format as its format string, so a % arriving in data changed the exception class.
PMD ships no rule for it — its only format-string rules are InvalidLogMessageFormat (logger call sites only), SimpleDateFormatNeedsLocale and UnsynchronizedStaticFormatter — and Error Prone, SpotBugs, Checkstyle and NullAway are not wired into the build at all, so PMD is the only lever. The ruleset already carries a hand-written XPath rule, AvoidAssertStatement, added for the same reason: a defect class the compiler and tests cannot see.
A sibling rule covers this one:
<rule name="AvoidSingleArgumentStringFormat" language="java"
message="String.format with a single argument formats nothing"
class="net.sourceforge.pmd.lang.rule.xpath.XPathRule">
<description>A one-argument String.format call is pointless or a latent crash.</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>//MethodCall[@MethodName = 'format'][ArgumentList[count(*) = 1][not(*[1][self::StringLiteral])]]</value>
</property>
</properties>
</rule>
Run against main across :isthmus:pmdMain, :isthmus:pmdTest, :core:pmdMain, :core:pmdTest and :isthmus-cli:pmdMain, it reports exactly one violation — TypeConverter.java:220, the #1244 site — so it becomes clean the moment #1249 lands, with no accompanying cleanup.
A broader variant dropping the StringLiteral exclusion also flags SubstraitRelNodeConverter.java:708, a no-argument String.format wrapping a plain literal. That arm is due to be rewritten by #1092, so the broad variant becomes enableable once that lands. Worth noting either way that the unqualified @MethodName = 'format' has no false positives only because the repo currently contains no non-String .format( call; a future DateTimeFormatter.format(x) would trip it, so qualifying the receiver is worth doing.
One trap for whoever verifies this: ruleSetConfig is set from resources.text.fromUri(...) and is not a tracked task input, so editing the ruleset does not invalidate cached PMD results. Without --rerun-tasks the PMD tasks come back FROM-CACHE and report a stale zero.
Measured on main at 944b921.
Nothing in the build catches a
String.formatcall whose format string is not a literal, which is how #1244 survived: the message was built by concatenation and handed toString.formatas its format string, so a%arriving in data changed the exception class.PMD ships no rule for it — its only format-string rules are
InvalidLogMessageFormat(logger call sites only),SimpleDateFormatNeedsLocaleandUnsynchronizedStaticFormatter— and Error Prone, SpotBugs, Checkstyle and NullAway are not wired into the build at all, so PMD is the only lever. The ruleset already carries a hand-written XPath rule,AvoidAssertStatement, added for the same reason: a defect class the compiler and tests cannot see.A sibling rule covers this one:
Run against
mainacross:isthmus:pmdMain,:isthmus:pmdTest,:core:pmdMain,:core:pmdTestand:isthmus-cli:pmdMain, it reports exactly one violation —TypeConverter.java:220, the #1244 site — so it becomes clean the moment #1249 lands, with no accompanying cleanup.A broader variant dropping the
StringLiteralexclusion also flagsSubstraitRelNodeConverter.java:708, a no-argumentString.formatwrapping a plain literal. That arm is due to be rewritten by #1092, so the broad variant becomes enableable once that lands. Worth noting either way that the unqualified@MethodName = 'format'has no false positives only because the repo currently contains no non-String.format(call; a futureDateTimeFormatter.format(x)would trip it, so qualifying the receiver is worth doing.One trap for whoever verifies this:
ruleSetConfigis set fromresources.text.fromUri(...)and is not a tracked task input, so editing the ruleset does not invalidate cached PMD results. Without--rerun-tasksthe PMD tasks come backFROM-CACHEand report a stale zero.Measured on
mainat 944b921.