File indexing completed on 2024-04-21 15:24:29

0001 /*
0002     SPDX-FileCopyrightText: 2007 Piyush verma <piyush.verma@gmail.com>
0003     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
0004     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "phpparsejob.h"
0010 #include <QFile>
0011 #include <QReadWriteLock>
0012 
0013 #include <ktexteditor/document.h>
0014 
0015 #include <language/duchain/duchainlock.h>
0016 #include <language/duchain/duchain.h>
0017 #include <language/duchain/topducontext.h>
0018 #include <interfaces/icore.h>
0019 #include <interfaces/ilanguagecontroller.h>
0020 #include <language/backgroundparser/backgroundparser.h>
0021 #include <language/backgroundparser/urlparselock.h>
0022 #include <language/editor/documentrange.h>
0023 
0024 #include "editorintegrator.h"
0025 #include "parsesession.h"
0026 #include "phplanguagesupport.h"
0027 #include "phpdebugvisitor.h"
0028 #include "duchain/builders/declarationbuilder.h"
0029 #include "duchain/builders/usebuilder.h"
0030 #include "duchain/helper.h"
0031 #include "phpducontext.h"
0032 #include "phpdebug.h"
0033 
0034 #include <QReadLocker>
0035 #include <QThread>
0036 #include <language/duchain/duchainutils.h>
0037 
0038 #include <mutex>
0039 #include <memory>
0040 
0041 using namespace KDevelop;
0042 
0043 namespace Php
0044 {
0045 
0046 ParseJob::ParseJob(const IndexedString& url, ILanguageSupport* languageSupport)
0047 : KDevelop::ParseJob(url, languageSupport)
0048 , m_parentJob(nullptr)
0049 {
0050 }
0051 
0052 ParseJob::~ParseJob()
0053 {
0054 }
0055 
0056 LanguageSupport* ParseJob::php() const
0057 {
0058     return dynamic_cast<LanguageSupport*>(languageSupport());
0059 }
0060 
0061 void ParseJob::run(ThreadWeaver::JobPointer /*self*/, ThreadWeaver::Thread * /*thread*/)
0062 {
0063     if (document() != internalFunctionFile()) {
0064         // make sure we loaded the internal file already
0065         const auto &phpSupport = languageSupport();
0066         static std::once_flag once;
0067         std::call_once(once, [phpSupport] {
0068             qCDebug(PHP) << "Initializing internal function file" << internalFunctionFile();
0069             auto internalJob = std::unique_ptr<ParseJob>(static_cast<ParseJob*>(phpSupport->createParseJob(internalFunctionFile())));
0070             internalJob->run({}, nullptr);
0071             Q_ASSERT(internalJob->success());
0072         });
0073     }
0074 
0075     UrlParseLock urlLock(document());
0076 
0077     if (!(minimumFeatures() & Rescheduled) && !isUpdateRequired(phpLanguageString())) {
0078         return;
0079     }
0080     qCDebug(PHP) << "parsing" << document().str();
0081 
0082     KDevelop::ProblemPointer p = readContents();
0083     if (p) {
0084         //TODO: associate problem with topducontext
0085         return abortJob();;
0086     }
0087 
0088     ParseSession session;
0089     //TODO: support different charsets
0090     session.setContents(QString::fromUtf8(contents().contents));
0091     session.setCurrentDocument(document());
0092 
0093     // 2) parse
0094     StartAst* ast = nullptr;
0095     bool matched = session.parse(&ast);
0096 
0097     if (abortRequested() || ICore::self()->shuttingDown()) {
0098         return abortJob();
0099     }
0100 
0101     KDevelop::ReferencedTopDUContext toUpdate;
0102     {
0103         KDevelop::DUChainReadLocker duchainlock(KDevelop::DUChain::lock());
0104         toUpdate = KDevelop::DUChainUtils::standardContextForUrl(document().toUrl());
0105     }
0106 
0107     KDevelop::TopDUContext::Features newFeatures = minimumFeatures();
0108     if (toUpdate)
0109         newFeatures |= toUpdate->features();
0110 
0111     //Remove update-flags like 'Recursive' or 'ForceUpdate'
0112     newFeatures &= KDevelop::TopDUContext::AllDeclarationsContextsUsesAndAST;
0113 
0114     if (matched) {
0115         if (abortRequested()) {
0116             return abortJob();
0117         }
0118 
0119         EditorIntegrator editor(&session);
0120 
0121         QReadLocker parseLock(php()->parseLock());
0122 
0123         DeclarationBuilder builder(&editor);
0124         KDevelop::ReferencedTopDUContext chain = builder.build(document(), ast, toUpdate);
0125 
0126         if (abortRequested()) {
0127             return abortJob();
0128         }
0129 
0130         setDuChain(chain);
0131 
0132         bool hadUnresolvedIdentifiers = builder.hadUnresolvedIdentifiers();
0133 
0134         if ( newFeatures & TopDUContext::AllDeclarationsContextsAndUses
0135                 && document() != internalFunctionFile() )
0136         {
0137             UseBuilder useBuilder(&editor);
0138             useBuilder.buildUses(ast);
0139 
0140             if (useBuilder.hadUnresolvedIdentifiers())
0141                 hadUnresolvedIdentifiers = true;
0142         }
0143 
0144         if (hadUnresolvedIdentifiers) {
0145             if (!(minimumFeatures() & Rescheduled)) {
0146                 // Need to create new parse job with lower priority
0147                 qCDebug(PHP) << "Reschedule file " << document().str() << "for parsing";
0148                 KDevelop::TopDUContext::Features feat = static_cast<KDevelop::TopDUContext::Features>(
0149                         minimumFeatures() | KDevelop::TopDUContext::VisibleDeclarationsAndContexts | Rescheduled
0150                     );
0151                 int priority = qMin(parsePriority()+100, (int)KDevelop::BackgroundParser::WorstPriority);
0152                 KDevelop::ICore::self()->languageController()->backgroundParser()
0153                     ->addDocument(document(), feat, priority);
0154 
0155             } else {
0156                 // We haven't resolved all identifiers, but by now, we don't expect to
0157                 qCDebug(PHP) << "Builder found unresolved identifiers when they should have been resolved! (if there was no coding error)";
0158             }
0159         }
0160 
0161         if (abortRequested()) {
0162             return abortJob();
0163         }
0164 
0165         if (abortRequested()) {
0166             return abortJob();
0167         }
0168 
0169         {
0170             DUChainWriteLocker lock(DUChain::lock());
0171 
0172             foreach(const ProblemPointer &p, session.problems()) {
0173                 chain->addProblem(p);
0174             }
0175 
0176             chain->setFeatures(newFeatures);
0177             ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile();
0178             file->setModificationRevision(contents().modification);
0179             DUChain::self()->updateContextEnvironment( chain->topContext(), file.data() );
0180         }
0181 
0182         highlightDUChain();
0183     } else {
0184         ReferencedTopDUContext top;
0185         DUChainWriteLocker lock;
0186         {
0187             top = DUChain::self()->chainForDocument(document());
0188         }
0189         if (top) {
0190             ///NOTE: if we clear the imported parent contexts, autocompletion of built-in PHP stuff won't work!
0191             //top->clearImportedParentContexts();
0192             top->parsingEnvironmentFile()->clearModificationRevisions();
0193             top->clearProblems();
0194         } else {
0195             ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document());
0196             file->setLanguage(phpLanguageString());
0197             top = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file);
0198             DUChain::self()->addDocumentChain(top);
0199         }
0200         foreach(const ProblemPointer &p, session.problems()) {
0201             top->addProblem(p);
0202         }
0203         setDuChain(top);
0204         qCDebug(PHP) << "===Failed===" << document().str();
0205     }
0206 
0207     DUChain::self()->emitUpdateReady(document(), duChain());
0208 }
0209 
0210 void ParseJob::setParentJob(ParseJob *job)
0211 {
0212     m_parentJob = job;
0213 }
0214 
0215 
0216 bool ParseJob::hasParentDocument(const IndexedString &doc)
0217 {
0218     if (document() == doc) return true;
0219     if (!m_parentJob) return false;
0220     if (m_parentJob->document() == doc) return true;
0221     return m_parentJob->hasParentDocument(doc);
0222 }
0223 
0224 ProblemPointer ParseJob::createProblem(const QString &description, AstNode* node,
0225                                        EditorIntegrator * editor, IProblem::Source source,
0226                                        IProblem::Severity severity)
0227 {
0228     ProblemPointer p(new Problem());
0229     p->setSource(source);
0230     p->setSeverity(severity);
0231     p->setDescription(description);
0232     p->setFinalLocation(DocumentRange(document(), editor->findRange(node).castToSimpleRange()));
0233     qCDebug(PHP) << p->description();
0234     return p;
0235 }
0236 
0237 }
0238 
0239 #include "moc_phpparsejob.cpp"
0240 
0241 // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on