File indexing completed on 2024-05-05 04:39:30

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 }