File indexing completed on 2024-05-05 16:45:13
0001 /* 0002 SPDX-FileCopyrightText: 2013 Christoph Thielecke <crissi99@gmx.de> 0003 SPDX-FileCopyrightText: 2016 Anton Anikin <anton.anikin@htower.ru> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "parser.h" 0009 0010 #include "debug.h" 0011 0012 #include <interfaces/icore.h> 0013 #include <interfaces/iuicontroller.h> 0014 #include <shell/problem.h> 0015 #include <sublime/message.h> 0016 #include <language/editor/documentrange.h> 0017 // KF 0018 #include <KLocalizedString> 0019 // Qt 0020 #include <QApplication> 0021 #include <QFile> 0022 0023 namespace cppcheck 0024 { 0025 0026 /** 0027 * Convert the value of \<verbose\> attribute of \<error\> element from cppcheck's 0028 * XML-output to 'good-looking' HTML-version. This is necessary because the 0029 * displaying of the original message is performed without line breaks - such 0030 * tooltips are uncomfortable to read, and large messages will not fit into the 0031 * screen. 0032 * 0033 * This function put the original message into \<html\> tag that automatically 0034 * provides line wrapping by builtin capabilities of Qt library. The source text 0035 * also can contain tokens '\012' (line break) - they are present in the case of 0036 * source code examples. In such cases, the entire text between the first and 0037 * last tokens (i.e. source code) is placed into \<pre\> tag. 0038 * 0039 * @param[in] input the original value of \<verbose\> attribute 0040 * @return HTML version for displaying in problem's tooltip 0041 */ 0042 QString verboseMessageToHtml( const QString & input ) 0043 { 0044 QString output(QStringLiteral("<html>%1</html>").arg(input.toHtmlEscaped())); 0045 0046 output.replace(QLatin1String("\\012"), QLatin1String("\n")); 0047 0048 if (output.count(QLatin1Char('\n')) >= 2) { 0049 output.replace(output.indexOf(QLatin1Char('\n')), 1, QStringLiteral("<pre>") ); 0050 output.replace(output.lastIndexOf(QLatin1Char('\n')), 1, QStringLiteral("</pre><br>") ); 0051 } 0052 0053 return output; 0054 } 0055 0056 CppcheckParser::CppcheckParser() 0057 { 0058 } 0059 0060 CppcheckParser::~CppcheckParser() 0061 { 0062 } 0063 0064 void CppcheckParser::clear() 0065 { 0066 m_stateStack.clear(); 0067 } 0068 0069 bool CppcheckParser::startElement() 0070 { 0071 State newState = Unknown; 0072 0073 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: elem: " << qPrintable(name().toString()); 0074 0075 if (name() == QLatin1String("results")) { 0076 newState = Results; 0077 } 0078 0079 else if (name() == QLatin1String("cppcheck")) { 0080 newState = CppCheck; 0081 } 0082 0083 else if (name() == QLatin1String("errors")) { 0084 newState = Errors; 0085 } 0086 0087 else if (name() == QLatin1String("location")) { 0088 newState = Location; 0089 if (attributes().hasAttribute(QStringLiteral("file")) && attributes().hasAttribute(QStringLiteral("line"))) { 0090 QString errorFile = attributes().value(QStringLiteral("file")).toString(); 0091 0092 // Usually when "file0" attribute exists it associated with source and 0093 // attribute "file" associated with header). 0094 // But sometimes cppcheck produces errors with "file" and "file0" attributes 0095 // both associated with same *source* file. In such cases attribute "file" contains 0096 // only file name, without full path. Therefore we should use "file0" instead "file". 0097 if (!QFile::exists(errorFile) && attributes().hasAttribute(QStringLiteral("file0"))) { 0098 errorFile = attributes().value(QStringLiteral("file0")).toString(); 0099 } 0100 0101 m_errorFiles += errorFile; 0102 m_errorLines += attributes().value(QStringLiteral("line")).toString().toInt(); 0103 } 0104 } 0105 0106 else if (name() == QLatin1String("error")) { 0107 newState = Error; 0108 m_errorSeverity = QStringLiteral("unknown"); 0109 m_errorInconclusive = false; 0110 m_errorFiles.clear(); 0111 m_errorLines.clear(); 0112 m_errorMessage.clear(); 0113 m_errorVerboseMessage.clear(); 0114 0115 if (attributes().hasAttribute(QStringLiteral("msg"))) { 0116 m_errorMessage = attributes().value(QStringLiteral("msg")).toString(); 0117 } 0118 0119 if (attributes().hasAttribute(QStringLiteral("verbose"))) { 0120 m_errorVerboseMessage = verboseMessageToHtml(attributes().value(QStringLiteral("verbose")).toString()); 0121 } 0122 0123 if (attributes().hasAttribute(QStringLiteral("severity"))) { 0124 m_errorSeverity = attributes().value(QStringLiteral("severity")).toString(); 0125 } 0126 0127 if (attributes().hasAttribute(QStringLiteral("inconclusive"))) { 0128 m_errorInconclusive = true; 0129 } 0130 } 0131 0132 else { 0133 m_stateStack.push(m_stateStack.top()); 0134 return true; 0135 } 0136 0137 m_stateStack.push(newState); 0138 0139 return true; 0140 } 0141 0142 bool CppcheckParser::endElement(QVector<KDevelop::IProblem::Ptr>& problems) 0143 { 0144 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: elem: " << qPrintable(name().toString()); 0145 0146 State state = m_stateStack.pop(); 0147 0148 switch (state) { 0149 case CppCheck: 0150 if (attributes().hasAttribute(QStringLiteral("version"))) { 0151 qCDebug(KDEV_CPPCHECK) << "Cppcheck report version: " << attributes().value(QStringLiteral("version")); 0152 } 0153 break; 0154 0155 case Errors: 0156 // errors finished 0157 break; 0158 0159 case Error: 0160 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::endElement: new error elem: line: " 0161 << (m_errorLines.isEmpty() ? QStringLiteral("?") : QString::number(m_errorLines.first())) 0162 << " at " << (m_errorFiles.isEmpty() ? QStringLiteral("?") : m_errorFiles.first()) 0163 << ", msg: " << m_errorMessage; 0164 0165 storeError(problems); 0166 break; 0167 0168 case Results: 0169 // results finished 0170 break; 0171 0172 case Location: 0173 break; 0174 0175 default: 0176 break; 0177 } 0178 0179 return true; 0180 } 0181 0182 QVector<KDevelop::IProblem::Ptr> CppcheckParser::parse() 0183 { 0184 QVector<KDevelop::IProblem::Ptr> problems; 0185 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse!"; 0186 0187 while (!atEnd()) { 0188 int readNextVal = readNext(); 0189 0190 switch (readNextVal) { 0191 case StartDocument: 0192 clear(); 0193 break; 0194 0195 case StartElement: 0196 startElement(); 0197 break; 0198 0199 case EndElement: 0200 endElement(problems); 0201 break; 0202 0203 case Characters: 0204 break; 0205 0206 default: 0207 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::startElement: case: " << readNextVal; 0208 break; 0209 } 0210 } 0211 0212 qCDebug(KDEV_CPPCHECK) << "CppcheckParser::parse: end"; 0213 0214 if (hasError()) { 0215 switch (error()) { 0216 case CustomError: 0217 case UnexpectedElementError: 0218 case NotWellFormedError: { 0219 const QString messageText = 0220 i18n("Cppcheck XML Parsing: error at line %1, column %2: %3", lineNumber(), columnNumber(), errorString()); 0221 auto* message = new Sublime::Message(messageText, Sublime::Message::Error); 0222 KDevelop::ICore::self()->uiController()->postMessage(message); 0223 break; 0224 } 0225 case NoError: 0226 case PrematureEndOfDocumentError: 0227 break; 0228 } 0229 } 0230 0231 return problems; 0232 } 0233 0234 void CppcheckParser::storeError(QVector<KDevelop::IProblem::Ptr>& problems) 0235 { 0236 // Construct problem with using first location element 0237 KDevelop::IProblem::Ptr problem = getProblem(); 0238 0239 // Adds other <location> elements as diagnostics. 0240 // This allows the user to track the problem. 0241 for (int locationIdx = 1; locationIdx < m_errorFiles.size(); ++locationIdx) { 0242 problem->addDiagnostic(getProblem(locationIdx)); 0243 } 0244 0245 problems.push_back(problem); 0246 } 0247 0248 KDevelop::IProblem::Ptr CppcheckParser::getProblem(int locationIdx) const 0249 { 0250 KDevelop::IProblem::Ptr problem(new KDevelop::DetectedProblem(i18n("Cppcheck"))); 0251 QStringList messagePrefix; 0252 QString errorMessage(m_errorMessage); 0253 0254 if (m_errorSeverity == QLatin1String("error")) { 0255 problem->setSeverity(KDevelop::IProblem::Error); 0256 } 0257 0258 else if (m_errorSeverity == QLatin1String("warning")) { 0259 problem->setSeverity(KDevelop::IProblem::Warning); 0260 } 0261 0262 else { 0263 problem->setSeverity(KDevelop::IProblem::Hint); 0264 0265 messagePrefix.push_back(m_errorSeverity); 0266 } 0267 0268 if (m_errorInconclusive) { 0269 messagePrefix.push_back(QStringLiteral("inconclusive")); 0270 } 0271 0272 if (!messagePrefix.isEmpty()) { 0273 errorMessage = QStringLiteral("(%1) %2").arg(messagePrefix.join(QLatin1String(", ")), m_errorMessage); 0274 } 0275 0276 problem->setDescription(errorMessage); 0277 problem->setExplanation(m_errorVerboseMessage); 0278 0279 KDevelop::DocumentRange range; 0280 0281 if (locationIdx < 0 || locationIdx >= m_errorFiles.size()) { 0282 range = KDevelop::DocumentRange::invalid(); 0283 } else { 0284 range.document = KDevelop::IndexedString(m_errorFiles.at(locationIdx)); 0285 range.setBothLines(m_errorLines.at(locationIdx) - 1); 0286 range.setBothColumns(0); 0287 } 0288 0289 problem->setFinalLocation(range); 0290 problem->setFinalLocationMode(KDevelop::IProblem::TrimmedLine); 0291 0292 return problem; 0293 } 0294 0295 }