forked from qt-creator/qt-creator
ANSI: Crash in AnsiEscapeCodeHandler::parseText
Qt creator crashes when capturing output from a program that can output unfinished control sequence. For example: "\x1b". Task-number: QTCREATORBUG-14720 Change-Id: I7535e509a192685aece63aea79234d88153fcb56 Reviewed-by: Tobias Hunger <tobias.hunger@theqtcompany.com> Reviewed-by: André Hartmann <aha_1980@gmx.de> Reviewed-by: wonder.mice <wonder.mice@gmail.com>
This commit is contained in:
committed by
wonder.mice
parent
16ab843f95
commit
9f29762d34
@@ -86,53 +86,83 @@ QList<FormattedText> AnsiEscapeCodeHandler::parseText(const FormattedText &input
|
||||
DefaultBackgroundColor = 49
|
||||
};
|
||||
|
||||
QList<FormattedText> outputData;
|
||||
|
||||
QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat;
|
||||
|
||||
const QString escape = QLatin1String("\x1b[");
|
||||
const int escapePos = input.text.indexOf(escape);
|
||||
if (escapePos < 0) {
|
||||
outputData << FormattedText(input.text, charFormat);
|
||||
return outputData;
|
||||
} else if (escapePos != 0) {
|
||||
outputData << FormattedText(input.text.left(escapePos), charFormat);
|
||||
}
|
||||
|
||||
const QChar semicolon = QLatin1Char(';');
|
||||
const QChar colorTerminator = QLatin1Char('m');
|
||||
const QChar eraseToEol = QLatin1Char('K');
|
||||
// strippedText always starts with "\e["
|
||||
QString strippedText = input.text.mid(escapePos);
|
||||
|
||||
QList<FormattedText> outputData;
|
||||
QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat;
|
||||
QString strippedText;
|
||||
if (m_pendingText.isEmpty()) {
|
||||
strippedText = input.text;
|
||||
} else {
|
||||
strippedText = m_pendingText.append(input.text);
|
||||
m_pendingText.clear();
|
||||
}
|
||||
|
||||
while (!strippedText.isEmpty()) {
|
||||
while (strippedText.startsWith(escape)) {
|
||||
strippedText.remove(0, 2);
|
||||
QTC_ASSERT(m_pendingText.isEmpty(), break);
|
||||
const int escapePos = strippedText.indexOf(escape[0]);
|
||||
if (escapePos < 0) {
|
||||
outputData << FormattedText(strippedText, charFormat);
|
||||
break;
|
||||
} else if (escapePos != 0) {
|
||||
outputData << FormattedText(strippedText.left(escapePos), charFormat);
|
||||
strippedText.remove(0, escapePos);
|
||||
}
|
||||
QTC_ASSERT(strippedText[0] == escape[0], break);
|
||||
|
||||
while (!strippedText.isEmpty() && escape[0] == strippedText[0]) {
|
||||
if (escape.startsWith(strippedText)) {
|
||||
// control secquence is not complete
|
||||
m_pendingText += strippedText;
|
||||
strippedText.clear();
|
||||
break;
|
||||
}
|
||||
if (!strippedText.startsWith(escape)) {
|
||||
// not a control sequence
|
||||
m_pendingText.clear();
|
||||
outputData << FormattedText(strippedText.left(1), charFormat);
|
||||
strippedText.remove(0, 1);
|
||||
continue;
|
||||
}
|
||||
m_pendingText += strippedText.mid(0, escape.length());
|
||||
strippedText.remove(0, escape.length());
|
||||
|
||||
// \e[K is not supported. Just strip it.
|
||||
if (strippedText.startsWith(eraseToEol)) {
|
||||
m_pendingText.clear();
|
||||
strippedText.remove(0, 1);
|
||||
continue;
|
||||
}
|
||||
// get the number
|
||||
QString strNumber;
|
||||
QStringList numbers;
|
||||
while (strippedText.at(0).isDigit() || strippedText.at(0) == semicolon) {
|
||||
while (!strippedText.isEmpty()) {
|
||||
if (strippedText.at(0).isDigit()) {
|
||||
strNumber += strippedText.at(0);
|
||||
} else {
|
||||
numbers << strNumber;
|
||||
strNumber.clear();
|
||||
}
|
||||
strippedText.remove(0, 1);
|
||||
}
|
||||
if (!strNumber.isEmpty())
|
||||
numbers << strNumber;
|
||||
if (strNumber.isEmpty() || strippedText.at(0) != semicolon)
|
||||
break;
|
||||
strNumber.clear();
|
||||
}
|
||||
m_pendingText += strippedText.mid(0, 1);
|
||||
strippedText.remove(0, 1);
|
||||
}
|
||||
if (strippedText.isEmpty())
|
||||
break;
|
||||
|
||||
// remove terminating char
|
||||
if (!strippedText.startsWith(colorTerminator)) {
|
||||
m_pendingText.clear();
|
||||
strippedText.remove(0, 1);
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
// got consistent control sequence, ok to clear pending text
|
||||
m_pendingText.clear();
|
||||
strippedText.remove(0, 1);
|
||||
|
||||
if (numbers.isEmpty()) {
|
||||
@@ -224,17 +254,6 @@ QList<FormattedText> AnsiEscapeCodeHandler::parseText(const FormattedText &input
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strippedText.isEmpty())
|
||||
break;
|
||||
int index = strippedText.indexOf(escape);
|
||||
if (index > 0) {
|
||||
outputData << FormattedText(strippedText.left(index), charFormat);
|
||||
strippedText.remove(0, index);
|
||||
} else if (index == -1) {
|
||||
outputData << FormattedText(strippedText, charFormat);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return outputData;
|
||||
}
|
||||
|
||||
@@ -62,6 +62,7 @@ private:
|
||||
|
||||
bool m_previousFormatClosed;
|
||||
QTextCharFormat m_previousFormat;
|
||||
QString m_pendingText;
|
||||
};
|
||||
|
||||
} // namespace Utils
|
||||
|
||||
@@ -57,6 +57,7 @@ private Q_SLOTS:
|
||||
void testSimpleFormat();
|
||||
void testSimpleFormat_data();
|
||||
void testLineOverlappingFormat();
|
||||
void testSplitControlSequence();
|
||||
|
||||
private:
|
||||
const QString red;
|
||||
@@ -225,6 +226,31 @@ void tst_AnsiEscapeCodeHandler::testSimpleFormat_data()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("All text after this is ", defaultFormat)
|
||||
<< FormattedText("not deleted", defaultFormat));
|
||||
|
||||
QTest::newRow("Unfinished control sequence \\x1b")
|
||||
<< QString("A text before \x1b") << QTextCharFormat()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("A text before ", defaultFormat));
|
||||
|
||||
QTest::newRow("Unfinished control sequence \\x1b[")
|
||||
<< QString("A text before \x1b[") << QTextCharFormat()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("A text before ", defaultFormat));
|
||||
|
||||
QTest::newRow("Unfinished control sequence \\x1b[3")
|
||||
<< QString("A text before \x1b[3") << QTextCharFormat()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("A text before ", defaultFormat));
|
||||
|
||||
QTest::newRow("Unfinished control sequence \\x1b[31")
|
||||
<< QString("A text before \x1b[31") << QTextCharFormat()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("A text before ", defaultFormat));
|
||||
|
||||
QTest::newRow("Unfinished control sequence \\x1b[31,")
|
||||
<< QString("A text before \x1b[31,") << QTextCharFormat()
|
||||
<< (FormattedTextList()
|
||||
<< FormattedText("A text before ", defaultFormat));
|
||||
}
|
||||
|
||||
void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat()
|
||||
@@ -254,6 +280,29 @@ void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat()
|
||||
QCOMPARE(result[3].format, defaultFormat);
|
||||
}
|
||||
|
||||
void tst_AnsiEscapeCodeHandler::testSplitControlSequence()
|
||||
{
|
||||
// Test line-overlapping formats
|
||||
const QString line1 = "Normal line \x1b";
|
||||
const QString line2 = "[1m bold line";
|
||||
|
||||
QTextCharFormat defaultFormat;
|
||||
|
||||
AnsiEscapeCodeHandler handler;
|
||||
FormattedTextList result;
|
||||
result.append(handler.parseText(FormattedText(line1, defaultFormat)));
|
||||
result.append(handler.parseText(FormattedText(line2, defaultFormat)));
|
||||
|
||||
QTextCharFormat boldFormat;
|
||||
boldFormat.setFontWeight(QFont::Bold);
|
||||
|
||||
QCOMPARE(result.size(), 2);
|
||||
QCOMPARE(result[0].text, QLatin1String("Normal line "));
|
||||
QCOMPARE(result[0].format, defaultFormat);
|
||||
QCOMPARE(result[1].text, QLatin1String(" bold line"));
|
||||
QCOMPARE(result[1].format, boldFormat);
|
||||
}
|
||||
|
||||
QTEST_APPLESS_MAIN(tst_AnsiEscapeCodeHandler)
|
||||
|
||||
#include "tst_ansiescapecodehandler.moc"
|
||||
|
||||
Reference in New Issue
Block a user