CppEditor: Do not produce invalid code when escaping string literals

Fixes: QTCREATORBUG-26003
Change-Id: Ie4d0ae85cc0ae2d1d45ae0bedbf0212d217aa69b
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
Christian Kandeler
2021-07-28 11:26:06 +02:00
parent 1bf9900aed
commit b152f3ace8
2 changed files with 61 additions and 21 deletions

View File

@@ -253,6 +253,8 @@ QuickFixOperationTest::QuickFixOperationTest(const QList<QuickFixTestDocument::P
// Check // Check
QString result = testDocument->m_editorWidget->document()->toPlainText(); QString result = testDocument->m_editorWidget->document()->toPlainText();
removeTrailingWhitespace(result); removeTrailingWhitespace(result);
QEXPECT_FAIL("escape string literal: raw string literal", "FIXME", Continue);
QEXPECT_FAIL("escape string literal: unescape adjacent literals", "FIXME", Continue);
if (!expectedFailMessage.isEmpty()) if (!expectedFailMessage.isEmpty())
QEXPECT_FAIL("", expectedFailMessage.data(), Continue); QEXPECT_FAIL("", expectedFailMessage.data(), Continue);
else if (result != testDocument->m_expectedSource) { else if (result != testDocument->m_expectedSource) {
@@ -1750,6 +1752,26 @@ void CppEditorPlugin::test_quickfix_data()
<< CppQuickFixFactoryPtr(new ConvertToCamelCase(true)) << CppQuickFixFactoryPtr(new ConvertToCamelCase(true))
<< _("void @WhAt_TODO_hErE();\n") << _("void @WhAt_TODO_hErE();\n")
<< _("void WhAtTODOHErE();\n"); << _("void WhAtTODOHErE();\n");
QTest::newRow("escape string literal: simple case")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"àxyz";)")
<< _(R"(const char *str = "\xc3\xa0xyz";)");
QTest::newRow("escape string literal: simple case reverse")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"\xc3\xa0xyz";)")
<< _(R"(const char *str = "àxyz";)");
QTest::newRow("escape string literal: raw string literal")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"x(const char *str = @R"(àxyz)";)x")
<< _(R"x(const char *str = R"(\xc3\xa0xyz)";)x");
QTest::newRow("escape string literal: splitting required")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"àf23бgб1";)")
<< _(R"(const char *str = "\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)");
QTest::newRow("escape string literal: unescape adjacent literals")
<< CppQuickFixFactoryPtr(new EscapeStringLiteral)
<< _(R"(const char *str = @"\xc3\xa0""f23\xd0\xb1g\xd0\xb1""1";)")
<< _(R"(const char *str = "àf23бgб1";)");
} }
void CppEditorPlugin::test_quickfix() void CppEditorPlugin::test_quickfix()

View File

@@ -7079,17 +7079,25 @@ private:
return false; return false;
} }
static QByteArray escapeString(const QByteArray &contents) static QByteArrayList escapeString(const QByteArray &contents)
{ {
QByteArray newContents; QByteArrayList newContents;
QByteArray chunk;
bool wasEscaped = false;
for (const quint8 c : contents) { for (const quint8 c : contents) {
if (isascii(c) && isprint(c)) { const bool needsEscape = !isascii(c) || !isprint(c);
newContents += c; if (!needsEscape && wasEscaped && std::isxdigit(c) && !chunk.isEmpty()) {
} else { newContents << chunk;
newContents += QByteArray("\\x") + chunk.clear();
QByteArray::number(c, 16).rightJustified(2, '0');
} }
if (needsEscape)
chunk += QByteArray("\\x") + QByteArray::number(c, 16).rightJustified(2, '0');
else
chunk += c;
wasEscaped = needsEscape;
} }
if (!chunk.isEmpty())
newContents << chunk;
return newContents; return newContents;
} }
@@ -7154,25 +7162,35 @@ public:
QTC_ASSERT(stringLiteral, return); QTC_ASSERT(stringLiteral, return);
const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token). const QByteArray oldContents(currentFile->tokenAt(stringLiteral->literal_token).
identifier->chars()); identifier->chars());
QByteArray newContents; QByteArrayList newContents;
if (m_escape) if (m_escape)
newContents = escapeString(oldContents); newContents = escapeString(oldContents);
else else
newContents = unescapeString(oldContents); newContents = {unescapeString(oldContents)};
if (oldContents != newContents) { if (newContents.isEmpty()
// Check UTF-8 byte array is correct or not. || (newContents.size() == 1 && newContents.first() == oldContents)) {
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8"); return;
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
const QString str = decoder->toUnicode(newContents);
const QByteArray utf8buf = str.toUtf8();
if (utf8codec->canEncode(str) && newContents == utf8buf) {
ChangeSet changes;
changes.replace(startPos + 1, endPos - 1, str);
currentFile->setChangeSet(changes);
currentFile->apply();
}
} }
QTextCodec *utf8codec = QTextCodec::codecForName("UTF-8");
QScopedPointer<QTextDecoder> decoder(utf8codec->makeDecoder());
ChangeSet changes;
bool replace = true;
for (const QByteArray &chunk : qAsConst(newContents)) {
const QString str = decoder->toUnicode(chunk);
const QByteArray utf8buf = str.toUtf8();
if (!utf8codec->canEncode(str) || chunk != utf8buf)
return;
if (replace)
changes.replace(startPos + 1, endPos - 1, str);
else
changes.insert(endPos, "\"" + str + "\"");
replace = false;
}
currentFile->setChangeSet(changes);
currentFile->apply();
} }
private: private: