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

0001 /*
0002     SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org>
0003 
0004     SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0005 */
0006 
0007 #include "clangproblem.h"
0008 #include <interfaces/idocumentcontroller.h>
0009 #include <interfaces/icore.h>
0010 
0011 #include "util/clangtypes.h"
0012 #include "util/clangdebug.h"
0013 #include "util/clangutils.h"
0014 
0015 #include <language/duchain/duchainlock.h>
0016 #include <language/codegen/documentchangeset.h>
0017 
0018 #include <KLocalizedString>
0019 
0020 using namespace KDevelop;
0021 
0022 namespace {
0023 
0024 IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName)
0025 {
0026     switch (severity) {
0027     case CXDiagnostic_Fatal:
0028     case CXDiagnostic_Error:
0029         return IProblem::Error;
0030     case CXDiagnostic_Warning:
0031         if (optionName.startsWith(QLatin1String("-Wunused-"))) {
0032             return IProblem::Hint;
0033         }
0034         return IProblem::Warning;
0035     default:
0036         return IProblem::Hint;
0037     }
0038 }
0039 
0040 /**
0041  * Clang diagnostic messages always start with a lowercase character
0042  *
0043  * @return Prettified version, starting with uppercase character
0044  */
0045 inline QString prettyDiagnosticSpelling(const QString& str)
0046 {
0047     QString ret = str;
0048     if (ret.isEmpty()) {
0049       return {};
0050     }
0051     ret[0] = ret[0].toUpper();
0052     return ret;
0053 }
0054 
0055 ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic, CXTranslationUnit unit)
0056 {
0057     ClangFixits fixits;
0058     auto numFixits = clang_getDiagnosticNumFixIts(diagnostic);
0059     fixits.reserve(numFixits);
0060     for (uint i = 0; i < numFixits; ++i) {
0061         CXSourceRange range;
0062         const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString();
0063         const auto original = ClangUtils::getRawContents(unit, range);
0064         fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString(), original};
0065     }
0066     return fixits;
0067 }
0068 
0069 }
0070 
0071 QDebug operator<<(QDebug debug, const ClangFixit& fixit)
0072 {
0073     debug.nospace() << "ClangFixit["
0074         << "replacementText=" << fixit.replacementText
0075         << ", range=" << fixit.range
0076         << ", description=" << fixit.description
0077         << ", currentText=" << fixit.currentText
0078         << "]";
0079     return debug;
0080 }
0081 
0082 ClangProblem::ClangProblem() = default;
0083 
0084 ClangProblem::ClangProblem(const ClangProblem& other)
0085     : Problem(),
0086       m_fixits(other.m_fixits)
0087 {
0088     setSource(other.source());
0089     setFinalLocation(other.finalLocation());
0090     setFinalLocationMode(other.finalLocationMode());
0091     setDescription(other.description());
0092     setExplanation(other.explanation());
0093     setSeverity(other.severity());
0094 
0095     auto diagnostics = other.diagnostics();
0096     for (auto& diagnostic : diagnostics) {
0097         auto* clangDiagnostic = dynamic_cast<ClangProblem*>(diagnostic.data());
0098         Q_ASSERT(clangDiagnostic);
0099         diagnostic = ClangProblem::Ptr(new ClangProblem(*clangDiagnostic));
0100     }
0101     setDiagnostics(diagnostics);
0102 }
0103 
0104 ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit)
0105 {
0106     const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString();
0107 
0108     auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption);
0109     setSeverity(severity);
0110 
0111     QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString();
0112     if (!diagnosticOption.isEmpty()) {
0113         description.append(QLatin1String(" [") + diagnosticOption + QLatin1Char(']'));
0114     }
0115     setDescription(prettyDiagnosticSpelling(description));
0116 
0117     ClangLocation location(clang_getDiagnosticLocation(diagnostic));
0118     CXFile diagnosticFile;
0119     clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr);
0120     const ClangString fileName(clang_getFileName(diagnosticFile));
0121     DocumentRange docRange(IndexedString(QUrl::fromLocalFile(fileName.toString()).adjusted(QUrl::NormalizePathSegments)), KTextEditor::Range(location, location));
0122     const uint numRanges = clang_getDiagnosticNumRanges(diagnostic);
0123     for (uint i = 0; i < numRanges; ++i) {
0124         auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange();
0125         // Note that the second condition is a workaround for seemingly wrong ranges that
0126         // were observed sometimes. In principle, such a range should be valid.
0127         if(!range.isValid() || (range.isEmpty() && range.start().line() == 0 && range.start().column() == 0)){
0128             continue;
0129         }
0130 
0131         if (range.start() < docRange.start()) {
0132             docRange.setStart(range.start());
0133         }
0134         if (range.end() > docRange.end()) {
0135             docRange.setEnd(range.end());
0136         }
0137     }
0138     if (docRange.isEmpty()) {
0139         // try to find a bigger range for the given location by using the token at the given location
0140         CXFile file = nullptr;
0141         unsigned line = 0;
0142         unsigned column = 0;
0143         clang_getExpansionLocation(location, &file, &line, &column, nullptr);
0144         // just skip ahead some characters, hoping that it's sufficient to encompass
0145         // a token we can use for building the range
0146         auto nextLocation = clang_getLocation(unit, file, line, column + 100);
0147         auto rangeToTokenize = clang_getRange(location, nextLocation);
0148         const ClangTokens tokens(unit, rangeToTokenize);
0149         if (tokens.size()) {
0150             docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange());
0151         }
0152     }
0153 
0154     setFixits(fixitsForDiagnostic(diagnostic, unit));
0155     setFinalLocation(docRange);
0156     setSource(IProblem::SemanticAnalysis);
0157 
0158     QVector<IProblem::Ptr> diagnostics;
0159     auto childDiagnostics = clang_getChildDiagnostics(diagnostic);
0160     auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics);
0161     diagnostics.reserve(numChildDiagnostics);
0162     for (uint j = 0; j < numChildDiagnostics; ++j) {
0163         auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j);
0164         ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit));
0165         diagnostics << ProblemPointer(problem.data());
0166     }
0167     setDiagnostics(diagnostics);
0168 }
0169 
0170 IAssistant::Ptr ClangProblem::solutionAssistant() const
0171 {
0172     if (allFixits().isEmpty()) {
0173         return {};
0174     }
0175 
0176     return IAssistant::Ptr(new ClangFixitAssistant(allFixits()));
0177 }
0178 
0179 ClangFixits ClangProblem::fixits() const
0180 {
0181     return m_fixits;
0182 }
0183 
0184 void ClangProblem::setFixits(const ClangFixits& fixits)
0185 {
0186     m_fixits = fixits;
0187 }
0188 
0189 ClangFixits ClangProblem::allFixits() const
0190 {
0191     ClangFixits result;
0192     result << m_fixits;
0193 
0194     const auto& diagnostics = this->diagnostics();
0195     for (const IProblem::Ptr& diagnostic : diagnostics) {
0196         const Ptr problem(dynamic_cast<ClangProblem*>(diagnostic.data()));
0197         Q_ASSERT(problem);
0198         result << problem->allFixits();
0199     }
0200     return result;
0201 }
0202 
0203 ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits)
0204     : m_title(i18n("Fix-it Hints"))
0205     , m_fixits(fixits)
0206 {
0207 }
0208 
0209 ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits)
0210     : m_title(title)
0211     , m_fixits(fixits)
0212 {
0213 }
0214 
0215 QString ClangFixitAssistant::title() const
0216 {
0217     return m_title;
0218 }
0219 
0220 void ClangFixitAssistant::createActions()
0221 {
0222     KDevelop::IAssistant::createActions();
0223 
0224     for (const ClangFixit& fixit : qAsConst(m_fixits)) {
0225         addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit)));
0226     }
0227 }
0228 
0229 ClangFixits ClangFixitAssistant::fixits() const
0230 {
0231     return m_fixits;
0232 }
0233 
0234 ClangFixitAction::ClangFixitAction(const ClangFixit& fixit)
0235     : m_fixit(fixit)
0236 {
0237 }
0238 
0239 QString ClangFixitAction::description() const
0240 {
0241     if (!m_fixit.description.isEmpty())
0242         return m_fixit.description;
0243 
0244     const auto range = m_fixit.range;
0245     if (range.start() == range.end()) {
0246         return i18n("Insert \"%1\" at line: %2, column: %3",
0247                     m_fixit.replacementText, range.start().line()+1, range.start().column()+1);
0248     } else if (range.start().line() == range.end().line()) {
0249         if (m_fixit.currentText.isEmpty()) {
0250             return i18n("Replace text at line: %1, column: %2 with: \"%3\"",
0251                     range.start().line()+1, range.start().column()+1, m_fixit.replacementText);
0252         } else
0253             return i18n("Replace \"%1\" with: \"%2\"",
0254                     m_fixit.currentText, m_fixit.replacementText);
0255     } else {
0256         return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"",
0257                     range.start().line()+1, range.start().column()+1, m_fixit.replacementText);
0258     }
0259 }
0260 
0261 void ClangFixitAction::execute()
0262 {
0263     DocumentChangeSet changes;
0264     {
0265         DUChainReadLocker lock;
0266 
0267         DocumentChange change(m_fixit.range.document, m_fixit.range,
0268                     m_fixit.currentText, m_fixit.replacementText);
0269         change.m_ignoreOldText = !m_fixit.currentText.isEmpty();
0270         changes.addChange(change);
0271     }
0272 
0273     changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange);
0274     changes.applyAllChanges();
0275     emit executed(this);
0276 }
0277 
0278 #include "moc_clangproblem.cpp"