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