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:
Andrey Pokrovskiy
2015-07-11 23:35:49 -07:00
committed by wonder.mice
parent 16ab843f95
commit 9f29762d34
3 changed files with 101 additions and 32 deletions

View File

@@ -86,53 +86,83 @@ QList<FormattedText> AnsiEscapeCodeHandler::parseText(const FormattedText &input
DefaultBackgroundColor = 49 DefaultBackgroundColor = 49
}; };
QList<FormattedText> outputData;
QTextCharFormat charFormat = m_previousFormatClosed ? input.format : m_previousFormat;
const QString escape = QLatin1String("\x1b["); 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 semicolon = QLatin1Char(';');
const QChar colorTerminator = QLatin1Char('m'); const QChar colorTerminator = QLatin1Char('m');
const QChar eraseToEol = QLatin1Char('K'); 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.isEmpty()) {
while (strippedText.startsWith(escape)) { QTC_ASSERT(m_pendingText.isEmpty(), break);
strippedText.remove(0, 2); 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. // \e[K is not supported. Just strip it.
if (strippedText.startsWith(eraseToEol)) { if (strippedText.startsWith(eraseToEol)) {
m_pendingText.clear();
strippedText.remove(0, 1); strippedText.remove(0, 1);
continue; continue;
} }
// get the number // get the number
QString strNumber; QString strNumber;
QStringList numbers; QStringList numbers;
while (strippedText.at(0).isDigit() || strippedText.at(0) == semicolon) { while (!strippedText.isEmpty()) {
if (strippedText.at(0).isDigit()) { if (strippedText.at(0).isDigit()) {
strNumber += strippedText.at(0); strNumber += strippedText.at(0);
} else { } else {
numbers << strNumber;
strNumber.clear();
}
strippedText.remove(0, 1);
}
if (!strNumber.isEmpty()) if (!strNumber.isEmpty())
numbers << strNumber; 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 // remove terminating char
if (!strippedText.startsWith(colorTerminator)) { if (!strippedText.startsWith(colorTerminator)) {
m_pendingText.clear();
strippedText.remove(0, 1); strippedText.remove(0, 1);
continue; break;
} }
// got consistent control sequence, ok to clear pending text
m_pendingText.clear();
strippedText.remove(0, 1); strippedText.remove(0, 1);
if (numbers.isEmpty()) { 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; return outputData;
} }

View File

@@ -62,6 +62,7 @@ private:
bool m_previousFormatClosed; bool m_previousFormatClosed;
QTextCharFormat m_previousFormat; QTextCharFormat m_previousFormat;
QString m_pendingText;
}; };
} // namespace Utils } // namespace Utils

View File

@@ -57,6 +57,7 @@ private Q_SLOTS:
void testSimpleFormat(); void testSimpleFormat();
void testSimpleFormat_data(); void testSimpleFormat_data();
void testLineOverlappingFormat(); void testLineOverlappingFormat();
void testSplitControlSequence();
private: private:
const QString red; const QString red;
@@ -225,6 +226,31 @@ void tst_AnsiEscapeCodeHandler::testSimpleFormat_data()
<< (FormattedTextList() << (FormattedTextList()
<< FormattedText("All text after this is ", defaultFormat) << FormattedText("All text after this is ", defaultFormat)
<< FormattedText("not deleted", 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() void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat()
@@ -254,6 +280,29 @@ void tst_AnsiEscapeCodeHandler::testLineOverlappingFormat()
QCOMPARE(result[3].format, defaultFormat); 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) QTEST_APPLESS_MAIN(tst_AnsiEscapeCodeHandler)
#include "tst_ansiescapecodehandler.moc" #include "tst_ansiescapecodehandler.moc"