1 /*
2     SPDX-FileCopyrightText: 2007 Piyush verma <piyush.verma@gmail.com>
3     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
4     SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de>
5 
6     SPDX-License-Identifier: GPL-2.0-or-later
7 */
8 
9 #include "phpparsejob.h"
10 #include <QFile>
11 #include <QReadWriteLock>
12 
13 #include <ktexteditor/document.h>
14 
15 #include <kdebug.h>
16 #include <klocale.h>
17 
18 #include <language/duchain/duchainlock.h>
19 #include <language/duchain/duchain.h>
20 #include <language/duchain/topducontext.h>
21 #include <language/duchain/dumpchain.h>
22 #include <interfaces/ilanguage.h>
23 #include <interfaces/icore.h>
24 #include <interfaces/ilanguagecontroller.h>
25 #include <language/backgroundparser/backgroundparser.h>
26 #include <language/backgroundparser/urlparselock.h>
27 
28 #include "editorintegrator.h"
29 #include "parsesession.h"
30 #include "phplanguagesupport.h"
31 #include "phpdebugvisitor.h"
32 #include "duchain/builders/declarationbuilder.h"
33 #include "duchain/builders/usebuilder.h"
34 #include "duchain/helper.h"
35 #include "phpducontext.h"
36 
37 #include <QtCore/QReadLocker>
38 #include <QtCore/QThread>
39 #include <language/duchain/duchainutils.h>
40 
41 using namespace KDevelop;
42 
43 namespace Php
44 {
45 
ParseJob(const IndexedString & url,ILanguageSupport * languageSupport)46 ParseJob::ParseJob(const IndexedString& url, ILanguageSupport* languageSupport)
47 : KDevelop::ParseJob(url, languageSupport)
48 , m_parentJob(0)
49 {
50 }
51 
~ParseJob()52 ParseJob::~ParseJob()
53 {
54 }
55 
php() const56 LanguageSupport* ParseJob::php() const
57 {
58     return dynamic_cast<LanguageSupport*>(languageSupport());
59 }
60 
run()61 void ParseJob::run()
62 {
63     /// Indexed string for 'Php', identifies environment files from this language plugin
64     static const IndexedString phpLangString("Php");
65 
66     // make sure we loaded the internal file already
67     if ( !php()->internalFunctionsLoaded() && !m_parentJob && document() != internalFunctionFile() ) {
68         kDebug() << "waiting for internal function file to finish parsing";
69         QReadLocker(php()->internalFunctionsLock());
70     }
71 
72     UrlParseLock urlLock(document());
73 
74     if (!(minimumFeatures() & Resheduled) && !isUpdateRequired(phpLangString)) {
75         return;
76     }
77     kDebug() << "parsing" << document().str();
78 
79     KDevelop::ProblemPointer p = readContents();
80     if (p) {
81         //TODO: associate problem with topducontext
82         return abortJob();;
83     }
84 
85     ParseSession session;
86     //TODO: support different charsets
87     session.setContents(QString::fromUtf8(contents().contents));
88     session.setCurrentDocument(document());
89 
90     // 2) parse
91     StartAst* ast = 0;
92     bool matched = session.parse(&ast);
93 
94     if (abortRequested() || ICore::self()->shuttingDown()) {
95         return abortJob();
96     }
97 
98     KDevelop::ReferencedTopDUContext toUpdate;
99     {
100         KDevelop::DUChainReadLocker duchainlock(KDevelop::DUChain::lock());
101         toUpdate = KDevelop::DUChainUtils::standardContextForUrl(document().toUrl());
102     }
103 
104     KDevelop::TopDUContext::Features newFeatures = minimumFeatures();
105     if (toUpdate)
106         newFeatures = (KDevelop::TopDUContext::Features)(newFeatures | toUpdate->features());
107 
108     //Remove update-flags like 'Recursive' or 'ForceUpdate'
109     newFeatures = static_cast<KDevelop::TopDUContext::Features>(newFeatures & KDevelop::TopDUContext::AllDeclarationsContextsUsesAndAST);
110 
111     if (matched) {
112         if (abortRequested()) {
113             return abortJob();
114         }
115 
116         EditorIntegrator editor(&session);
117 
118         QReadLocker parseLock(php()->language()->parseLock());
119 
120         DeclarationBuilder builder(&editor);
121         KDevelop::ReferencedTopDUContext chain = builder.build(document(), ast, toUpdate);
122 
123         if (abortRequested()) {
124             return abortJob();
125         }
126 
127         setDuChain(chain);
128 
129         bool hadUnresolvedIdentifiers = builder.hadUnresolvedIdentifiers();
130 
131         if ( newFeatures & TopDUContext::AllDeclarationsContextsAndUses
132                 && document() != internalFunctionFile() )
133         {
134             UseBuilder useBuilder(&editor);
135             useBuilder.buildUses(ast);
136 
137             if (useBuilder.hadUnresolvedIdentifiers())
138                 hadUnresolvedIdentifiers = true;
139         }
140 
141         if (hadUnresolvedIdentifiers) {
142             if (!(minimumFeatures() & Resheduled) && KDevelop::ICore::self()->languageController()->backgroundParser()->queuedCount()) {
143                 // Need to create new parse job with lower priority
144                 kDebug() << "Reschedule file " << document().str() << "for parsing";
145                 KDevelop::TopDUContext::Features feat = static_cast<KDevelop::TopDUContext::Features>(
146                         minimumFeatures() | KDevelop::TopDUContext::VisibleDeclarationsAndContexts | Resheduled
147                     );
148                 int priority = qMin(parsePriority()+100, (int)KDevelop::BackgroundParser::WorstPriority);
149                 KDevelop::ICore::self()->languageController()->backgroundParser()
150                     ->addDocument(document(), feat, priority);
151 
152             } else {
153                 // We haven't resolved all identifiers, but by now, we don't expect to
154                 kDebug() << "Builder found unresolved identifiers when they should have been resolved! (if there was no coding error)";
155             }
156         }
157 
158         if (abortRequested()) {
159             return abortJob();
160         }
161 
162         if (abortRequested()) {
163             return abortJob();
164         }
165 
166         {
167             DUChainWriteLocker lock(DUChain::lock());
168 
169             foreach(const ProblemPointer &p, session.problems()) {
170                 chain->addProblem(p);
171             }
172 
173             chain->setFeatures(newFeatures);
174             ParsingEnvironmentFilePointer file = chain->parsingEnvironmentFile();
175             file->setModificationRevision(contents().modification);
176             DUChain::self()->updateContextEnvironment( chain->topContext(), file.data() );
177         }
178 
179         highlightDUChain();
180     } else {
181         ReferencedTopDUContext top;
182         DUChainWriteLocker lock;
183         {
184             top = DUChain::self()->chainForDocument(document());
185         }
186         if (top) {
187             ///NOTE: if we clear the imported parent contexts, autocompletion of built-in PHP stuff won't work!
188             //top->clearImportedParentContexts();
189             top->parsingEnvironmentFile()->clearModificationRevisions();
190             top->clearProblems();
191         } else {
192             ParsingEnvironmentFile *file = new ParsingEnvironmentFile(document());
193             file->setLanguage(phpLangString);
194             top = new TopDUContext(document(), RangeInRevision(0, 0, INT_MAX, INT_MAX), file);
195             DUChain::self()->addDocumentChain(top);
196         }
197         foreach(const ProblemPointer &p, session.problems()) {
198             top->addProblem(p);
199         }
200         setDuChain(top);
201         kDebug() << "===Failed===" << document().str();
202     }
203 }
204 
setParentJob(ParseJob * job)205 void ParseJob::setParentJob(ParseJob *job)
206 {
207     m_parentJob = job;
208 }
209 
210 
hasParentDocument(const IndexedString & doc)211 bool ParseJob::hasParentDocument(const IndexedString &doc)
212 {
213     if (document() == doc) return true;
214     if (!m_parentJob) return false;
215     if (m_parentJob->document() == doc) return true;
216     return m_parentJob->hasParentDocument(doc);
217 }
218 
createProblem(const QString & description,AstNode * node,EditorIntegrator * editor,ProblemData::Source source,ProblemData::Severity severity)219 ProblemPointer ParseJob::createProblem(const QString &description, AstNode* node,
220                                        EditorIntegrator * editor, ProblemData::Source source,
221                                        ProblemData::Severity severity)
222 {
223     ProblemPointer p(new Problem());
224     p->setSource(source);
225     p->setSeverity(severity);
226     p->setDescription(description);
227     p->setFinalLocation(DocumentRange(document(), editor->findRange(node).castToSimpleRange()));
228     kDebug() << p->description();
229     return p;
230 }
231 
232 }
233 
234 #include "phpparsejob.moc"
235 // kate: space-indent on; indent-width 4; tab-width 4; replace-tabs on; auto-insert-doxygen on
236