Debugger: Split long cdbextension commands

Fixes: QTCREATORBUG-22922
Change-Id: I5def321f0f97717728bc5cdcd5309b458a8ecfa1
Reviewed-by: Christian Stenger <christian.stenger@qt.io>
This commit is contained in:
David Schulz
2020-03-31 14:09:32 +02:00
parent 41a122641c
commit 9cfd1b5d6c
3 changed files with 177 additions and 27 deletions

View File

@@ -191,6 +191,12 @@ static inline bool isOption(const std::string &opt)
return opt.size() == 2 && opt.at(0) == '-' && opt != "--"; return opt.size() == 2 && opt.at(0) == '-' && opt != "--";
} }
struct CommandToken
{
int majorPart = 0;
int minorPart = 0;
};
// Helper for commandTokens() below: // Helper for commandTokens() below:
// Simple splitting of command lines allowing for '"'-quoted tokens // Simple splitting of command lines allowing for '"'-quoted tokens
// 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *') // 'typecast local.i "class QString *"' -> ('typecast','local.i','class QString *')
@@ -251,6 +257,38 @@ static inline void splitCommand(PCSTR args, Inserter it)
} }
} }
// check whether arguments start with a "-t 23.1" or "-t 3" token identifier
CommandToken extractToken(PCSTR args, PCSTR *afterToken)
{
CommandToken token;
while (*args == ' ') // skip whitespace
++args;
if (*args != '-')
return {};
if (*(++args) != 't')
return {};
++args;
while (*args == ' ') // skip whitespace
++args;
PSTR end = nullptr;
token.majorPart = strtol(args, &end, 10);
if (args >= end)
return {};
args = end;
if (*args == '.') {
++args;
end = nullptr;
token.minorPart = strtol(args, &end, 10);
if (args >= end)
return {};
args = end;
}
while (*args == ' ') // skip whitespace
++args;
*afterToken = args;
return token;
}
// Split & Parse the arguments of a command and extract the // Split & Parse the arguments of a command and extract the
// optional first integer token argument ('command -t <number> remaining arguments') // optional first integer token argument ('command -t <number> remaining arguments')
// Each command takes the 'token' argument and includes it in its output // Each command takes the 'token' argument and includes it in its output
@@ -259,18 +297,25 @@ static inline void splitCommand(PCSTR args, Inserter it)
template<class StringContainer> template<class StringContainer>
static inline StringContainer commandTokens(PCSTR args, int *token = 0) static inline StringContainer commandTokens(PCSTR args, int *token = 0)
{ {
typedef typename StringContainer::iterator ContainerIterator; static std::map<int, std::string> s_commandBuffer;
if (token) if (token)
*token = -1; // Handled as 'display' in engine, so that user can type commands *token = -1; // Handled as 'display' in engine, so that user can type commands
StringContainer tokens; StringContainer tokens;
splitCommand(args, std::back_inserter(tokens)); CommandToken commandToken = extractToken(args, &args);
// Check for token if (commandToken.majorPart != 0) {
ContainerIterator it = tokens.begin(); s_commandBuffer[commandToken.majorPart].append(args);
if (it != tokens.end() && *it == "-t" && ++it != tokens.end()) { if (commandToken.minorPart == 0) {
if (token) DebugPrint() << commandToken.majorPart << ':' << s_commandBuffer[commandToken.majorPart];
std::istringstream(*it) >> *token; splitCommand(s_commandBuffer[commandToken.majorPart].data(), std::back_inserter(tokens));
tokens.erase(tokens.begin(), ++it); if (token)
*token = commandToken.majorPart;
} else if (token) {
*token = 0;
}
} else {
splitCommand(args, std::back_inserter(tokens));
} }
return tokens; return tokens;
} }
@@ -283,6 +328,8 @@ extern "C" HRESULT CALLBACK pid(CIDebugClient *client, PCSTR args)
int token; int token;
commandTokens<StringList>(args, &token); commandTokens<StringList>(args, &token);
if (token == 0) // partial message
return S_OK;
dprintf("Qt Creator CDB extension version 4.3 %d bit.\n", dprintf("Qt Creator CDB extension version 4.3 %d bit.\n",
sizeof(void *) * 8); sizeof(void *) * 8);
if (const ULONG pid = currentProcessId(client)) if (const ULONG pid = currentProcessId(client))
@@ -304,6 +351,9 @@ extern "C" HRESULT CALLBACK expandlocals(CIDebugClient *client, PCSTR args)
int token; int token;
StringList tokens = commandTokens<StringList>(args, &token); StringList tokens = commandTokens<StringList>(args, &token);
if (token == 0) // partial message
return S_OK;
StringVector inames; StringVector inames;
bool runComplexDumpers = false; bool runComplexDumpers = false;
do { do {
@@ -423,10 +473,12 @@ static std::string commandLocals(ExtensionCommandContext &commandExtCtx,PCSTR ar
typedef InameExpressionMap::value_type InameExpressionMapEntry; typedef InameExpressionMap::value_type InameExpressionMapEntry;
// Parse the command // Parse the command
StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial arguments
return {};
ExtensionContext &extCtx = ExtensionContext::instance(); ExtensionContext &extCtx = ExtensionContext::instance();
DumpCommandParameters parameters; DumpCommandParameters parameters;
std::string iname; std::string iname;
StringList tokens = commandTokens<StringList>(args, token);
StringVector expandedInames; StringVector expandedInames;
StringVector uninitializedInames; StringVector uninitializedInames;
InameExpressionMap watcherInameExpressionMap; InameExpressionMap watcherInameExpressionMap;
@@ -577,8 +629,11 @@ extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn)
ExtensionCommandContext exc(client); ExtensionCommandContext exc(client);
int token; int token;
#ifdef WITH_PYTHON #ifdef WITH_PYTHON
const StringList args = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial arguments
return {};
std::stringstream command; std::stringstream command;
for (std::string arg : commandTokens<StringList>(argsIn, &token)) for (std::string arg : args)
command << arg << ' '; command << arg << ' ';
PyObject *ptype = NULL; PyObject *ptype = NULL;
@@ -594,6 +649,8 @@ extern "C" HRESULT CALLBACK script(CIDebugClient *client, PCSTR argsIn)
PyErr_Restore(ptype, pvalue, ptraceback); PyErr_Restore(ptype, pvalue, ptraceback);
#else #else
commandTokens<StringList>(argsIn, &token); commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial arguments
return {};
ExtensionContext::instance().report('N', token, 0, "script", ExtensionContext::instance().report('N', token, 0, "script",
"Python is not supported in this CDB extension.\n" "Python is not supported in this CDB extension.\n"
"You need to define PYTHON_INSTALL_DIR in your creator build environment " "You need to define PYTHON_INSTALL_DIR in your creator build environment "
@@ -608,6 +665,8 @@ extern "C" HRESULT CALLBACK locals(CIDebugClient *client, PCSTR args)
std::string errorMessage; std::string errorMessage;
int token; int token;
const std::string output = commandLocals(exc, args, &token, &errorMessage); const std::string output = commandLocals(exc, args, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
SymbolGroupValue::verbose = 0; SymbolGroupValue::verbose = 0;
if (output.empty()) if (output.empty())
ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str()); ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
@@ -625,6 +684,9 @@ static std::string commmandWatches(ExtensionCommandContext &exc, PCSTR args, int
// Parse the command // Parse the command
DumpCommandParameters parameters; DumpCommandParameters parameters;
StringList tokens = commandTokens<StringList>(args, token); StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial message
return {};
// Parse away options // Parse away options
for (bool optionLeft = true; optionLeft && !tokens.empty(); ) { for (bool optionLeft = true; optionLeft && !tokens.empty(); ) {
switch (parameters.parseOption(&tokens)) { switch (parameters.parseOption(&tokens)) {
@@ -662,6 +724,9 @@ extern "C" HRESULT CALLBACK watches(CIDebugClient *client, PCSTR args)
std::string errorMessage = "e"; std::string errorMessage = "e";
int token = 0; int token = 0;
const std::string output = commmandWatches(exc, args, &token, &errorMessage); const std::string output = commmandWatches(exc, args, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
SymbolGroupValue::verbose = 0; SymbolGroupValue::verbose = 0;
if (output.empty()) if (output.empty())
ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str()); ExtensionContext::instance().report('N', token, 0, "locals", errorMessage.c_str());
@@ -677,6 +742,9 @@ static std::string dumplocalHelper(ExtensionCommandContext &exc,PCSTR args, int
{ {
// Parse the command // Parse the command
StringList tokens = commandTokens<StringList>(args, token); StringList tokens = commandTokens<StringList>(args, token);
if (token == 0) // partial message
return {};
// Frame and iname // Frame and iname
unsigned frame; unsigned frame;
if (tokens.empty() || integerFromString(tokens.front(), &frame)) { if (tokens.empty() || integerFromString(tokens.front(), &frame)) {
@@ -714,6 +782,9 @@ extern "C" HRESULT CALLBACK dumplocal(CIDebugClient *client, PCSTR argsIn)
std::string errorMessage; std::string errorMessage;
int token = 0; int token = 0;
const std::string value = dumplocalHelper(exc,argsIn, &token, &errorMessage); const std::string value = dumplocalHelper(exc,argsIn, &token, &errorMessage);
if (token == 0) // partial message
return S_OK;
if (value.empty()) if (value.empty())
ExtensionContext::instance().report('N', token, 0, "dumplocal", errorMessage.c_str()); ExtensionContext::instance().report('N', token, 0, "dumplocal", errorMessage.c_str());
else else
@@ -733,6 +804,9 @@ extern "C" HRESULT CALLBACK typecast(CIDebugClient *client, PCSTR args)
int token; int token;
const StringVector tokens = commandTokens<StringVector>(args, &token); const StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
std::string iname; std::string iname;
std::string desiredType; std::string desiredType;
if (tokens.size() == 3u && integerFromString(tokens.front(), &frame)) { if (tokens.size() == 3u && integerFromString(tokens.front(), &frame)) {
@@ -761,6 +835,9 @@ extern "C" HRESULT CALLBACK addsymbol(CIDebugClient *client, PCSTR args)
int token; int token;
const StringVector tokens = commandTokens<StringVector>(args, &token); const StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
std::string name; std::string name;
std::string iname; std::string iname;
if (tokens.size() >= 2u && integerFromString(tokens.front(), &frame)) { if (tokens.size() >= 2u && integerFromString(tokens.front(), &frame)) {
@@ -790,6 +867,9 @@ extern "C" HRESULT CALLBACK addwatch(CIDebugClient *client, PCSTR argsIn)
bool success = false; bool success = false;
do { do {
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 2) { if (tokens.size() != 2) {
errorMessage = singleLineUsage(commandDescriptions[CmdAddWatch]); errorMessage = singleLineUsage(commandDescriptions[CmdAddWatch]);
break; break;
@@ -824,6 +904,9 @@ extern "C" HRESULT CALLBACK assign(CIDebugClient *client, PCSTR argsIn)
int token = 0; int token = 0;
do { do {
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.empty()) { if (tokens.empty()) {
errorMessage = singleLineUsage(commandDescriptions[CmdAssign]); errorMessage = singleLineUsage(commandDescriptions[CmdAssign]);
break; break;
@@ -883,6 +966,8 @@ extern "C" HRESULT CALLBACK threads(CIDebugClient *client, PCSTR argsIn)
int token; int token;
commandTokens<StringList>(argsIn, &token); commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const std::string gdbmi = gdbmiThreadList(exc.systemObjects(), const std::string gdbmi = gdbmiThreadList(exc.systemObjects(),
exc.symbols(), exc.symbols(),
@@ -906,6 +991,9 @@ extern "C" HRESULT CALLBACK registers(CIDebugClient *Client, PCSTR argsIn)
int token; int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token); const StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const bool humanReadable = !tokens.empty() && tokens.front() == "-h"; const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage); const std::string regs = gdbmiRegisters(exc.registers(), exc.control(), humanReadable, IncludePseudoRegisters, &errorMessage);
if (regs.empty()) if (regs.empty())
@@ -925,6 +1013,9 @@ extern "C" HRESULT CALLBACK modules(CIDebugClient *Client, PCSTR argsIn)
int token; int token;
const StringList tokens = commandTokens<StringList>(argsIn, &token); const StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
const bool humanReadable = !tokens.empty() && tokens.front() == "-h"; const bool humanReadable = !tokens.empty() && tokens.front() == "-h";
const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage); const std::string modules = gdbmiModules(exc.symbols(), humanReadable, &errorMessage);
if (modules.empty()) if (modules.empty())
@@ -949,6 +1040,9 @@ extern "C" HRESULT CALLBACK setparameter(CIDebugClient *, PCSTR args)
{ {
int token; int token;
StringVector tokens = commandTokens<StringVector>(args, &token); StringVector tokens = commandTokens<StringVector>(args, &token);
if (token == 0) // partial message
return S_OK;
const size_t count = tokens.size(); const size_t count = tokens.size();
size_t success = 0; size_t success = 0;
for (size_t i = 0; i < count; ++i) { for (size_t i = 0; i < count; ++i) {
@@ -1014,6 +1108,9 @@ extern "C" HRESULT CALLBACK memory(CIDebugClient *Client, PCSTR argsIn)
ULONG length = 0; ULONG length = 0;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token); const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() == 2 if (tokens.size() == 2
&& integerFromString(tokens.front(), &address) && integerFromString(tokens.front(), &address)
&& integerFromString(tokens.at(1), &length)) { && integerFromString(tokens.at(1), &length)) {
@@ -1041,6 +1138,9 @@ extern "C" HRESULT CALLBACK expression(CIDebugClient *Client, PCSTR argsIn)
do { do {
const StringVector tokens = commandTokens<StringVector>(argsIn, &token); const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 1) { if (tokens.size() != 1) {
errorMessage = singleLineUsage(commandDescriptions[CmdExpression]); errorMessage = singleLineUsage(commandDescriptions[CmdExpression]);
@@ -1070,6 +1170,9 @@ extern "C" HRESULT CALLBACK stack(CIDebugClient *Client, PCSTR argsIn)
unsigned maxFrames = ExtensionContext::instance().parameters().maxStackDepth; unsigned maxFrames = ExtensionContext::instance().parameters().maxStackDepth;
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (!tokens.empty() && tokens.front() == "-h") { if (!tokens.empty() && tokens.front() == "-h") {
humanReadable = true; humanReadable = true;
tokens.pop_front(); tokens.pop_front();
@@ -1106,6 +1209,9 @@ extern "C" HRESULT CALLBACK qmlstack(CIDebugClient *client, PCSTR argsIn)
do { do {
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (!tokens.empty() && tokens.front() == "-h") { if (!tokens.empty() && tokens.front() == "-h") {
humanReadable = true; humanReadable = true;
tokens.pop_front(); tokens.pop_front();
@@ -1170,6 +1276,9 @@ extern "C" HRESULT CALLBACK widgetat(CIDebugClient *client, PCSTR argsIn)
int y = -1; int y = -1;
const StringVector tokens = commandTokens<StringVector>(argsIn, &token); const StringVector tokens = commandTokens<StringVector>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
if (tokens.size() != 2) { if (tokens.size() != 2) {
errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]); errorMessage = singleLineUsage(commandDescriptions[CmdWidgetAt]);
break; break;
@@ -1197,6 +1306,9 @@ extern "C" HRESULT CALLBACK breakpoints(CIDebugClient *client, PCSTR argsIn)
bool humanReadable = false; bool humanReadable = false;
unsigned verbose = 0; unsigned verbose = 0;
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') { while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
switch (tokens.front().at(1)) { switch (tokens.front().at(1)) {
case 'h': case 'h':
@@ -1226,6 +1338,9 @@ extern "C" HRESULT CALLBACK test(CIDebugClient *client, PCSTR argsIn)
Mode mode = Invalid; Mode mode = Invalid;
int token = 0; int token = 0;
StringList tokens = commandTokens<StringList>(argsIn, &token); StringList tokens = commandTokens<StringList>(argsIn, &token);
if (token == 0) // partial message
return S_OK;
// Parse away options // Parse away options
while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') { while (!tokens.empty() && tokens.front().size() == 2 && tokens.front().at(0) == '-') {
const char option = tokens.front().at(1); const char option = tokens.front().at(1);

View File

@@ -959,6 +959,13 @@ void CdbEngine::executeDebuggerCommand(const QString &command)
// Post command to the cdb process // Post command to the cdb process
void CdbEngine::runCommand(const DebuggerCommand &dbgCmd) void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
{ {
constexpr int maxCommandLength = 4096;
constexpr int maxTokenLength = 4 /*" -t "*/
+ 5 /* 99999 tokens should be enough for a single qc run time*/
+ 1 /* token part splitter '.' */
+ 3 /* 1000 parts should also be more than enough */
+ 1 /* final space */;
QString cmd = dbgCmd.function + dbgCmd.argsToString(); QString cmd = dbgCmd.function + dbgCmd.argsToString();
if (!m_accessible) { if (!m_accessible) {
doInterruptInferior([this, dbgCmd](){ doInterruptInferior([this, dbgCmd](){
@@ -970,11 +977,26 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
return; return;
} }
if (dbgCmd.flags == ScriptCommand) {
// repack script command into an extension command
DebuggerCommand newCmd("script", ExtensionCommand, dbgCmd.callback);
if (!dbgCmd.args.isNull())
newCmd.args = QString{dbgCmd.function + '(' + dbgCmd.argsToPython() + ')'};
else
newCmd.args = dbgCmd.function;
runCommand(newCmd);
return;
}
QString fullCmd; QString fullCmd;
if (dbgCmd.flags == NoFlags) { if (dbgCmd.flags == NoFlags) {
fullCmd = cmd; fullCmd = cmd + '\n';
if (fullCmd.length() > maxCommandLength) {
showMessage("Command is longer than 4096 characters execution will likely fail.",
LogWarning);
}
} else { } else {
const int token = m_nextCommandToken++; const int token = ++m_nextCommandToken;
StringInputStream str(fullCmd); StringInputStream str(fullCmd);
if (dbgCmd.flags == BuiltinCommand) { if (dbgCmd.flags == BuiltinCommand) {
// Post a built-in-command producing free-format output with a callback. // Post a built-in-command producing free-format output with a callback.
@@ -982,23 +1004,35 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
// printing a specially formatted token to be identifiable in the output. // printing a specially formatted token to be identifiable in the output.
str << ".echo \"" << m_tokenPrefix << token << "<\"\n" str << ".echo \"" << m_tokenPrefix << token << "<\"\n"
<< cmd << "\n" << cmd << "\n"
<< ".echo \"" << m_tokenPrefix << token << ">\""; << ".echo \"" << m_tokenPrefix << token << ">\"" << '\n';
if (fullCmd.length() > maxCommandLength) {
showMessage("Command is longer than 4096 characters execution will likely fail.",
LogWarning);
}
} else if (dbgCmd.flags == ExtensionCommand) { } else if (dbgCmd.flags == ExtensionCommand) {
// Post an extension command producing one-line output with a callback, // Post an extension command producing one-line output with a callback,
// pass along token for identification in hash. // pass along token for identification in hash.
str << m_extensionCommandPrefix << dbgCmd.function << "%1%2"; const QString prefix = m_extensionCommandPrefix + dbgCmd.function;
if (dbgCmd.args.isString()) QList<QStringRef> splittedArguments;
str << ' ' << dbgCmd.argsToString(); if (dbgCmd.args.isString()) {
cmd = fullCmd.arg("", ""); const QString &arguments = dbgCmd.argsToString();
fullCmd = fullCmd.arg(" -t ").arg(token); cmd = prefix + arguments;
} else if (dbgCmd.flags == ScriptCommand) { int argumentSplitPos = 0;
// Add extension prefix and quotes the script command QList<QStringRef> splittedArguments;
// pass along token for identification in hash. int maxArgumentSize = maxCommandLength - prefix.length() - maxTokenLength;
str << m_extensionCommandPrefix + "script %1%2 " << dbgCmd.function; while (argumentSplitPos < arguments.size()) {
if (!dbgCmd.args.isNull()) splittedArguments << arguments.midRef(argumentSplitPos, maxArgumentSize);
str << '(' << dbgCmd.argsToPython() << ')'; argumentSplitPos += splittedArguments.last().length();
cmd = fullCmd.arg("", ""); }
fullCmd = fullCmd.arg(" -t ").arg(token); QTC_CHECK(argumentSplitPos == arguments.size());
int tokenPart = splittedArguments.size();
for (const QStringRef part : qAsConst(splittedArguments))
str << prefix << " -t " << token << '.' << --tokenPart << ' ' << part << '\n';
} else {
cmd = prefix;
str << prefix << " -t " << token << '.' << 0 << '\n';
}
} }
m_commandForToken.insert(token, dbgCmd); m_commandForToken.insert(token, dbgCmd);
} }
@@ -1011,7 +1045,7 @@ void CdbEngine::runCommand(const DebuggerCommand &dbgCmd)
qDebug("CdbEngine::postCommand: resulting command '%s'\n", qPrintable(fullCmd)); qDebug("CdbEngine::postCommand: resulting command '%s'\n", qPrintable(fullCmd));
} }
showMessage(cmd, LogInput); showMessage(cmd, LogInput);
m_process.write(fullCmd.toLocal8Bit() + '\n'); m_process.write(fullCmd.toLocal8Bit());
} }
void CdbEngine::activateFrame(int index) void CdbEngine::activateFrame(int index)

View File

@@ -42,6 +42,7 @@ public:
StringInputStream &operator<<(char a) { m_target.append(a); return *this; } StringInputStream &operator<<(char a) { m_target.append(a); return *this; }
StringInputStream &operator<<(const char *a) { m_target.append(QString::fromUtf8(a)); return *this; } StringInputStream &operator<<(const char *a) { m_target.append(QString::fromUtf8(a)); return *this; }
StringInputStream &operator<<(const QString &a) { m_target.append(a); return *this; } StringInputStream &operator<<(const QString &a) { m_target.append(a); return *this; }
StringInputStream &operator<<(const QStringRef &a) { m_target.append(a); return *this; }
StringInputStream &operator<<(int i) { appendInt(i); return *this; } StringInputStream &operator<<(int i) { appendInt(i); return *this; }
StringInputStream &operator<<(unsigned i) { appendInt(i); return *this; } StringInputStream &operator<<(unsigned i) { appendInt(i); return *this; }