A file whose name contains the literal text ${message} makes the SARIF logger emit malformed JSON. The name is not meant to be a variable; the substitution order lets it act as one. The same hazard exists on the message side: a violation message can carry placeholder text too.
Cause
The result template is filled by a chain of String.replace. On master the uri is substituted before the message:
.replace(URI_PLACEHOLDER, renderFileNameUri(event.getFileName())) ... .replace(MESSAGE_PLACEHOLDER, message)
renderFileNameUri returns the file name verbatim. When that name contains ${message}, the text lands in the uri value, and the following .replace(MESSAGE_PLACEHOLDER, message) rescans the whole accumulated string and splices the message object into the middle of the uri. The injected quotes close the string early, so the JSON is broken. That output is what CI uploads to code scanning.
Reordering alone is not enough: a message can also carry placeholder text, e.g. ${uri}, when a check echoes matched source text through a MessageFormat argument (arguments are not re-parsed, and escape leaves ${...} untouched). Whichever of uri/message is substituted last would then pull the other value into it.
Fix
Fill every placeholder in a single pass, so a value substituted for one placeholder is never rescanned and taken for another. A file name or message that happens to carry placeholder text is kept verbatim. addError (both branches) and addException use the single-pass fill. Regression tests cover a name containing ${message} and a message containing ${uri}.
Reproduction (CLI)
Config (config.xml):
<module name="Checker"> <module name="TreeWalker"> <module name="EmptyStatement"/> </module> </module>
A file named report${message}.java with one empty statement:
class Foo { void m() { ; } }
Command:
java -jar checkstyle.jar -c config.xml -f sarif 'report${message}.java'
On master the uri value is broken (message object spliced in, JSON no longer parses):
"uri": "file:.../report "id": "empty.statement",
"text": "Empty statement.".java"
On this branch the file name is preserved literally and the JSON is valid:
"uri": "file:.../report${message}.java"
Piping each output through python3 -m json.tool: master fails to parse, this branch parses.