1 /*
2     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "contextbuilder.h"
8 
9 #include <KLocalizedString>
10 
11 #include <language/duchain/duchain.h>
12 #include <language/duchain/topducontext.h>
13 #include <language/duchain/duchainlock.h>
14 #include <language/duchain/declaration.h>
15 #include <language/duchain/classdeclaration.h>
16 #include <language/editor/documentrange.h>
17 
18 #include <interfaces/icore.h>
19 #include <interfaces/ilanguagecontroller.h>
20 #include <interfaces/icompletionsettings.h>
21 
22 #include "../editorintegrator.h"
23 #include "../helper.h"
24 #include "../phpducontext.h"
25 
26 #include "../parser/parsesession.h"
27 #include "../parser/phpast.h"
28 #include <duchaindebug.h>
29 
30 using namespace KDevelop;
31 
32 namespace Php
33 {
34 
ContextBuilder()35 ContextBuilder::ContextBuilder()
36     : m_isInternalFunctions(false), m_reportErrors(true),
37       m_mapAst(false), m_hadUnresolvedIdentifiers(false),
38       m_editor(nullptr), m_openNamespaces(nullptr)
39 {
40 }
41 
~ContextBuilder()42 ContextBuilder::~ContextBuilder()
43 {
44 }
45 
editor() const46 EditorIntegrator* ContextBuilder::editor() const
47 {
48     return m_editor;
49 }
50 
build(const IndexedString & url,AstNode * node,const ReferencedTopDUContext & updateContext_)51 ReferencedTopDUContext ContextBuilder::build(const IndexedString& url, AstNode* node,
52                                              const ReferencedTopDUContext& updateContext_)
53 {
54     ReferencedTopDUContext updateContext(updateContext_);
55     m_isInternalFunctions = url == internalFunctionFile();
56     if ( m_isInternalFunctions ) {
57         m_reportErrors = false;
58     } else if ( ICore::self() ) {
59         m_reportErrors = ICore::self()->languageController()->completionSettings()->highlightSemanticProblems();
60     }
61 
62     if (!updateContext) {
63         DUChainReadLocker lock(DUChain::lock());
64         updateContext = DUChain::self()->chainForDocument(url);
65     }
66     if (updateContext) {
67         qCDebug(DUCHAIN) << "re-compiling" << url.str();
68         DUChainWriteLocker lock(DUChain::lock());
69         updateContext->clearImportedParentContexts();
70         updateContext->parsingEnvironmentFile()->clearModificationRevisions();
71         updateContext->clearProblems();
72         updateContext->updateImportsCache();
73     } else {
74         qCDebug(DUCHAIN) << "compiling" << url.str();
75     }
76     ReferencedTopDUContext top = ContextBuilderBase::build(url, node, updateContext);
77 
78     {
79         DUChainWriteLocker lock(DUChain::lock());
80         top->updateImportsCache();
81     }
82 
83     return top;
84 }
85 
hadUnresolvedIdentifiers() const86 bool ContextBuilder::hadUnresolvedIdentifiers() const
87 {
88     return m_hadUnresolvedIdentifiers;
89 }
90 
91 
startVisiting(AstNode * node)92 void ContextBuilder::startVisiting(AstNode* node)
93 {
94     if (compilingContexts()) {
95         TopDUContext* top = dynamic_cast<TopDUContext*>(currentContext());
96         Q_ASSERT(top);
97         {
98             DUChainWriteLocker lock(DUChain::lock());
99             top->updateImportsCache(); //Mark that we will use a cached import-structure
100         }
101 
102         bool hasImports;
103         {
104             DUChainReadLocker lock(DUChain::lock());
105             hasImports = !top->importedParentContexts().isEmpty();
106         }
107         if (!hasImports && top->url() != internalFunctionFile()) {
108             DUChainWriteLocker lock(DUChain::lock());
109             TopDUContext* import = DUChain::self()->chainForDocument(internalFunctionFile());
110             if (!import) {
111                 qWarning() << "importing internalFunctions failed" << currentContext()->url().str();
112                 Q_ASSERT(false);
113             } else {
114                 top->addImportedParentContext(import);
115                 top->updateImportsCache();
116             }
117         }
118 
119     }
120     visitNode(node);
121     if (m_openNamespaces) {
122         closeNamespaces(m_openNamespaces);
123         m_openNamespaces = nullptr;
124     }
125 }
126 
newContext(const RangeInRevision & range)127 DUContext* ContextBuilder::newContext(const RangeInRevision& range)
128 {
129     return new PhpDUContext<DUContext>(range, currentContext());
130 }
131 
newTopContext(const RangeInRevision & range,ParsingEnvironmentFile * file)132 TopDUContext* ContextBuilder::newTopContext(const RangeInRevision& range, ParsingEnvironmentFile* file)
133 {
134     if (!file) {
135         file = new ParsingEnvironmentFile(m_editor->parseSession()->currentDocument());
136         /// Indexed string for 'Php', identifies environment files from this language plugin
137         static const IndexedString phpLangString("Php");
138         file->setLanguage(phpLangString);
139     }
140     TopDUContext* ret = new PhpDUContext<TopDUContext>(m_editor->parseSession()->currentDocument(), range, file);
141     ret->setType(DUContext::Global);
142     return ret;
143 }
144 
setContextOnNode(AstNode * node,DUContext * ctx)145 void ContextBuilder::setContextOnNode(AstNode* node, DUContext* ctx)
146 {
147     node->ducontext = ctx;
148 }
149 
contextFromNode(AstNode * node)150 DUContext* ContextBuilder::contextFromNode(AstNode* node)
151 {
152     return node->ducontext;
153 }
154 
editorFindRange(AstNode * fromRange,AstNode * toRange)155 RangeInRevision ContextBuilder::editorFindRange(AstNode* fromRange, AstNode* toRange)
156 {
157     return m_editor->findRange(fromRange, toRange ? toRange : fromRange);
158 }
159 
startPos(AstNode * node)160 CursorInRevision ContextBuilder::startPos(AstNode* node)
161 {
162     return m_editor->findPosition(node->startToken, EditorIntegrator::FrontEdge);
163 }
164 
identifierForNode(IdentifierAst * id)165 QualifiedIdentifier ContextBuilder::identifierForNode(IdentifierAst* id)
166 {
167     if (!id)
168         return QualifiedIdentifier();
169 
170     return QualifiedIdentifier(stringForNode(id));
171 }
172 
identifierForNode(SemiReservedIdentifierAst * id)173 QualifiedIdentifier ContextBuilder::identifierForNode(SemiReservedIdentifierAst* id)
174 {
175     if (!id)
176         return QualifiedIdentifier();
177 
178     return QualifiedIdentifier(stringForNode(id));
179 }
180 
identifierForNode(VariableIdentifierAst * id)181 QualifiedIdentifier ContextBuilder::identifierForNode(VariableIdentifierAst* id)
182 {
183     if (!id)
184         return QualifiedIdentifier();
185     QString ret(stringForNode(id));
186     ret = ret.mid(1); //cut off $
187     return QualifiedIdentifier(ret);
188 }
189 
identifierPairForNode(IdentifierAst * id,bool isConstIdentifier)190 IdentifierPair ContextBuilder::identifierPairForNode(IdentifierAst* id, bool isConstIdentifier)
191 {
192     if (!id) {
193         return qMakePair(IndexedString(), QualifiedIdentifier());
194     }
195     const QString ret = stringForNode(id);
196 
197     if ( isConstIdentifier ) {
198         return qMakePair(IndexedString(ret), QualifiedIdentifier(ret));
199     } else {
200         return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower()));
201     }
202 }
203 
identifierPairForNode(SemiReservedIdentifierAst * id)204 IdentifierPair ContextBuilder::identifierPairForNode(SemiReservedIdentifierAst* id )
205 {
206     if (!id) {
207         return qMakePair(IndexedString(), QualifiedIdentifier());
208     }
209     const QString ret = stringForNode(id);
210 
211     return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower()));
212 }
213 
identifierPairForNode(ReservedNonModifierIdentifierAst * id)214 IdentifierPair ContextBuilder::identifierPairForNode(ReservedNonModifierIdentifierAst* id )
215 {
216     if (!id) {
217         return qMakePair(IndexedString(), QualifiedIdentifier());
218     }
219     const QString ret = stringForNode(id);
220 
221     return qMakePair(IndexedString(ret), QualifiedIdentifier(ret.toLower()));
222 }
223 
stringForNode(IdentifierAst * node) const224 QString ContextBuilder::stringForNode(IdentifierAst* node) const
225 {
226     return m_editor->parseSession()->symbol(node->string);
227 }
228 
stringForNode(SemiReservedIdentifierAst * node) const229 QString ContextBuilder::stringForNode(SemiReservedIdentifierAst* node) const
230 {
231     return m_editor->parseSession()->symbol(node->string);
232 }
233 
stringForNode(ReservedNonModifierIdentifierAst * node) const234 QString ContextBuilder::stringForNode(ReservedNonModifierIdentifierAst* node) const
235 {
236     return m_editor->parseSession()->symbol(node->string);
237 }
238 
stringForNode(VariableIdentifierAst * node) const239 QString ContextBuilder::stringForNode(VariableIdentifierAst* node) const
240 {
241     return m_editor->parseSession()->symbol(node->variable);
242 }
243 
visitClassDeclarationStatement(ClassDeclarationStatementAst * node)244 void ContextBuilder::visitClassDeclarationStatement(ClassDeclarationStatementAst* node)
245 {
246     openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->className).second);
247     classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration())
248     DefaultVisitor::visitClassDeclarationStatement(node);
249     closeContext();
250 }
251 
classContextOpened(DUContext * context)252 void ContextBuilder::classContextOpened(DUContext* context)
253 {
254     Q_UNUSED(context);
255 }
256 
visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst * node)257 void ContextBuilder::visitInterfaceDeclarationStatement(InterfaceDeclarationStatementAst* node)
258 {
259     openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->interfaceName).second);
260     classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration())
261     DefaultVisitor::visitInterfaceDeclarationStatement(node);
262     closeContext();
263 }
264 
visitTraitDeclarationStatement(TraitDeclarationStatementAst * node)265 void ContextBuilder::visitTraitDeclarationStatement(TraitDeclarationStatementAst* node)
266 {
267     openContext(node, editorFindRange(node, node), DUContext::Class, identifierPairForNode(node->traitName).second);
268     classContextOpened(currentContext()); //This callback is needed, so we can set the internal context and so find the declaration for the context (before closeDeclaration())
269     DefaultVisitor::visitTraitDeclarationStatement(node);
270     closeContext();
271 }
272 
visitClassStatement(ClassStatementAst * node)273 void ContextBuilder::visitClassStatement(ClassStatementAst *node)
274 {
275     visitOptionalModifiers(node->modifiers);
276     if (node->methodName) {
277         //method declaration
278         DUContext* parameters = openContext(node->parameters, DUContext::Function, identifierForNode(node->methodName));
279         Q_ASSERT(!parameters->inSymbolTable());
280 
281         visitParameterList(node->parameters);
282         if (node->returnType) {
283             visitReturnType(node->returnType);
284         }
285         closeContext();
286 
287         if ( !m_isInternalFunctions && node->methodBody ) {
288             // the internal functions file has only empty method bodies, so skip them
289             DUContext* body = openContext(node->methodBody, DUContext::Other, identifierForNode(node->methodName));
290             if (compilingContexts()) {
291                 DUChainWriteLocker lock(DUChain::lock());
292                 body->addImportedParentContext(parameters);
293                 body->setInSymbolTable(false);
294             }
295             visitMethodBody(node->methodBody);
296             closeContext();
297         }
298     } else {
299         //member-variable or const
300         DefaultVisitor::visitClassStatement(node);
301     }
302 }
303 
visitFunctionDeclarationStatement(FunctionDeclarationStatementAst * node)304 void ContextBuilder::visitFunctionDeclarationStatement(FunctionDeclarationStatementAst* node)
305 {
306     visitIdentifier(node->functionName);
307 
308     DUContext* parameters = openContext(node->parameters, DUContext::Function, node->functionName);
309     Q_ASSERT(!parameters->inSymbolTable());
310 
311     visitParameterList(node->parameters);
312     if (node->returnType) {
313         visitReturnType(node->returnType);
314     }
315     closeContext();
316 
317     if ( !m_isInternalFunctions && node->functionBody ) {
318         // the internal functions file has only empty method bodies, so skip them
319         DUContext* body = openContext(node->functionBody, DUContext::Other, node->functionName);
320         if (compilingContexts()) {
321             DUChainWriteLocker lock(DUChain::lock());
322             body->addImportedParentContext(parameters);
323             body->setInSymbolTable(false);
324         }
325         visitInnerStatementList(node->functionBody);
326         closeContext();
327     }
328 }
329 
visitClosure(ClosureAst * node)330 void ContextBuilder::visitClosure(ClosureAst* node)
331 {
332     DUContext* parameters = openContext(node->parameters, DUContext::Function);
333     Q_ASSERT(!parameters->inSymbolTable());
334 
335     visitParameterList(node->parameters);
336     if (node->returnType) {
337         visitReturnType(node->returnType);
338     }
339     closeContext();
340 
341     DUContext* imported = nullptr;
342     if ( node->lexicalVars ) {
343         imported = openContext(node->lexicalVars, DUContext::Other);
344         Q_ASSERT(!imported->inSymbolTable());
345 
346         visitLexicalVarList(node->lexicalVars);
347         closeContext();
348     }
349 
350     if ( !m_isInternalFunctions && node->functionBody ) {
351         // the internal functions file has only empty method bodies, so skip them
352         DUContext* body = openContext(node->functionBody, DUContext::Other);
353         if (compilingContexts()) {
354             DUChainWriteLocker lock;
355             body->addImportedParentContext(parameters);
356             if (imported) {
357                 body->addImportedParentContext(imported, CursorInRevision::invalid(), true);
358             }
359             body->setInSymbolTable(false);
360         }
361         visitInnerStatementList(node->functionBody);
362         closeContext();
363     }
364 }
365 
visitNamespaceDeclarationStatement(NamespaceDeclarationStatementAst * node)366 void ContextBuilder::visitNamespaceDeclarationStatement(NamespaceDeclarationStatementAst* node)
367 {
368     // close existing namespace context
369     if (m_openNamespaces) {
370         closeNamespaces(m_openNamespaces);
371         m_openNamespaces = nullptr;
372     }
373 
374     if ( !node->namespaceNameSequence ) {
375         if (node->body) {
376             // global namespace
377             DefaultVisitor::visitInnerStatementList(node->body);
378         }
379         return;
380     }
381 
382     { // open
383     ///TODO: support \ as separator
384 
385     RangeInRevision bodyRange;
386     if (node->body) {
387         bodyRange = editorFindRange(node->body, node->body);
388     } else {
389         bodyRange = RangeInRevision(m_editor->findPosition(node->endToken), currentContext()->topContext()->range().end);
390     }
391     const KDevPG::ListNode< IdentifierAst* >* it = node->namespaceNameSequence->front();
392     do {
393         openNamespace(node, it->element, identifierPairForNode(it->element), bodyRange);
394     } while(it->hasNext() && (it = it->next));
395     }
396 
397     if (node->body) {
398         DefaultVisitor::visitInnerStatementList(node->body);
399         closeNamespaces(node);
400     } else {
401         m_openNamespaces = node;
402     }
403 }
404 
closeNamespaces(NamespaceDeclarationStatementAst * namespaces)405 void ContextBuilder::closeNamespaces(NamespaceDeclarationStatementAst* namespaces)
406 {
407     ///TODO: support \ as separator
408     const KDevPG::ListNode< IdentifierAst* >* it = namespaces->namespaceNameSequence->front();
409     do {
410         Q_ASSERT(currentContext()->type() == DUContext::Namespace);
411         closeNamespace(namespaces, it->element, identifierPairForNode(it->element));
412     } while(it->hasNext() && (it = it->next));
413 }
414 
openNamespace(NamespaceDeclarationStatementAst * parent,IdentifierAst * node,const IdentifierPair & identifier,const RangeInRevision & range)415 void ContextBuilder::openNamespace(NamespaceDeclarationStatementAst* parent, IdentifierAst* node, const IdentifierPair& identifier, const RangeInRevision& range)
416 {
417     if ( node == parent->namespaceNameSequence->back()->element ) {
418         openContext(node, range, DUContext::Namespace, identifier.second);
419     } else {
420         openContext(node, range, DUContext::Namespace, identifier.second);
421     }
422 }
423 
closeNamespace(NamespaceDeclarationStatementAst *,IdentifierAst *,const IdentifierPair &)424 void ContextBuilder::closeNamespace(NamespaceDeclarationStatementAst* /*parent*/, IdentifierAst* /*node*/, const IdentifierPair& /*identifier*/)
425 {
426     closeContext();
427 }
428 
addBaseType(NamespacedIdentifierAst * identifier)429 void ContextBuilder::addBaseType(NamespacedIdentifierAst * identifier)
430 {
431     DUChainWriteLocker lock(DUChain::lock());
432 
433     Q_ASSERT(currentContext()->type() == DUContext::Class);
434 
435     ClassDeclaration* currentClass = dynamic_cast<ClassDeclaration*>(currentContext()->owner());
436 
437     ClassDeclaration* baseClass = dynamic_cast<ClassDeclaration*>(
438         findDeclarationImport(ClassDeclarationType, identifierForNamespace(identifier, m_editor)).data() );
439 
440     if (currentClass && baseClass) {
441         if (DUContext* baseContext = baseClass->logicalInternalContext(nullptr)) {
442             // prevent circular context imports which could lead to segfaults
443             if (!baseContext->imports(currentContext()) && !currentContext()->imports(baseContext)) {
444                 currentContext()->addImportedParentContext(baseContext);
445                 BaseClassInstance base;
446                 base.baseClass = baseClass->indexedType();
447                 base.access = Declaration::Public;
448                 base.virtualInheritance = false;
449                 currentClass->addBaseClass(base);
450             } else if (m_reportErrors && baseClass->classType() != ClassDeclarationData::Interface) {
451                 reportError(i18n("Circular inheritance of %1 and %2", currentClass->toString(), baseClass->toString()), identifier);
452             }
453         }
454     }
455     if (!baseClass) {
456         qCDebug(DUCHAIN) << "unresolved identifier";
457         m_hadUnresolvedIdentifiers = true;
458     }
459 }
460 
461 
visitUnaryExpression(UnaryExpressionAst * node)462 void ContextBuilder::visitUnaryExpression(UnaryExpressionAst* node)
463 {
464     DefaultVisitor::visitUnaryExpression(node);
465     if (!compilingContexts()) {
466         return;
467     }
468     IndexedString includeFile = getIncludeFileForNode(node, m_editor);
469 
470     if ( !includeFile.isEmpty() ) {
471         DUChainWriteLocker lock(DUChain::lock());
472         TopDUContext *top = DUChain::self()->chainForDocument(includeFile);
473         if (top) {
474             currentContext()->topContext()->addImportedParentContext(top);
475             currentContext()->topContext()->parsingEnvironmentFile()
476             ->addModificationRevisions(top->parsingEnvironmentFile()->allModificationRevisions());
477         }
478     }
479 }
480 
visitFunctionCallParameterListElement(FunctionCallParameterListElementAst * node)481 void ContextBuilder::visitFunctionCallParameterListElement(FunctionCallParameterListElementAst* node)
482 {
483     DefaultVisitor::visitFunctionCallParameterListElement(node);
484 
485     setContextOnNode(node, currentContext());
486 }
487 
reportError(const QString & errorMsg,AstNode * node,IProblem::Severity severity)488 void ContextBuilder::reportError(const QString& errorMsg, AstNode* node, IProblem::Severity severity)
489 {
490     reportError(errorMsg, m_editor->findRange(node), severity);
491 }
492 
reportError(const QString & errorMsg,QList<AstNode * > nodes,IProblem::Severity severity)493 void ContextBuilder::reportError(const QString& errorMsg, QList< AstNode* > nodes, IProblem::Severity severity)
494 {
495     RangeInRevision range = RangeInRevision::invalid();
496     foreach ( AstNode* node, nodes ) {
497         if ( !range.isValid() ) {
498             range = m_editor->findRange(node);
499         } else {
500             range.end = m_editor->findPosition(node->endToken);
501         }
502     }
503     reportError(errorMsg, range, severity);
504 }
505 
reportError(const QString & errorMsg,RangeInRevision range,IProblem::Severity severity)506 void ContextBuilder::reportError(const QString& errorMsg, RangeInRevision range, IProblem::Severity severity)
507 {
508     auto *p = new Problem();
509     p->setSeverity(severity);
510     p->setSource(IProblem::DUChainBuilder);
511     p->setDescription(errorMsg);
512     p->setFinalLocation(DocumentRange(m_editor->parseSession()->currentDocument(),
513                                                 range.castToSimpleRange()));
514     {
515         DUChainWriteLocker lock(DUChain::lock());
516         qCDebug(DUCHAIN) << "Problem" << p->description() << p->finalLocation();
517         currentContext()->topContext()->addProblem(ProblemPointer(p));
518     }
519 }
520 
findDeclarationImport(DeclarationType declarationType,IdentifierAst * node)521 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType,
522                                                          IdentifierAst* node)
523 {
524     QualifiedIdentifier id;
525     if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) {
526         id = identifierPairForNode(node).second;
527     } else {
528         id = identifierForNode(node);
529     }
530     return findDeclarationImportHelper(currentContext(), id, declarationType);
531 }
532 
findDeclarationImport(DeclarationType declarationType,SemiReservedIdentifierAst * node,DeclarationScope declarationScope)533 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType,
534                                                          SemiReservedIdentifierAst* node,
535                                                          DeclarationScope declarationScope)
536 {
537     QualifiedIdentifier id;
538     if ( declarationType == ClassDeclarationType || declarationType == FunctionDeclarationType ) {
539         id = identifierPairForNode(node).second;
540     } else {
541         id = identifierForNode(node);
542     }
543 
544     if (declarationScope == GlobalScope) {
545         id.setExplicitlyGlobal(true);
546     }
547 
548     return findDeclarationImportHelper(currentContext(), id, declarationType);
549 }
550 
findDeclarationImport(DeclarationType declarationType,VariableIdentifierAst * node)551 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType,
552                                                          VariableIdentifierAst* node)
553 {
554     return findDeclarationImportHelper(currentContext(), identifierForNode(node), declarationType);
555 }
556 
findDeclarationImport(DeclarationType declarationType,const QualifiedIdentifier & identifier)557 DeclarationPointer ContextBuilder::findDeclarationImport(DeclarationType declarationType,
558                                                          const QualifiedIdentifier &identifier)
559 {
560     return findDeclarationImportHelper(currentContext(), identifier, declarationType);
561 }
562 
563 }
564