File indexing completed on 2024-05-12 04:37:43

0001 /*
0002     SPDX-FileCopyrightText: 2006 Adam Treat <treat@kde.org>
0003     SPDX-FileCopyrightText: 2006-2008 Hamish Rodda <rodda@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "parsejob.h"
0009 
0010 #include <QFile>
0011 #include <QMutex>
0012 #include <QMutexLocker>
0013 #include <QStandardPaths>
0014 
0015 #include <KLocalizedString>
0016 #include <KFormat>
0017 #include <KTextEditor/MovingInterface>
0018 
0019 #include "backgroundparser.h"
0020 #include <debug.h>
0021 #include "duchain/topducontext.h"
0022 
0023 #include "duchain/duchainlock.h"
0024 #include "duchain/duchain.h"
0025 #include "duchain/parsingenvironment.h"
0026 #include "editor/documentrange.h"
0027 
0028 #include <util/foregroundlock.h>
0029 #include <util/kdevstringhandler.h>
0030 #include <interfaces/icore.h>
0031 #include <interfaces/ilanguagecontroller.h>
0032 #include <codegen/coderepresentation.h>
0033 #include <duchain/declaration.h>
0034 #include <duchain/use.h>
0035 #include <interfaces/icodehighlighting.h>
0036 #include <duchain/problem.h>
0037 
0038 using namespace KTextEditor;
0039 
0040 static QMutex minimumFeaturesMutex;
0041 static QHash<KDevelop::IndexedString, QList<KDevelop::TopDUContext::Features>> staticMinimumFeatures;
0042 
0043 namespace KDevelop {
0044 class ParseJobPrivate
0045 {
0046 public:
0047 
0048     ParseJobPrivate(const IndexedString& url_, ILanguageSupport* languageSupport_) :
0049         url(url_)
0050         , languageSupport(languageSupport_)
0051         , abortRequested(0)
0052         , hasReadContents(false)
0053         , aborted(false)
0054         , features(TopDUContext::VisibleDeclarationsAndContexts)
0055         , parsePriority(0)
0056         , sequentialProcessingFlags(ParseJob::IgnoresSequentialProcessing)
0057         , maximumFileSize(5 * 1024 * 1024) // 5 MB
0058     {
0059     }
0060 
0061     ~ParseJobPrivate()
0062     {
0063     }
0064 
0065     ReferencedTopDUContext duContext;
0066 
0067     IndexedString url;
0068     ILanguageSupport* languageSupport;
0069 
0070     ParseJob::Contents contents;
0071 
0072     QAtomicInt abortRequested;
0073 
0074     bool hasReadContents : 1;
0075     bool aborted : 1;
0076     TopDUContext::Features features;
0077     QVector<QPointer<QObject>> notify;
0078     QPointer<DocumentChangeTracker> tracker;
0079     RevisionReference revision;
0080     RevisionReference previousRevision;
0081 
0082     int parsePriority;
0083     ParseJob::SequentialProcessingFlags sequentialProcessingFlags;
0084     qint64 maximumFileSize;
0085 };
0086 
0087 ParseJob::ParseJob(const IndexedString& url, KDevelop::ILanguageSupport* languageSupport)
0088     : ThreadWeaver::Sequence()
0089     , d_ptr(new ParseJobPrivate(url, languageSupport))
0090 {
0091 }
0092 
0093 ParseJob::~ParseJob()
0094 {
0095     Q_D(ParseJob);
0096 
0097     for (auto& p : qAsConst(d->notify)) {
0098         if (p) {
0099             QMetaObject::invokeMethod(p.data(), "updateReady", Qt::QueuedConnection,
0100                                       Q_ARG(KDevelop::IndexedString, d->url),
0101                                       Q_ARG(KDevelop::ReferencedTopDUContext, d->duContext));
0102         }
0103     }
0104 }
0105 
0106 ILanguageSupport* ParseJob::languageSupport() const
0107 {
0108     Q_D(const ParseJob);
0109 
0110     return d->languageSupport;
0111 }
0112 
0113 void ParseJob::setParsePriority(int priority)
0114 {
0115     Q_D(ParseJob);
0116 
0117     d->parsePriority = priority;
0118 }
0119 
0120 int ParseJob::parsePriority() const
0121 {
0122     Q_D(const ParseJob);
0123 
0124     return d->parsePriority;
0125 }
0126 
0127 bool ParseJob::requiresSequentialProcessing() const
0128 {
0129     Q_D(const ParseJob);
0130 
0131     return d->sequentialProcessingFlags & RequiresSequentialProcessing;
0132 }
0133 
0134 bool ParseJob::respectsSequentialProcessing() const
0135 {
0136     Q_D(const ParseJob);
0137 
0138     return d->sequentialProcessingFlags & RespectsSequentialProcessing;
0139 }
0140 
0141 void ParseJob::setSequentialProcessingFlags(SequentialProcessingFlags flags)
0142 {
0143     Q_D(ParseJob);
0144 
0145     d->sequentialProcessingFlags = flags;
0146 }
0147 
0148 qint64 ParseJob::maximumFileSize() const
0149 {
0150     Q_D(const ParseJob);
0151 
0152     return d->maximumFileSize;
0153 }
0154 
0155 void ParseJob::setMaximumFileSize(qint64 value)
0156 {
0157     Q_D(ParseJob);
0158 
0159     d->maximumFileSize = value;
0160 }
0161 
0162 IndexedString ParseJob::document() const
0163 {
0164     Q_D(const ParseJob);
0165 
0166     return d->url;
0167 }
0168 
0169 bool ParseJob::success() const
0170 {
0171     Q_D(const ParseJob);
0172 
0173     return !d->aborted;
0174 }
0175 
0176 void ParseJob::setMinimumFeatures(TopDUContext::Features features)
0177 {
0178     Q_D(ParseJob);
0179 
0180     d->features = features;
0181 }
0182 
0183 bool ParseJob::hasStaticMinimumFeatures()
0184 {
0185     QMutexLocker lock(&minimumFeaturesMutex);
0186     return !::staticMinimumFeatures.isEmpty();
0187 }
0188 
0189 TopDUContext::Features ParseJob::staticMinimumFeatures(const IndexedString& url)
0190 {
0191     QMutexLocker lock(&minimumFeaturesMutex);
0192     TopDUContext::Features features{};
0193 
0194     const auto featuresIt = ::staticMinimumFeatures.constFind(url);
0195     if (featuresIt != ::staticMinimumFeatures.constEnd())
0196         for (const TopDUContext::Features f : *featuresIt)
0197             features |= f;
0198 
0199     return features;
0200 }
0201 
0202 TopDUContext::Features ParseJob::minimumFeatures() const
0203 {
0204     Q_D(const ParseJob);
0205 
0206     return d->features | staticMinimumFeatures(d->url);
0207 }
0208 
0209 void ParseJob::setDuChain(const ReferencedTopDUContext& duChain)
0210 {
0211     Q_D(ParseJob);
0212 
0213     d->duContext = duChain;
0214 }
0215 
0216 ReferencedTopDUContext ParseJob::duChain() const
0217 {
0218     Q_D(const ParseJob);
0219 
0220     return d->duContext;
0221 }
0222 
0223 bool ParseJob::abortRequested() const
0224 {
0225     Q_D(const ParseJob);
0226     return d->abortRequested.loadRelaxed() != 0;
0227 }
0228 
0229 void ParseJob::requestAbort()
0230 {
0231     Q_D(ParseJob);
0232 
0233     d->abortRequested = 1;
0234 }
0235 
0236 void ParseJob::abortJob()
0237 {
0238     Q_D(ParseJob);
0239 
0240     d->aborted = true;
0241     setStatus(Status_Aborted);
0242 }
0243 
0244 void ParseJob::setNotifyWhenReady(const QVector<QPointer<QObject>>& notify)
0245 {
0246     Q_D(ParseJob);
0247 
0248     d->notify = notify;
0249 }
0250 
0251 void ParseJob::setStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features)
0252 {
0253     QMutexLocker lock(&minimumFeaturesMutex);
0254     ::staticMinimumFeatures[url].append(features);
0255 }
0256 
0257 void ParseJob::unsetStaticMinimumFeatures(const IndexedString& url, TopDUContext::Features features)
0258 {
0259     QMutexLocker lock(&minimumFeaturesMutex);
0260     ::staticMinimumFeatures[url].removeOne(features);
0261     if (::staticMinimumFeatures[url].isEmpty())
0262         ::staticMinimumFeatures.remove(url);
0263 }
0264 
0265 KDevelop::ProblemPointer ParseJob::readContents()
0266 {
0267     Q_D(ParseJob);
0268 
0269     Q_ASSERT(!d->hasReadContents);
0270     d->hasReadContents = true;
0271 
0272     QString localFile(document().toUrl().toLocalFile());
0273     QFileInfo fileInfo(localFile);
0274 
0275     QDateTime lastModified = fileInfo.lastModified();
0276 
0277     d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document());
0278 
0279     //Try using an artificial code-representation, which overrides everything else
0280     if (artificialCodeRepresentationExists(document())) {
0281         CodeRepresentation::Ptr repr = createCodeRepresentation(document());
0282         d->contents.contents = repr->text().toUtf8();
0283         qCDebug(LANGUAGE) << "took contents for " << document().str() << " from artificial code-representation";
0284         return KDevelop::ProblemPointer();
0285     }
0286 
0287     bool hadTracker = false;
0288     if (d->tracker) {
0289         ForegroundLock lock;
0290         if (DocumentChangeTracker* t = d->tracker.data()) {
0291             // The file is open in an editor
0292             d->previousRevision = t->revisionAtLastReset();
0293 
0294             t->reset(); // Reset the tracker to the current revision
0295             Q_ASSERT(t->revisionAtLastReset());
0296 
0297             d->contents.contents = t->document()->text().toUtf8();
0298             d->contents.modification =
0299                 KDevelop::ModificationRevision(lastModified, t->revisionAtLastReset()->revision());
0300 
0301             d->revision = t->acquireRevision(d->contents.modification.revision);
0302             hadTracker = true;
0303         }
0304     }
0305     if (!hadTracker) {
0306         // We have to load the file from disk
0307 
0308         if (fileInfo.size() > d->maximumFileSize) {
0309             KFormat f;
0310 
0311             KDevelop::ProblemPointer p(new Problem());
0312             p->setSource(IProblem::Disk);
0313             p->setDescription(i18nc("%1: filename", "Skipped file that is too large: '%1'", localFile));
0314             p->setExplanation(i18nc("%1: file size, %2: limit file size",
0315                                     "The file is %1 and exceeds the limit of %2.",
0316                                     f.formatByteSize(fileInfo.size()),
0317                                     f.formatByteSize(d->maximumFileSize)));
0318             p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid()));
0319             qCWarning(LANGUAGE) << p->description() << p->explanation();
0320             return p;
0321         }
0322         QFile file(localFile);
0323 
0324         if (!file.open(QIODevice::ReadOnly)) {
0325             KDevelop::ProblemPointer p(new Problem());
0326             p->setSource(IProblem::Disk);
0327             p->setDescription(i18n("Could not open file '%1'", localFile));
0328             switch (file.error()) {
0329             case QFile::ReadError:
0330                 p->setExplanation(i18n("File could not be read from disk."));
0331                 break;
0332             case QFile::OpenError:
0333                 p->setExplanation(i18n("File could not be opened."));
0334                 break;
0335             case QFile::PermissionsError:
0336                 p->setExplanation(i18n("File could not be read from disk due to permissions."));
0337                 break;
0338             default:
0339                 break;
0340             }
0341             p->setFinalLocation(DocumentRange(document(), KTextEditor::Range::invalid()));
0342 
0343             qCWarning(LANGUAGE) << "Could not open file" << document().str() << "(path" << localFile << ")";
0344 
0345             return p;
0346         }
0347 
0348         d->contents.contents = file.readAll(); ///@todo Convert from local encoding to utf-8 if they don't match
0349 
0350         // This is consistent with KTextEditor::Document::text() as used for already-open files.
0351         normalizeLineEndings(d->contents.contents);
0352         d->contents.modification = KDevelop::ModificationRevision(lastModified);
0353 
0354         file.close();
0355     }
0356 
0357     return KDevelop::ProblemPointer();
0358 }
0359 
0360 const KDevelop::ParseJob::Contents& ParseJob::contents() const
0361 {
0362     Q_D(const ParseJob);
0363 
0364     Q_ASSERT(d->hasReadContents);
0365     return d->contents;
0366 }
0367 
0368 struct MovingRangeTranslator
0369     : public DUChainVisitor
0370 {
0371     MovingRangeTranslator(qint64 _source, qint64 _target, MovingInterface* _moving) : source(_source)
0372         , target(_target)
0373         , moving(_moving)
0374     {
0375     }
0376 
0377     void visit(DUContext* context) override
0378     {
0379         translateRange(context);
0380         ///@todo Also map import-positions
0381         // Translate uses
0382         uint usesCount = context->usesCount();
0383         for (uint u = 0; u < usesCount; ++u) {
0384             RangeInRevision r = context->uses()[u].m_range;
0385             translateRange(r);
0386             context->changeUseRange(u, r);
0387         }
0388     }
0389 
0390     void visit(Declaration* declaration) override
0391     {
0392         translateRange(declaration);
0393     }
0394 
0395     void translateRange(DUChainBase* object)
0396     {
0397         RangeInRevision r = object->range();
0398         translateRange(r);
0399         object->setRange(r);
0400     }
0401 
0402     void translateRange(RangeInRevision& r)
0403     {
0404         // PHP and python use top contexts that start at (0, 0) end at INT_MAX, so make sure that doesn't overflow
0405         // or translate the start of the top context away from (0, 0)
0406         if (r.start.line != 0 || r.start.column != 0) {
0407             moving->transformCursor(r.start.line, r.start.column, MovingCursor::MoveOnInsert, source, target);
0408         }
0409         if (r.end.line != std::numeric_limits<int>::max() || r.end.column != std::numeric_limits<int>::max()) {
0410             moving->transformCursor(r.end.line, r.end.column, MovingCursor::StayOnInsert, source, target);
0411         }
0412     }
0413 
0414     KTextEditor::Range range;
0415     qint64 source;
0416     qint64 target;
0417     MovingInterface* moving;
0418 };
0419 
0420 void ParseJob::translateDUChainToRevision(TopDUContext* context)
0421 {
0422     Q_D(ParseJob);
0423 
0424     qint64 targetRevision = d->contents.modification.revision;
0425 
0426     if (targetRevision == -1) {
0427         qCDebug(LANGUAGE) << "invalid target revision" << targetRevision;
0428         return;
0429     }
0430 
0431     qint64 sourceRevision;
0432 
0433     {
0434         DUChainReadLocker duChainLock;
0435 
0436         Q_ASSERT(context->parsingEnvironmentFile());
0437 
0438         // Cannot map if there is no source revision
0439         sourceRevision = context->parsingEnvironmentFile()->modificationRevision().revision;
0440 
0441         if (sourceRevision == -1) {
0442             qCDebug(LANGUAGE) << "invalid source revision" << sourceRevision;
0443             return;
0444         }
0445     }
0446 
0447     if (sourceRevision > targetRevision) {
0448         qCDebug(LANGUAGE) << "for document" << document().str() <<
0449             ": source revision is higher than target revision:" << sourceRevision << " > " << targetRevision;
0450         return;
0451     }
0452 
0453     ForegroundLock lock;
0454     if (DocumentChangeTracker* t = d->tracker.data()) {
0455         if (!d->previousRevision) {
0456             qCDebug(LANGUAGE) << "not translating because there is no valid predecessor-revision";
0457             return;
0458         }
0459 
0460         if (sourceRevision != d->previousRevision->revision() || !d->previousRevision->valid()) {
0461             qCDebug(LANGUAGE) <<
0462                 "not translating because the document revision does not match the tracker start revision (maybe the document was cleared)";
0463             return;
0464         }
0465 
0466         if (!t->holdingRevision(sourceRevision) || !t->holdingRevision(targetRevision)) {
0467             qCDebug(LANGUAGE) << "lost one of the translation revisions, not doing the map";
0468             return;
0469         }
0470 
0471         // Perform translation
0472         MovingInterface* moving = t->documentMovingInterface();
0473 
0474         DUChainWriteLocker wLock;
0475 
0476         MovingRangeTranslator translator(sourceRevision, targetRevision, moving);
0477         context->visit(translator);
0478 
0479         const QList<ProblemPointer> problems = context->problems();
0480         for (auto& problem : problems) {
0481             RangeInRevision r = problem->range();
0482             translator.translateRange(r);
0483             problem->setRange(r);
0484         }
0485 
0486         // Update the modification revision in the meta-data
0487         ModificationRevision modRev = context->parsingEnvironmentFile()->modificationRevision();
0488         modRev.revision = targetRevision;
0489         context->parsingEnvironmentFile()->setModificationRevision(modRev);
0490     }
0491 }
0492 
0493 bool ParseJob::isUpdateRequired(const IndexedString& languageString)
0494 {
0495     if (abortRequested()) {
0496         return false;
0497     }
0498 
0499     if (minimumFeatures() & TopDUContext::ForceUpdate) {
0500         return true;
0501     }
0502 
0503     DUChainReadLocker lock;
0504     if (abortRequested()) {
0505         return false;
0506     }
0507     const auto files = DUChain::self()->allEnvironmentFiles(document());
0508     for (const ParsingEnvironmentFilePointer& file : files) {
0509         if (file->language() != languageString) {
0510             continue;
0511         }
0512         if (!file->needsUpdate(environment()) && file->featuresSatisfied(minimumFeatures())) {
0513             qCDebug(LANGUAGE) << "Already up to date" << document().str();
0514             setDuChain(file->topContext());
0515             lock.unlock();
0516             highlightDUChain();
0517             return false;
0518         }
0519         break;
0520     }
0521 
0522     return !abortRequested();
0523 }
0524 
0525 const ParsingEnvironment* ParseJob::environment() const
0526 {
0527     return nullptr;
0528 }
0529 
0530 void ParseJob::highlightDUChain()
0531 {
0532     Q_D(ParseJob);
0533 
0534     ENSURE_CHAIN_NOT_LOCKED
0535     if (!d->languageSupport->codeHighlighting() || !duChain() || abortRequested()) {
0536         // language doesn't support highlighting
0537         return;
0538     }
0539     if (!d->hasReadContents && !d->tracker) {
0540         d->tracker = ICore::self()->languageController()->backgroundParser()->trackerForUrl(document());
0541     }
0542     if (d->tracker) {
0543         d->languageSupport->codeHighlighting()->highlightDUChain(duChain());
0544     }
0545 }
0546 
0547 ControlFlowGraph* ParseJob::controlFlowGraph()
0548 {
0549     return nullptr;
0550 }
0551 
0552 DataAccessRepository* ParseJob::dataAccessInformation()
0553 {
0554     return nullptr;
0555 }
0556 
0557 bool ParseJob::hasTracker() const
0558 {
0559     Q_D(const ParseJob);
0560 
0561     return d->tracker;
0562 }
0563 }
0564 
0565 #include "moc_parsejob.cpp"