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"