Skip to content

Fix #15107: Exporting XML via GUI escapes special characters twice. - #8943

Open
ludviggunne wants to merge 2 commits into
cppcheck-opensource:mainfrom
ludviggunne:15107-gui-escape
Open

ludviggunne wants to merge 2 commits into
cppcheck-opensource:mainfrom
ludviggunne:15107-gui-escape

Conversation

@ludviggunne

Copy link
Copy Markdown
Collaborator

No description provided.

@dmcppcheck dmcppcheck left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

The writer no longer escapes twice, but the reader still unescapes twice. XmlReportV2::writeError() now relies on QXmlStreamWriter's escaping alone. XmlReportV2::readError() still runs XmlReport::unquoteMessage() on msg, verbose, file, file0 and info after QXmlStreamReader has already decoded them. Any text that contains a literal entity such as <, &, " or ' therefore changes on a save/load round trip. Before this PR the round trip kept the text intact, because the extra quoting and unquoting cancelled out.

Cppcheck produces such messages itself. if (strcmp(s, "&lt;") == 0 && strcmp(s, "&lt;") == 0) gives Condition 'strcmp(s,"&lt;")==0' is always true. After saving this from the GUI (or after it is written to the project's lastResults.xml) and loading it again, the message reads strcmp(s,"<"). ErrorItem::same() compares message and errorPath when there is no hash, so these items can also lose their sinceDate and tags in updateFromOldReport.

To keep reading old double-escaped files, the unquoting should depend on the file, for example on a version or marker in the header that this PR's writer emits. It should not run on every file. The new test does not catch the problem because its strings contain no literal entity. The XML that the CLI writes was already decoded twice by the GUI before this PR; this PR adds the same problem for files the GUI writes itself.

Testing

  • Qt is not installed in the container, so I could not build the GUI or run gui/test/xmlreportv2. The problem is found by reading the code, but it is concrete.
  • Built cppcheck and testrunner in pr/ with make -j8; ./testrunner TestErrorLogger passes. The PR does not change the core library or the CLI.
  • Showed that cppcheck emits messages with literal entities:
// scratch/a.c
#include <string.h>
void f(const char *s) {
    if (strcmp(s, "&lt;") == 0 && strcmp(s, "&lt;") == 0) {}
}
$ pr/cppcheck --enable=all --xml a.c
<error id="knownConditionTrueFalse" ... msg="Condition &apos;strcmp(s,&quot;&amp;lt;&quot;)==0&apos; is always true" ...>
    <location file="a.c" line="3" column="53" info="Condition &apos;strcmp(s,&quot;&amp;lt;&quot;)==0&apos; is always true"/>
  • Round trip in the GUI, traced in the code. The message is Condition 'strcmp(s,"&lt;")==0' is always true.
    • With the PR, the writer stores &amp;lt;. QXmlStreamReader decodes that to &lt;, and unquoteMessage then turns it into <, so the reloaded message has strcmp(s,"<").
    • On base, the writer stored &amp;amp;lt;. Reading decoded it to &amp;lt; and unquoting gave &lt;, so the message came back correctly.
  • A version of the new test that would catch this: set item.summary = "x &lt; y", then check QCOMPARE(errors[0].summary, item.summary) after reading.

Automated review of 54b2dde by Claude. The pull request was built and tested locally.

Comment thread gui/xmlreportv2.cpp
mXmlWriter->writeAttribute(MsgAttribute, summary);
const QString message = XmlReport::quoteMessage(error.message);
mXmlWriter->writeAttribute(VerboseAttribute, message);
mXmlWriter->writeAttribute(MsgAttribute, error.summary);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

Now that the attribute is escaped only once, the reader at line 230 still calls XmlReport::unquoteMessage() on the value that QXmlStreamReader has already decoded. Literal entities in a message are lost on save/load. For example, cppcheck reports Condition 'strcmp(s,"&lt;")==0' is always true; it is written as &amp;lt;, read back as &lt; and then unquoted to <. Before this PR the round trip was correct. The same applies to file, file0 and info (lines 259/261/266). Fallback matching in ErrorItem::same() uses message and errorPath, so updateFromOldReport() on lastResults.xml can also stop matching these items. Old double-escaped files should be detected, for example from a marker or version in the header, and only those should be unquoted.

Comment thread gui/xmlreport.h
* @brief Unquote the message.
* @param message Message to quote.
* @return quoted message.
* For older reports where special characters have been escaped twice (#15107).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

This comment says the function is for older reports, but readError() calls it on every file. That includes files written after this PR and the XML that the CLI writes, which are escaped only once.

item.errorId = "unusedVariable";
item.severity = Severity::style;
item.summary = "Unused variable: \"x\" & <y>";
item.message = item.summary;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI review

The round-trip check passes only because the string has no literal entity. With e.g. item.summary = "x &lt; y", the QCOMPARE(errors[0].summary, item.summary) below would fail, because unquoteMessage turns the text that was read into x < y.

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