Commit 9f29762d authored by Andrey Pokrovskiy's avatar Andrey Pokrovskiy Committed by wonder.mice
Browse files

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: default avatarTobias Hunger <tobias.hunger@theqtcompany.com>
Reviewed-by: default avatarAndré Hartmann <aha_1980@gmx.de>
Reviewed-by: default avatarwonder.mice <wonder.mice@gmail.com>
parent 16ab843f
......@@ -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;
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 (!strNumber.isEmpty())
numbers << strNumber;
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"
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment