/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "cppchecksymbols.h" #include "cpplocalsymbols.h" #include #include #include #include // This is for experimeting highlighting ctors/dtors as functions (instead of types). // Whenever this feature is considered "accepted" the switch below should be permanently // removed, unless we decide to actually make this a user setting - that is why it's // currently a bool instead of a define. static const bool highlightCtorDtorAsType = true; using namespace CPlusPlus; using namespace CppTools; namespace { class FriendlyThread: public QThread { public: using QThread::msleep; }; class CollectSymbols: protected SymbolVisitor { Document::Ptr _doc; Snapshot _snapshot; QSet _types; QSet _fields; QSet _functions; QSet _statics; bool _mainDocument; public: CollectSymbols(Document::Ptr doc, const Snapshot &snapshot) : _doc(doc), _snapshot(snapshot), _mainDocument(false) { QSet processed; process(doc, &processed); } const QSet &types() const { return _types; } const QSet &fields() const { return _fields; } const QSet &functions() const { return _functions; } const QSet &statics() const { return _statics; } protected: void process(Document::Ptr doc, QSet *processed) { if (!doc) return; if (!processed->contains(doc->globalNamespace())) { processed->insert(doc->globalNamespace()); foreach (const Document::Include &i, doc->resolvedIncludes()) process(_snapshot.document(i.resolvedFileName()), processed); _mainDocument = (doc == _doc); // ### improve accept(doc->globalNamespace()); } } void addType(const Identifier *id) { if (id) _types.insert(QByteArray::fromRawData(id->chars(), id->size())); } void addType(const Name *name) { if (!name) { return; } else if (const QualifiedNameId *q = name->asQualifiedNameId()) { addType(q->base()); addType(q->name()); } else if (name->isNameId() || name->isTemplateNameId()) { addType(name->identifier()); } } void addField(const Name *name) { if (!name) { return; } else if (name->isNameId()) { const Identifier *id = name->identifier(); _fields.insert(QByteArray::fromRawData(id->chars(), id->size())); } } void addFunction(const Name *name) { if (!name) { return; } else if (name->isNameId()) { const Identifier *id = name->identifier(); _functions.insert(QByteArray::fromRawData(id->chars(), id->size())); } } void addStatic(const Name *name) { if (!name) { return; } else if (name->isNameId() || name->isTemplateNameId()) { const Identifier *id = name->identifier(); _statics.insert(QByteArray::fromRawData(id->chars(), id->size())); } } // nothing to do bool visit(UsingNamespaceDirective *) override { return true; } bool visit(UsingDeclaration *) override { return true; } bool visit(Argument *) override { return true; } bool visit(BaseClass *) override { return true; } bool visit(Function *symbol) override { addFunction(symbol->name()); return true; } bool visit(Block *) override { return true; } bool visit(NamespaceAlias *symbol) override { addType(symbol->name()); return true; } bool visit(Declaration *symbol) override { if (symbol->enclosingEnum() != nullptr) addStatic(symbol->name()); if (symbol->type()->isFunctionType()) addFunction(symbol->name()); if (symbol->isTypedef()) addType(symbol->name()); else if (!symbol->type()->isFunctionType() && symbol->enclosingScope()->isClass()) addField(symbol->name()); return true; } bool visit(TypenameArgument *symbol) override { addType(symbol->name()); return true; } bool visit(Enum *symbol) override { addType(symbol->name()); return true; } bool visit(Namespace *symbol) override { addType(symbol->name()); return true; } bool visit(Template *) override { return true; } bool visit(Class *symbol) override { addType(symbol->name()); return true; } bool visit(ForwardClassDeclaration *symbol) override { addType(symbol->name()); return true; } // Objective-C bool visit(ObjCBaseClass *) override { return true; } bool visit(ObjCBaseProtocol *) override { return true; } bool visit(ObjCPropertyDeclaration *) override { return true; } bool visit(ObjCMethod *) override { return true; } bool visit(ObjCClass *symbol) override { addType(symbol->name()); return true; } bool visit(ObjCForwardClassDeclaration *symbol) override { addType(symbol->name()); return true; } bool visit(ObjCProtocol *symbol) override { addType(symbol->name()); return true; } bool visit(ObjCForwardProtocolDeclaration *symbol) override { addType(symbol->name()); return true; } }; } // end of anonymous namespace static bool sortByLinePredicate(const CheckSymbols::Result &lhs, const CheckSymbols::Result &rhs) { if (lhs.line == rhs.line) return lhs.column < rhs.column; else return lhs.line < rhs.line; } static bool acceptName(NameAST *ast, unsigned *referenceToken) { *referenceToken = ast->firstToken(); DestructorNameAST *dtor = ast->asDestructorName(); if (dtor) *referenceToken = dtor->unqualified_name->firstToken(); if (highlightCtorDtorAsType) return true; return !dtor && !ast->asConversionFunctionId() && !ast->asOperatorFunctionId(); } CheckSymbols::Future CheckSymbols::go(Document::Ptr doc, const LookupContext &context, const QList ¯oUses) { QTC_ASSERT(doc, return Future()); QTC_ASSERT(doc->translationUnit(), return Future()); QTC_ASSERT(doc->translationUnit()->ast(), return Future()); return (new CheckSymbols(doc, context, macroUses))->start(); } CheckSymbols * CheckSymbols::create(Document::Ptr doc, const LookupContext &context, const QList ¯oUses) { QTC_ASSERT(doc, return nullptr); QTC_ASSERT(doc->translationUnit(), return nullptr); QTC_ASSERT(doc->translationUnit()->ast(), return nullptr); return new CheckSymbols(doc, context, macroUses); } CheckSymbols::CheckSymbols(Document::Ptr doc, const LookupContext &context, const QList ¯oUses) : ASTVisitor(doc->translationUnit()), _doc(doc), _context(context) , _lineOfLastUsage(0), _macroUses(macroUses) { unsigned line = 0; getTokenEndPosition(translationUnit()->ast()->lastToken(), &line, nullptr); _chunkSize = qMax(50U, line / 200); _usages.reserve(_chunkSize); _astStack.reserve(200); typeOfExpression.init(_doc, _context.snapshot(), _context.bindings()); // make possible to instantiate templates typeOfExpression.setExpandTemplates(true); } CheckSymbols::~CheckSymbols() = default; void CheckSymbols::run() { CollectSymbols collectTypes(_doc, _context.snapshot()); _fileName = _doc->fileName(); _potentialTypes = collectTypes.types(); _potentialFields = collectTypes.fields(); _potentialFunctions = collectTypes.functions(); _potentialStatics = collectTypes.statics(); Utils::sort(_macroUses, sortByLinePredicate); if (!isCanceled()) { if (_doc->translationUnit()) { accept(_doc->translationUnit()->ast()); _usages << QVector::fromList(_macroUses); flush(); } emit codeWarningsUpdated(_doc, _diagMsgs); } reportFinished(); } bool CheckSymbols::warning(unsigned line, unsigned column, const QString &text, unsigned length) { Document::DiagnosticMessage m(Document::DiagnosticMessage::Warning, _fileName, line, column, text, length); _diagMsgs.append(m); return false; } bool CheckSymbols::warning(AST *ast, const QString &text) { const Token &firstToken = tokenAt(ast->firstToken()); const Token &lastToken = tokenAt(ast->lastToken() - 1); const unsigned length = lastToken.utf16charsEnd() - firstToken.utf16charsBegin(); unsigned line = 1, column = 1; getTokenStartPosition(ast->firstToken(), &line, &column); warning(line, column, text, length); return false; } FunctionDefinitionAST *CheckSymbols::enclosingFunctionDefinition(bool skipTopOfStack) const { int index = _astStack.size() - 1; if (skipTopOfStack && !_astStack.isEmpty()) --index; for (; index != -1; --index) { AST *ast = _astStack.at(index); if (FunctionDefinitionAST *funDef = ast->asFunctionDefinition()) return funDef; } return nullptr; } TemplateDeclarationAST *CheckSymbols::enclosingTemplateDeclaration() const { for (int index = _astStack.size() - 1; index != -1; --index) { AST *ast = _astStack.at(index); if (TemplateDeclarationAST *funDef = ast->asTemplateDeclaration()) return funDef; } return nullptr; } Scope *CheckSymbols::enclosingScope() const { for (int index = _astStack.size() - 1; index != -1; --index) { AST *ast = _astStack.at(index); if (NamespaceAST *ns = ast->asNamespace()) { if (ns->symbol) return ns->symbol; } else if (ClassSpecifierAST *classSpec = ast->asClassSpecifier()) { if (classSpec->symbol) return classSpec->symbol; } else if (FunctionDefinitionAST *funDef = ast->asFunctionDefinition()) { if (funDef->symbol) return funDef->symbol; } else if (TemplateDeclarationAST *templateDeclaration = ast->asTemplateDeclaration()) { if (templateDeclaration->symbol) return templateDeclaration->symbol; } else if (CompoundStatementAST *blockStmt = ast->asCompoundStatement()) { if (blockStmt->symbol) return blockStmt->symbol; } else if (IfStatementAST *ifStmt = ast->asIfStatement()) { if (ifStmt->symbol) return ifStmt->symbol; } else if (WhileStatementAST *whileStmt = ast->asWhileStatement()) { if (whileStmt->symbol) return whileStmt->symbol; } else if (ForStatementAST *forStmt = ast->asForStatement()) { if (forStmt->symbol) return forStmt->symbol; } else if (ForeachStatementAST *foreachStmt = ast->asForeachStatement()) { if (foreachStmt->symbol) return foreachStmt->symbol; } else if (RangeBasedForStatementAST *rangeBasedForStmt = ast->asRangeBasedForStatement()) { if (rangeBasedForStmt->symbol) return rangeBasedForStmt->symbol; } else if (SwitchStatementAST *switchStmt = ast->asSwitchStatement()) { if (switchStmt->symbol) return switchStmt->symbol; } else if (CatchClauseAST *catchClause = ast->asCatchClause()) { if (catchClause->symbol) return catchClause->symbol; } } return _doc->globalNamespace(); } bool CheckSymbols::preVisit(AST *ast) { _astStack.append(ast); if (isCanceled()) return false; return true; } void CheckSymbols::postVisit(AST *) { _astStack.takeLast(); } bool CheckSymbols::visit(NamespaceAST *ast) { if (ast->identifier_token) { const Token &tok = tokenAt(ast->identifier_token); if (!tok.generated()) { unsigned line, column; getTokenStartPosition(ast->identifier_token, &line, &column); Result use(line, column, tok.utf16chars(), SemanticHighlighter::TypeUse); addUse(use); } } return true; } bool CheckSymbols::visit(UsingDirectiveAST *) { return true; } bool CheckSymbols::visit(EnumeratorAST *ast) { addUse(ast->identifier_token, SemanticHighlighter::EnumerationUse); return true; } bool CheckSymbols::visit(DotDesignatorAST *ast) { addUse(ast->identifier_token, SemanticHighlighter::FieldUse); return true; } bool CheckSymbols::visit(SimpleDeclarationAST *ast) { NameAST *declrIdNameAST = nullptr; if (ast->declarator_list && !ast->declarator_list->next) { if (ast->symbols && !ast->symbols->next && !ast->symbols->value->isGenerated()) { Symbol *decl = ast->symbols->value; if (NameAST *nameAST = declaratorId(ast->declarator_list->value)) { if (Function *funTy = decl->type()->asFunctionType()) { if (funTy->isVirtual() || (nameAST->asDestructorName() && hasVirtualDestructor(_context.lookupType(funTy->enclosingScope())))) { addUse(nameAST, SemanticHighlighter::VirtualFunctionDeclarationUse); declrIdNameAST = nameAST; } else if (maybeAddFunction(_context.lookup(decl->name(), decl->enclosingScope()), nameAST, funTy->argumentCount(), FunctionDeclaration)) { declrIdNameAST = nameAST; // Add a diagnostic message if non-virtual function has override/final marker if ((_usages.back().kind != SemanticHighlighter::VirtualFunctionDeclarationUse)) { if (funTy->isOverride()) warning(declrIdNameAST, QCoreApplication::translate( "CPlusplus::CheckSymbols", "Only virtual functions can be marked 'override'")); else if (funTy->isFinal()) warning(declrIdNameAST, QCoreApplication::translate( "CPlusPlus::CheckSymbols", "Only virtual functions can be marked 'final'")); } } } } } } accept(ast->decl_specifier_list); for (DeclaratorListAST *it = ast->declarator_list; it ; it = it->next) { DeclaratorAST *declr = it->value; if (declrIdNameAST && declr->core_declarator && declr->core_declarator->asDeclaratorId() && declr->core_declarator->asDeclaratorId()->name == declrIdNameAST) { accept(declr->attribute_list); accept(declr->postfix_declarator_list); accept(declr->post_attribute_list); accept(declr->initializer); } else { accept(declr); } } return false; } bool CheckSymbols::visit(ElaboratedTypeSpecifierAST *ast) { accept(ast->attribute_list); accept(ast->name); addUse(ast->name, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(ObjCProtocolDeclarationAST *ast) { accept(ast->attribute_list); accept(ast->name); accept(ast->protocol_refs); accept(ast->member_declaration_list); addUse(ast->name, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(ObjCProtocolForwardDeclarationAST *ast) { accept(ast->attribute_list); accept(ast->identifier_list); for (NameListAST *i = ast->identifier_list; i; i = i->next) addUse(i->value, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(ObjCClassDeclarationAST *ast) { accept(ast->attribute_list); accept(ast->class_name); accept(ast->category_name); accept(ast->superclass); accept(ast->protocol_refs); accept(ast->inst_vars_decl); accept(ast->member_declaration_list); addUse(ast->class_name, SemanticHighlighter::TypeUse); if (ast->superclass && maybeType(ast->superclass->name)) addUse(ast->superclass, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(ObjCClassForwardDeclarationAST *ast) { accept(ast->attribute_list); accept(ast->identifier_list); for (NameListAST *i = ast->identifier_list; i; i = i->next) addUse(i->value, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(ObjCProtocolRefsAST *ast) { accept(ast->identifier_list); for (NameListAST *i = ast->identifier_list; i; i = i->next) if (maybeType(i->value->name)) addUse(i->value, SemanticHighlighter::TypeUse); return false; } bool CheckSymbols::visit(MemberAccessAST *ast) { accept(ast->base_expression); if (!ast->member_name) return false; if (const Name *name = ast->member_name->name) { if (const Identifier *ident = name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialFields.contains(id)) { const Token start = tokenAt(ast->firstToken()); const Token end = tokenAt(ast->lastToken() - 1); const QByteArray expression = _doc->utf8Source() .mid(start.bytesBegin(), end.bytesEnd() - start.bytesBegin()); const QList candidates = typeOfExpression(expression, enclosingScope(), TypeOfExpression::Preprocess); maybeAddField(candidates, ast->member_name); } } } return false; } bool CheckSymbols::visit(CallAST *ast) { if (ast->base_expression) { unsigned argumentCount = 0; for (ExpressionListAST *it = ast->expression_list; it; it = it->next) ++argumentCount; ExpressionAST *expr = ast->base_expression; if (MemberAccessAST *access = ast->base_expression->asMemberAccess()) { if (access->member_name && access->member_name->name) { if (maybeFunction(access->member_name->name)) { expr = access->base_expression; const QByteArray expression = textOf(access); const QList candidates = typeOfExpression(expression, enclosingScope(), TypeOfExpression::Preprocess); NameAST *memberName = access->member_name; if (QualifiedNameAST *q = memberName->asQualifiedName()) { checkNestedName(q); memberName = q->unqualified_name; } else if (TemplateIdAST *tId = memberName->asTemplateId()) { accept(tId->template_argument_list); } if (!maybeAddFunction(candidates, memberName, argumentCount, FunctionCall) && highlightCtorDtorAsType) { expr = ast->base_expression; } } } } else if (IdExpressionAST *idExpr = ast->base_expression->asIdExpression()) { if (const Name *name = idExpr->name->name) { if (maybeFunction(name)) { expr = nullptr; NameAST *exprName = idExpr->name; if (QualifiedNameAST *q = exprName->asQualifiedName()) { checkNestedName(q); exprName = q->unqualified_name; } else if (TemplateIdAST *tId = exprName->asTemplateId()) { accept(tId->template_argument_list); } const QList candidates = typeOfExpression(textOf(idExpr), enclosingScope(), TypeOfExpression::Preprocess); if (!maybeAddFunction(candidates, exprName, argumentCount, FunctionCall) && highlightCtorDtorAsType) { expr = ast->base_expression; } } } } accept(expr); accept(ast->expression_list); } return false; } bool CheckSymbols::visit(ObjCSelectorArgumentAST *ast) { addUse(ast->firstToken(), SemanticHighlighter::FunctionUse); return true; } bool CheckSymbols::visit(NewExpressionAST *ast) { accept(ast->new_placement); accept(ast->type_id); if (highlightCtorDtorAsType) { accept(ast->new_type_id); } else { ClassOrNamespace *binding = nullptr; NameAST *nameAST = nullptr; if (ast->new_type_id) { for (SpecifierListAST *it = ast->new_type_id->type_specifier_list; it; it = it->next) { if (NamedTypeSpecifierAST *spec = it->value->asNamedTypeSpecifier()) { nameAST = spec->name; if (QualifiedNameAST *qNameAST = nameAST->asQualifiedName()) { binding = checkNestedName(qNameAST); if (binding) binding = binding->findType(qNameAST->unqualified_name->name); nameAST = qNameAST->unqualified_name; } else if (maybeType(nameAST->name)) { binding = _context.lookupType(nameAST->name, enclosingScope()); } break; } } } if (binding && nameAST) { int arguments = 0; if (ast->new_initializer) { ExpressionListAST *list = nullptr; if (ExpressionListParenAST *exprListParen = ast->new_initializer->asExpressionListParen()) list = exprListParen->expression_list; else if (BracedInitializerAST *braceInit = ast->new_initializer->asBracedInitializer()) list = braceInit->expression_list; for (ExpressionListAST *it = list; it; it = it->next) ++arguments; } Scope *scope = enclosingScope(); foreach (Symbol *s, binding->symbols()) { if (Class *klass = s->asClass()) { scope = klass; break; } } maybeAddFunction(_context.lookup(nameAST->name, scope), nameAST, arguments, FunctionCall); } } accept(ast->new_initializer); return false; } QByteArray CheckSymbols::textOf(AST *ast) const { const Token start = tokenAt(ast->firstToken()); const Token end = tokenAt(ast->lastToken() - 1); const QByteArray text = _doc->utf8Source().mid(start.bytesBegin(), end.bytesEnd() - start.bytesBegin()); return text; } void CheckSymbols::checkNamespace(NameAST *name) { if (!name) return; unsigned line, column; getTokenStartPosition(name->firstToken(), &line, &column); if (ClassOrNamespace *b = _context.lookupType(name->name, enclosingScope())) { foreach (Symbol *s, b->symbols()) { if (s->isNamespace()) return; } } const unsigned length = tokenAt(name->lastToken() - 1).utf16charsEnd() - tokenAt(name->firstToken()).utf16charsBegin(); warning(line, column, QCoreApplication::translate("CPlusPlus::CheckSymbols", "Expected a namespace-name"), length); } bool CheckSymbols::hasVirtualDestructor(Class *klass) const { if (!klass) return false; const Identifier *id = klass->identifier(); if (!id) return false; for (Symbol *s = klass->find(id); s; s = s->next()) { if (!s->name()) continue; if (s->name()->isDestructorNameId()) { if (Function *funTy = s->type()->asFunctionType()) { if (funTy->isVirtual() && id->match(s->identifier())) return true; } } } return false; } bool CheckSymbols::hasVirtualDestructor(ClassOrNamespace *binding) const { QSet processed; QList todo; todo.append(binding); while (!todo.isEmpty()) { ClassOrNamespace *b = todo.takeFirst(); if (b && !processed.contains(b)) { processed.insert(b); foreach (Symbol *s, b->symbols()) { if (Class *k = s->asClass()) { if (hasVirtualDestructor(k)) return true; } } todo += b->usings(); } } return false; } void CheckSymbols::checkName(NameAST *ast, Scope *scope) { if (ast && ast->name) { if (!scope) scope = enclosingScope(); if (ast->asDestructorName() != nullptr) { Class *klass = scope->asClass(); if (!klass && scope->asFunction()) klass = scope->asFunction()->enclosingScope()->asClass(); if (klass) { if (hasVirtualDestructor(_context.lookupType(klass))) { addUse(ast, SemanticHighlighter::VirtualFunctionDeclarationUse); } else { bool added = false; if (highlightCtorDtorAsType && maybeType(ast->name)) added = maybeAddTypeOrStatic(_context.lookup(ast->name, klass), ast); if (!added) addUse(ast, SemanticHighlighter::FunctionUse); } } } else if (maybeType(ast->name) || maybeStatic(ast->name)) { if (!maybeAddTypeOrStatic(_context.lookup(ast->name, scope), ast)) { // it can be a local variable if (maybeField(ast->name)) maybeAddField(_context.lookup(ast->name, scope), ast); } } else if (maybeField(ast->name)) { maybeAddField(_context.lookup(ast->name, scope), ast); } } } bool CheckSymbols::visit(SimpleNameAST *ast) { checkName(ast); return true; } bool CheckSymbols::visit(TemplateIdAST *ast) { checkName(ast); return true; } bool CheckSymbols::visit(DestructorNameAST *ast) { checkName(ast); return true; } bool CheckSymbols::visit(ParameterDeclarationAST *ast) { accept(ast->type_specifier_list); // Skip parameter name, it does not need to be colored accept(ast->expression); return false; } bool CheckSymbols::visit(QualifiedNameAST *ast) { if (ast->name) { ClassOrNamespace *binding = checkNestedName(ast); if (binding && ast->unqualified_name) { if (ast->unqualified_name->asDestructorName() != nullptr) { if (hasVirtualDestructor(binding)) { addUse(ast->unqualified_name, SemanticHighlighter::VirtualFunctionDeclarationUse); } else { bool added = false; if (highlightCtorDtorAsType && maybeType(ast->name)) added = maybeAddTypeOrStatic(binding->find(ast->unqualified_name->name), ast->unqualified_name); if (!added) addUse(ast->unqualified_name, SemanticHighlighter::FunctionUse); } } else { QList items = binding->find(ast->unqualified_name->name); if (items.empty()) items = _context.lookup(ast->name, enclosingScope()); maybeAddTypeOrStatic(items, ast->unqualified_name); } if (TemplateIdAST *template_id = ast->unqualified_name->asTemplateId()) accept(template_id->template_argument_list); } } return false; } ClassOrNamespace *CheckSymbols::checkNestedName(QualifiedNameAST *ast) { ClassOrNamespace *binding = nullptr; if (ast->name) { if (NestedNameSpecifierListAST *it = ast->nested_name_specifier_list) { NestedNameSpecifierAST *nested_name_specifier = it->value; if (NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name) { // ### remove shadowing if (TemplateIdAST *template_id = class_or_namespace_name->asTemplateId()) accept(template_id->template_argument_list); const Name *name = class_or_namespace_name->name; binding = _context.lookupType(name, enclosingScope()); if (binding) addType(binding, class_or_namespace_name); else // for the case when we use template parameter as qualifier // e.g.: template void fun() { T::type type; } accept(nested_name_specifier->class_or_namespace_name); for (it = it->next; it; it = it->next) { NestedNameSpecifierAST *nested_name_specifier = it->value; if (NameAST *class_or_namespace_name = nested_name_specifier->class_or_namespace_name) { if (TemplateIdAST *template_id = class_or_namespace_name->asTemplateId()) { if (template_id->template_token) { addUse(template_id, SemanticHighlighter::TypeUse); binding = nullptr; // there's no way we can find a binding. } accept(template_id->template_argument_list); if (!binding) continue; } if (binding) { binding = binding->findType(class_or_namespace_name->name); addType(binding, class_or_namespace_name); } } } } } } return binding; } bool CheckSymbols::visit(TypenameTypeParameterAST *ast) { addUse(ast->name, SemanticHighlighter::TypeUse); accept(ast->type_id); return false; } bool CheckSymbols::visit(TemplateTypeParameterAST *ast) { accept(ast->template_parameter_list); addUse(ast->name, SemanticHighlighter::TypeUse); accept(ast->type_id); return false; } bool CheckSymbols::visit(MemInitializerAST *ast) { if (FunctionDefinitionAST *enclosingFunction = enclosingFunctionDefinition()) { if (ast->name && enclosingFunction->symbol) { if (ClassOrNamespace *binding = _context.lookupType(enclosingFunction->symbol)) { foreach (Symbol *s, binding->symbols()) { if (Class *klass = s->asClass()) { NameAST *nameAST = ast->name; if (QualifiedNameAST *q = nameAST->asQualifiedName()) { checkNestedName(q); nameAST = q->unqualified_name; } if (highlightCtorDtorAsType && maybeType(nameAST->name)) { checkName(nameAST, klass); } else if (maybeField(nameAST->name)) { maybeAddField(_context.lookup(nameAST->name, klass), nameAST); } else { // It's a constructor, count the number of arguments unsigned arguments = 0; if (ast->expression) { ExpressionListAST *expr_list = nullptr; if (ExpressionListParenAST *parenExprList = ast->expression->asExpressionListParen()) expr_list = parenExprList->expression_list; else if (BracedInitializerAST *bracedInitList = ast->expression->asBracedInitializer()) expr_list = bracedInitList->expression_list; for (ExpressionListAST *it = expr_list; it; it = it->next) ++arguments; } maybeAddFunction(_context.lookup(nameAST->name, klass), nameAST, arguments, FunctionCall); } break; } } } } accept(ast->expression); } return false; } bool CheckSymbols::visit(GotoStatementAST *ast) { if (ast->identifier_token) addUse(ast->identifier_token, SemanticHighlighter::LabelUse); return false; } bool CheckSymbols::visit(LabeledStatementAST *ast) { if (ast->label_token && !tokenAt(ast->label_token).isKeyword()) addUse(ast->label_token, SemanticHighlighter::LabelUse); accept(ast->statement); return false; } /** * \brief Highlights "override" and "final" pseudokeywords like true keywords */ bool CheckSymbols::visit(SimpleSpecifierAST *ast) { if (ast->specifier_token) { const Token &tk = tokenAt(ast->specifier_token); if (tk.is(T_IDENTIFIER)) { const Identifier &id = *(tk.identifier); if (id.equalTo(_doc->control()->cpp11Override()) || id.equalTo(_doc->control()->cpp11Final())) { addUse(ast->specifier_token, SemanticHighlighter::PseudoKeywordUse); } } } return false; } bool CheckSymbols::visit(ClassSpecifierAST *ast) { if (ast->final_token) addUse(ast->final_token, SemanticHighlighter::PseudoKeywordUse); return true; } bool CheckSymbols::visit(FunctionDefinitionAST *ast) { AST *thisFunction = _astStack.takeLast(); accept(ast->decl_specifier_list); _astStack.append(thisFunction); bool processEntireDeclr = true; if (ast->declarator && ast->symbol && !ast->symbol->isGenerated()) { Function *fun = ast->symbol; if (NameAST *declId = declaratorId(ast->declarator)) { processEntireDeclr = false; if (QualifiedNameAST *q = declId->asQualifiedName()) { checkNestedName(q); declId = q->unqualified_name; } if (fun->isVirtual() || (declId->asDestructorName() && hasVirtualDestructor(_context.lookupType(fun->enclosingScope())))) { addUse(declId, SemanticHighlighter::VirtualFunctionDeclarationUse); } else if (!maybeAddFunction(_context.lookup(fun->name(), fun->enclosingScope()), declId, fun->argumentCount(), FunctionDeclaration)) { processEntireDeclr = true; } } } if (ast->declarator) { if (processEntireDeclr) { accept(ast->declarator); } else { accept(ast->declarator->attribute_list); accept(ast->declarator->postfix_declarator_list); accept(ast->declarator->post_attribute_list); accept(ast->declarator->initializer); } } accept(ast->ctor_initializer); accept(ast->function_body); const LocalSymbols locals(_doc, ast); foreach (const QList &uses, locals.uses) { foreach (const Result &u, uses) addUse(u); } if (!enclosingFunctionDefinition(true)) if (_usages.size() >= _chunkSize) flush(); return false; } void CheckSymbols::addUse(NameAST *ast, Kind kind) { if (!ast) return; if (QualifiedNameAST *q = ast->asQualifiedName()) ast = q->unqualified_name; if (DestructorNameAST *dtor = ast->asDestructorName()) ast = dtor->unqualified_name; if (!ast) return; // nothing to do else if (ast->asOperatorFunctionId() != nullptr || ast->asConversionFunctionId() != nullptr) return; // nothing to do unsigned startToken = ast->firstToken(); if (TemplateIdAST *templ = ast->asTemplateId()) startToken = templ->identifier_token; addUse(startToken, kind); } void CheckSymbols::addUse(unsigned tokenIndex, Kind kind) { if (!tokenIndex) return; const Token &tok = tokenAt(tokenIndex); if (tok.generated()) return; unsigned line, column; getTokenStartPosition(tokenIndex, &line, &column); const unsigned length = tok.utf16chars(); const Result use(line, column, length, kind); addUse(use); } void CheckSymbols::addUse(const Result &use) { if (use.isInvalid()) return; if (!enclosingFunctionDefinition()) { if (_usages.size() >= _chunkSize) { if (use.line > _lineOfLastUsage) flush(); } } while (!_macroUses.isEmpty() && _macroUses.first().line <= use.line) _usages.append(_macroUses.takeFirst()); _lineOfLastUsage = qMax(_lineOfLastUsage, use.line); _usages.append(use); } void CheckSymbols::addType(ClassOrNamespace *b, NameAST *ast) { unsigned startToken; if (!b || !acceptName(ast, &startToken)) return; const Token &tok = tokenAt(startToken); if (tok.generated()) return; unsigned line, column; getTokenStartPosition(startToken, &line, &column); const unsigned length = tok.utf16chars(); const Result use(line, column, length, SemanticHighlighter::TypeUse); addUse(use); } bool CheckSymbols::isTemplateClass(Symbol *symbol) const { if (symbol) { if (Template *templ = symbol->asTemplate()) { if (Symbol *declaration = templ->declaration()) { return declaration->isClass() || declaration->isForwardClassDeclaration() || declaration->isTypedef(); } } } return false; } bool CheckSymbols::maybeAddTypeOrStatic(const QList &candidates, NameAST *ast) { unsigned startToken; if (!acceptName(ast, &startToken)) return false; const Token &tok = tokenAt(startToken); if (tok.generated()) return false; foreach (const LookupItem &r, candidates) { Symbol *c = r.declaration(); if (c->isUsingDeclaration()) // skip using declarations... continue; else if (c->isUsingNamespaceDirective()) // ... and using namespace directives. continue; else if (c->isTypedef() || c->isNamespace() || c->isStatic() || //consider also static variable c->isClass() || c->isEnum() || isTemplateClass(c) || c->isForwardClassDeclaration() || c->isTypenameArgument() || c->enclosingEnum() != nullptr) { unsigned line, column; getTokenStartPosition(startToken, &line, &column); const unsigned length = tok.utf16chars(); Kind kind = SemanticHighlighter::TypeUse; if (c->enclosingEnum() != nullptr) kind = SemanticHighlighter::EnumerationUse; else if (c->isStatic()) // treat static variable as a field(highlighting) kind = SemanticHighlighter::FieldUse; const Result use(line, column, length, kind); addUse(use); return true; } } return false; } bool CheckSymbols::maybeAddField(const QList &candidates, NameAST *ast) { unsigned startToken; if (!acceptName(ast, &startToken)) return false; const Token &tok = tokenAt(startToken); if (tok.generated()) return false; foreach (const LookupItem &r, candidates) { Symbol *c = r.declaration(); if (!c) continue; else if (!c->isDeclaration()) return false; else if (!(c->enclosingScope() && c->enclosingScope()->isClass())) return false; // shadowed else if (c->isTypedef() || (c->type() && c->type()->isFunctionType())) return false; // shadowed unsigned line, column; getTokenStartPosition(startToken, &line, &column); const unsigned length = tok.utf16chars(); const Result use(line, column, length, SemanticHighlighter::FieldUse); addUse(use); return true; } return false; } bool CheckSymbols::maybeAddFunction(const QList &candidates, NameAST *ast, unsigned argumentCount, FunctionKind functionKind) { unsigned startToken = ast->firstToken(); bool isDestructor = false; bool isConstructor = false; if (DestructorNameAST *dtor = ast->asDestructorName()) { isDestructor = true; if (dtor->unqualified_name) startToken = dtor->unqualified_name->firstToken(); } const Token &tok = tokenAt(startToken); if (tok.generated()) return false; enum { Match_None, Match_TooManyArgs, Match_TooFewArgs, Match_Ok } matchType = Match_None; Kind kind = functionKind == FunctionDeclaration ? SemanticHighlighter::FunctionDeclarationUse : SemanticHighlighter::FunctionUse; foreach (const LookupItem &r, candidates) { Symbol *c = r.declaration(); // Skip current if there's no declaration or name. if (!c || !c->name()) continue; // In addition check for destructors, since the leading ~ is not taken into consideration. // We don't want to compare destructors with something else or the other way around. if (isDestructor != c->name()->isDestructorNameId()) continue; isConstructor = isConstructorDeclaration(c); Function *funTy = c->type()->asFunctionType(); if (!funTy) { //Try to find a template function if (Template * t = r.type()->asTemplateType()) if ((c = t->declaration())) funTy = c->type()->asFunctionType(); } if (!funTy || funTy->isAmbiguous()) continue; // TODO: add diagnostic messages and color call-operators calls too? const bool isVirtual = funTy->isVirtual(); Kind matchingKind; if (functionKind == FunctionDeclaration) { matchingKind = isVirtual ? SemanticHighlighter::VirtualFunctionDeclarationUse : SemanticHighlighter::FunctionDeclarationUse; } else { matchingKind = isVirtual ? SemanticHighlighter::VirtualMethodUse : SemanticHighlighter::FunctionUse; } if (argumentCount < funTy->minimumArgumentCount()) { if (matchType != Match_Ok) { kind = matchingKind; matchType = Match_TooFewArgs; } } else if (argumentCount > funTy->argumentCount() && !funTy->isVariadic()) { if (matchType != Match_Ok) { matchType = Match_TooManyArgs; kind = matchingKind; } } else { matchType = Match_Ok; kind = matchingKind; if (isVirtual) break; // else continue, to check if there is a matching candidate which is virtual } } if (matchType != Match_None) { // decide how constructor and destructor should be highlighted if (highlightCtorDtorAsType && (isConstructor || isDestructor) && maybeType(ast->name) && kind == SemanticHighlighter::FunctionDeclarationUse) { return false; } unsigned line, column; getTokenStartPosition(startToken, &line, &column); const unsigned length = tok.utf16chars(); // Add a diagnostic message if argument count does not match if (matchType == Match_TooFewArgs) warning(line, column, QCoreApplication::translate("CplusPlus::CheckSymbols", "Too few arguments"), length); else if (matchType == Match_TooManyArgs) warning(line, column, QCoreApplication::translate("CPlusPlus::CheckSymbols", "Too many arguments"), length); const Result use(line, column, length, kind); addUse(use); return true; } return false; } NameAST *CheckSymbols::declaratorId(DeclaratorAST *ast) const { if (ast && ast->core_declarator) { if (NestedDeclaratorAST *nested = ast->core_declarator->asNestedDeclarator()) return declaratorId(nested->declarator); if (DeclaratorIdAST *declId = ast->core_declarator->asDeclaratorId()) return declId->name; } return nullptr; } bool CheckSymbols::maybeType(const Name *name) const { if (name) { if (const Identifier *ident = name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialTypes.contains(id)) return true; } } return false; } bool CheckSymbols::maybeField(const Name *name) const { if (name) { if (const Identifier *ident = name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialFields.contains(id)) return true; } } return false; } bool CheckSymbols::maybeStatic(const Name *name) const { if (name) { if (const Identifier *ident = name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialStatics.contains(id)) return true; } } return false; } bool CheckSymbols::maybeFunction(const Name *name) const { if (name) { if (const Identifier *ident = name->identifier()) { const QByteArray id = QByteArray::fromRawData(ident->chars(), ident->size()); if (_potentialFunctions.contains(id)) return true; } } return false; } void CheckSymbols::flush() { _lineOfLastUsage = 0; if (_usages.isEmpty()) return; Utils::sort(_usages, sortByLinePredicate); reportResults(_usages); int cap = _usages.capacity(); _usages.clear(); _usages.reserve(cap); } bool CheckSymbols::isConstructorDeclaration(Symbol *declaration) { Class *clazz = declaration->enclosingClass(); if (clazz && clazz->name()) return declaration->name()->match(clazz->name()); return false; }