File indexing completed on 2024-04-28 12:23:20

0001 /* This file is part of KDevelop
0002  *
0003  * Copyright 2008-2010 Alexander Dymo <adymo@kdevelop.org>
0004  * Copyright (C) 2011-2015 Miquel Sabaté Solà <mikisabate@gmail.com>
0005  *
0006  * This program is free software; you can redistribute it and/or modify
0007  * it under the terms of the GNU Library General Public License as
0008  * published by the Free Software Foundation; either version 2 of the
0009  * License, or (at your option) any later version.
0010  *
0011  * This program is distributed in the hope that it will be useful,
0012  * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0014  * GNU General Public License for more details.
0015  *
0016  * You should have received a copy of the GNU General Public
0017  * License along with this program; if not, write to the
0018  * Free Software Foundation, Inc.,
0019  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0020  */
0021 
0022 #include <parsejob.h>
0023 
0024 #include <mutex>
0025 
0026 #include <QtCore/QReadLocker>
0027 
0028 #include <interfaces/icore.h>
0029 #include <interfaces/ilanguagecontroller.h>
0030 #include <language/backgroundparser/backgroundparser.h>
0031 #include <language/backgroundparser/urlparselock.h>
0032 #include <language/duchain/duchainutils.h>
0033 
0034 #include <debug.h>
0035 #include <duchain/builders/declarationbuilder.h>
0036 #include <duchain/builders/usebuilder.h>
0037 #include <duchain/editorintegrator.h>
0038 #include <duchain/helpers.h>
0039 #include <languagesupport.h>
0040 #include <parser/parser.h>
0041 
0042 using namespace KDevelop;
0043 namespace ruby {
0044 
0045 ParseJob::ParseJob(const IndexedString &url, ILanguageSupport *languageSupport)
0046     : KDevelop::ParseJob(url, languageSupport)
0047     , m_parser(nullptr)
0048     , m_duContext (nullptr)
0049 {
0050 }
0051 
0052 ParseJob::~ParseJob()
0053 {
0054     delete m_parser;
0055 }
0056 
0057 LanguageSupport * ParseJob::ruby() const
0058 {
0059     return dynamic_cast<LanguageSupport *>(languageSupport());
0060 }
0061 
0062 void ParseJob::run(ThreadWeaver::JobPointer, ThreadWeaver::Thread *)
0063 {
0064     // Make sure that the builtins file is already loaded.
0065     if (document() != builtinsFile()) {
0066         const auto &langSupport = languageSupport();
0067         static std::once_flag once;
0068 
0069         std::call_once(once, [langSupport] {
0070             qCDebug(KDEV_RUBY) << "Initializing internal function file" << builtinsFile();
0071             ParseJob internalJob(builtinsFile(), langSupport);
0072             internalJob.setMinimumFeatures(TopDUContext::AllDeclarationsAndContexts);
0073             internalJob.run({}, nullptr);
0074             Q_ASSERT(internalJob.success());
0075         });
0076     }
0077 
0078     UrlParseLock urlLock(document());
0079     if (!(minimumFeatures() & Rescheduled) &&
0080         !isUpdateRequired(languageString())) {
0081         return;
0082     }
0083 
0084     QReadLocker parseLock(ruby()->parseLock());
0085     ProblemPointer p = readContents();
0086     if (p || abortRequested()) {
0087         return abortJob();
0088     }
0089 
0090     // NOTE: Although the parser can retrieve the contents on its own,
0091     // it's better to use contents().contents because this way the contents
0092     // are converted in utf8 format always.
0093     m_parser = new Parser(document(), contents().contents);
0094     m_parser->setRubyVersion(ruby()->version());
0095     Ast *ast = m_parser->parse();
0096 
0097     /* Setting up the TopDUContext features */
0098     ReferencedTopDUContext toUpdate;
0099     {
0100         DUChainReadLocker lock;
0101         toUpdate = DUChainUtils::standardContextForUrl(document().toUrl());
0102     }
0103 
0104     TopDUContext::Features newFeatures = minimumFeatures();
0105     if (toUpdate) {
0106         newFeatures |= toUpdate->features();
0107     }
0108 
0109     /* Remove update-flags like 'Recursive' or 'ForceUpdate' */
0110     newFeatures &= TopDUContext::AllDeclarationsContextsUsesAndAST;
0111 
0112     // And finally we do all the work if parsing was successful. Otherwise,
0113     // we have to add a new problem
0114     if (ast) {
0115         // Empty document, do nothing
0116         if (!ast->tree) {
0117             return;
0118         }
0119         if (abortRequested()) {
0120             return abortJob();
0121         }
0122 
0123         EditorIntegrator editor;
0124         editor.setParseSession(m_parser);
0125         DeclarationBuilder builder(&editor);
0126         builder.setPriority(parsePriority());
0127         m_duContext = builder.build(editor.url(), ast, toUpdate);
0128 
0129         // Add warnings
0130         DUChainWriteLocker wlock;
0131         for (const ProblemPointer p : m_parser->problems) {
0132             m_duContext->addProblem(p);
0133         }
0134         wlock.unlock();
0135         setDuChain(m_duContext);
0136 
0137         if (abortRequested()) {
0138             return abortJob();
0139         }
0140 
0141         if (newFeatures & TopDUContext::AllDeclarationsContextsAndUses &&
0142             document() != builtinsFile()) {
0143 
0144             UseBuilder useBuilder(&editor);
0145             useBuilder.setPriority(parsePriority());
0146             useBuilder.buildUses(ast);
0147         }
0148 
0149         if (abortRequested()) {
0150             return abortJob();
0151         }
0152 
0153         const auto &unresolvedImports = builder.unresolvedImports();
0154         if (!unresolvedImports.isEmpty()) {
0155             // Check whether one of the imports is queued for parsing, this
0156             // is to avoid deadlocks
0157             bool dependencyInQueue = false;
0158             for (const IndexedString &url : unresolvedImports) {
0159                 dependencyInQueue = KDevelop::ICore::self()->
0160                     languageController()->backgroundParser()->isQueued(url);
0161                 if (dependencyInQueue) {
0162                     break;
0163                 }
0164             }
0165 
0166             // We check whether this document already has been re-scheduled
0167             // once and abort if that is the case. This prevents infinite
0168             // loops in case something goes wrong (optimally, shouldn't reach
0169             // here if the document was already rescheduled, but there's many
0170             // cases where this might still happen)
0171             if (!(minimumFeatures() & Rescheduled) && dependencyInQueue) {
0172                 wlock.lock();
0173                 constexpr TopDUContext::Features features{TopDUContext::ForceUpdate};
0174                 ICore::self()->languageController()->backgroundParser()->addDocument(
0175                     document(),
0176                     static_cast<TopDUContext::Features>(features | Rescheduled),
0177                     parsePriority(),
0178                     nullptr,
0179                     ParseJob::FullSequentialProcessing
0180                 );
0181                 wlock.unlock();
0182             }
0183         }
0184 
0185         if (abortRequested()) {
0186             return abortJob();
0187         }
0188 
0189         wlock.lock();
0190         m_duContext->setFeatures(newFeatures);
0191         ParsingEnvironmentFilePointer file = m_duContext->parsingEnvironmentFile();
0192         file->setModificationRevision(contents().modification);
0193         DUChain::self()->updateContextEnvironment(m_duContext, file.data());
0194         wlock.unlock();
0195 
0196         highlightDUChain();
0197         qCDebug(KDEV_RUBY) << "**** Parsing Succeeded ****";
0198     } else {
0199         qCWarning(KDEV_RUBY) << "**** Parsing Failed ****";
0200         DUChainWriteLocker lock;
0201         m_duContext = DUChain::self()->chainForDocument(document());
0202         if (m_duContext) {
0203             m_duContext->parsingEnvironmentFile()->clearModificationRevisions();
0204             m_duContext->clearProblems();
0205         } else {
0206             ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document());
0207             file->setLanguage(languageString());
0208             m_duContext = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file);
0209             DUChain::self()->addDocumentChain(m_duContext);
0210         }
0211 
0212         for (const ProblemPointer p : m_parser->problems) {
0213             qCDebug(KDEV_RUBY) << "Added problem to context";
0214             m_duContext->addProblem(p);
0215         }
0216         setDuChain(m_duContext);
0217     }
0218     DUChain::self()->emitUpdateReady(document(), duChain());
0219 }
0220 
0221 }
0222