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
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ private:
|
|||||||
|
|
||||||
bool m_previousFormatClosed;
|
bool m_previousFormatClosed;
|
||||||
QTextCharFormat m_previousFormat;
|
QTextCharFormat m_previousFormat;
|
||||||
|
QString m_pendingText;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Utils
|
} // namespace Utils
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user