From 0c0889e331305ea5b4f5c7a58c4a0e82da6111cd Mon Sep 17 00:00:00 2001 From: Dimitri van Heesch Date: Mon, 26 Oct 2020 20:41:56 +0100 Subject: Refactoring: introduce SymbolResolver to group symbol lookup routines - Main goal was to avoid use of global state. --- src/CMakeLists.txt | 1 + src/classdef.cpp | 4 +- src/code.l | 40 +- src/doxygen.cpp | 66 ++- src/pycode.l | 12 +- src/symbolresolver.cpp | 1149 ++++++++++++++++++++++++++++++++++++++++++++++++ src/symbolresolver.h | 93 ++++ src/util.cpp | 1045 ++----------------------------------------- src/util.h | 84 ---- 9 files changed, 1331 insertions(+), 1163 deletions(-) create mode 100644 src/symbolresolver.cpp create mode 100644 src/symbolresolver.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c3d6f13..68de622 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -296,6 +296,7 @@ add_library(doxymain STATIC searchindex.cpp sqlite3gen.cpp stlsupport.cpp + symbolresolver.cpp tagreader.cpp template.cpp textdocvisitor.cpp diff --git a/src/classdef.cpp b/src/classdef.cpp index 91730a0..ffbbbb4 100644 --- a/src/classdef.cpp +++ b/src/classdef.cpp @@ -50,6 +50,7 @@ #include "namespacedef.h" #include "membergroup.h" #include "definitionimpl.h" +#include "symbolresolver.h" //----------------------------------------------------------------------------- @@ -3231,7 +3232,8 @@ void ClassDefImpl::addTypeConstraint(const QCString &typeConstraint,const QCStri //printf("addTypeConstraint(%s,%s)\n",type.data(),typeConstraint.data()); static bool hideUndocRelation = Config_getBool(HIDE_UNDOC_RELATIONS); if (typeConstraint.isEmpty() || type.isEmpty()) return; - ClassDef *cd = const_cast(getResolvedClass(this,getFileDef(),typeConstraint)); + SymbolResolver resolver(getFileDef()); + ClassDef *cd = const_cast(resolver.resolveClass(this,typeConstraint)); if (cd==0 && !hideUndocRelation) { cd = new ClassDefImpl(getDefFileName(),getDefLine(),getDefColumn(),typeConstraint,ClassDef::Class); diff --git a/src/code.l b/src/code.l index 6308dff..4ebd526 100644 --- a/src/code.l +++ b/src/code.l @@ -58,6 +58,7 @@ #include "namespacedef.h" #include "tooltip.h" #include "scopedtypevariant.h" +#include "symbolresolver.h" // Toggle for some debugging info //#define DBG_CTX(x) fprintf x @@ -176,6 +177,7 @@ struct codeYY_state VariableContext theVarContext; CallContext theCallContext; TooltipManager tooltipManager; + SymbolResolver symbolResolver; }; static bool isCastKeyword(const QCString &s); @@ -757,7 +759,7 @@ NUMBER {INTEGER_NUMBER}|{FLOAT_NUMBER} yyextra->scopeStack.push(CLASSBLOCK); pushScope(yyscanner,yyextra->curClassName); DBG_CTX((stderr,"***** yyextra->curClassName=%s\n",yyextra->curClassName.data())); - if (getResolvedClass(yyextra->currentDefinition,yyextra->sourceFileDef,yyextra->curClassName)==0) + if (yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,yyextra->curClassName)==0) { DBG_CTX((stderr,"Adding new class %s\n",yyextra->curClassName.data())); ScopedTypeVariant var(yyextra->curClassName); @@ -771,7 +773,7 @@ NUMBER {INTEGER_NUMBER}|{FLOAT_NUMBER} { bcd = dynamic_cast(it->second.globalDef()); } - if (bcd==0) bcd=getResolvedClass(yyextra->currentDefinition,yyextra->sourceFileDef,s); + if (bcd==0) bcd=yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,s); if (bcd && bcd->name()!=yyextra->curClassName) { var.localDef()->insertBaseClass(bcd->name()); @@ -1699,7 +1701,7 @@ NUMBER {INTEGER_NUMBER}|{FLOAT_NUMBER} { QCString scope = yyextra->name.left((uint)index); if (!yyextra->classScope.isEmpty()) scope.prepend(yyextra->classScope+"::"); - const ClassDef *cd=getResolvedClass(Doxygen::globalScope,yyextra->sourceFileDef,scope); + const ClassDef *cd=yyextra->symbolResolver.resolveClass(Doxygen::globalScope,scope); if (cd) { setClassScope(yyscanner,cd->name()); @@ -2222,7 +2224,7 @@ static void addVariable(yyscan_t yyscanner,QCString type,QCString name) } else { - const ClassDef *varDef = getResolvedClass(yyextra->currentDefinition,yyextra->sourceFileDef,ltype); + const ClassDef *varDef = yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,ltype); int i=0; if (varDef) { @@ -2570,11 +2572,11 @@ static const ClassDef *stripClassName(yyscan_t yyscanner,const char *s,const Def const ClassDef *cd=0; if (!yyextra->classScope.isEmpty()) { - cd=getResolvedClass(d,yyextra->sourceFileDef,yyextra->classScope+"::"+clName); + cd=yyextra->symbolResolver.resolveClass(d,yyextra->classScope+"::"+clName); } if (cd==0) { - cd=getResolvedClass(d,yyextra->sourceFileDef,clName); + cd=yyextra->symbolResolver.resolveClass(d,clName); } //printf("stripClass trying '%s' = %p\n",clName.data(),cd); if (cd) @@ -2632,7 +2634,7 @@ static const MemberDef *setCallContextForVar(yyscan_t yyscanner,const QCString & DBG_CTX((stderr,"local variable?\n")); if (mcv->type()!=ScopedTypeVariant::Dummy) // locally found variable { - DBG_CTX((stderr,"local var '%s' mcd=%s\n",name.data(),mcv.name().data())); + DBG_CTX((stderr,"local var '%s' mcd=%s\n",name.data(),mcv->name().data())); yyextra->theCallContext.setScope(*mcv); } } @@ -2843,7 +2845,8 @@ static void generateClassOrGlobalLink(yyscan_t yyscanner, { const Definition *d = yyextra->currentDefinition; //printf("d=%s yyextra->sourceFileDef=%s\n",d?d->name().data():"",yyextra->sourceFileDef?yyextra->sourceFileDef->name().data():""); - cd = getResolvedClass(d,yyextra->sourceFileDef,className,&md); + cd = yyextra->symbolResolver.resolveClass(d,className); + md = yyextra->symbolResolver.getTypedef(); DBG_CTX((stderr,"non-local variable name=%s cd=%s md=%s!\n", className.data(),cd?cd->name().data():"", md?md->name().data():"")); @@ -2853,7 +2856,8 @@ static void generateClassOrGlobalLink(yyscan_t yyscanner, DBG_CTX((stderr,"bareName=%s\n",bareName.data())); if (bareName!=className) { - cd=getResolvedClass(d,yyextra->sourceFileDef,bareName,&md); // try unspecialized version + cd = yyextra->symbolResolver.resolveClass(d,bareName); // try unspecialized version + md = yyextra->symbolResolver.getTypedef(); } } const NamespaceDef *nd = getResolvedNamespace(className); @@ -2935,19 +2939,18 @@ static void generateClassOrGlobalLink(yyscan_t yyscanner, { if (md==0) // not found as a typedef { - AccessStack accessStack; md = setCallContextForVar(yyscanner,clName); //printf("setCallContextForVar(%s) md=%p yyextra->currentDefinition=%p\n",clName,md,yyextra->currentDefinition); if (md && yyextra->currentDefinition) { DBG_CTX((stderr,"%s accessible from %s? %d md->getOuterScope=%s\n", md->name().data(),yyextra->currentDefinition->name().data(), - isAccessibleFrom(accessStack,yyextra->currentDefinition,yyextra->sourceFileDef,md), + yyextra->symbolResolver.isAccessibleFrom(yyextra->currentDefinition,md), md->getOuterScope()->name().data())); } if (md && yyextra->currentDefinition && - isAccessibleFrom(accessStack,yyextra->currentDefinition,yyextra->sourceFileDef,md)==-1) + yyextra->symbolResolver.isAccessibleFrom(yyextra->currentDefinition,md)==-1) { md=0; // variable not accessible } @@ -3134,7 +3137,7 @@ static void generateMemberLink(yyscan_t yyscanner, } else // variable not in current context, maybe it is in a parent context { - const ClassDef *vcd = getResolvedClass(yyextra->currentDefinition,yyextra->sourceFileDef,yyextra->classScope); + const ClassDef *vcd = yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,yyextra->classScope); if (vcd && vcd->isLinkable()) { //printf("Found class %s for variable '%s'\n",yyextra->classScope.data(),varName.data()); @@ -3381,11 +3384,8 @@ static void writeObjCMethodCall(yyscan_t yyscanner,ObjCCallCtx *ctx) } else { - ctx->objectType = getResolvedClass( - yyextra->currentDefinition, - yyextra->sourceFileDef, - ctx->objectTypeOrName, - &ctx->method); + ctx->objectType = yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,ctx->objectTypeOrName); + ctx->method = yyextra->symbolResolver.getTypedef(); } //printf(" object is class? %p\n",ctx->objectType); if (ctx->objectType) // found class @@ -3547,8 +3547,7 @@ static void writeObjCMethodCall(yyscan_t yyscanner,ObjCCallCtx *ctx) } else // object still needs to be resolved { - const ClassDef *cd = getResolvedClass(yyextra->currentDefinition, - yyextra->sourceFileDef, object); + const ClassDef *cd = yyextra->symbolResolver.resolveClass(yyextra->currentDefinition, object); if (cd && cd->isLinkable()) { if (ctx->objectType==0) ctx->objectType=cd; @@ -3836,6 +3835,7 @@ void CCodeParser::parseCode(CodeOutputInterface &od,const char *className,const yyextra->searchCtx = searchCtx; yyextra->collectXRefs = collectXRefs; yyextra->inFunctionTryBlock = FALSE; + yyextra->symbolResolver.setFileScope(fd); if (startLine!=-1) yyextra->yyLineNr = startLine; diff --git a/src/doxygen.cpp b/src/doxygen.cpp index 988b308..b459a4b 100644 --- a/src/doxygen.cpp +++ b/src/doxygen.cpp @@ -106,6 +106,7 @@ #include "stlsupport.h" #include "threadpool.h" #include "clangparser.h" +#include "symbolresolver.h" // provided by the generated file resources.cpp extern void initResources(); @@ -1813,7 +1814,8 @@ static void findUsingDeclarations(const Entry *root) // vector -> std::vector if (usingCd==0) { - usingCd = const_cast(getResolvedClass(nd,fd,name)); // try via resolving (see also bug757509) + SymbolResolver resolver(fd); + usingCd = const_cast(resolver.resolveClass(nd,name)); // try via resolving (see also bug757509) } if (usingCd==0) { @@ -1879,7 +1881,8 @@ static void findUsingDeclImports(const Entry *root) { QCString scope=root->name.left(i); QCString memName=root->name.right(root->name.length()-i-2); - const ClassDef *bcd = getResolvedClass(cd,0,scope); // todo: file in fileScope parameter + SymbolResolver resolver; + const ClassDef *bcd = resolver.resolveClass(cd,scope); // todo: file in fileScope parameter if (bcd && bcd!=cd) { //printf("found class %s memName=%s\n",bcd->name().data(),memName.data()); @@ -2433,8 +2436,9 @@ static bool isVarWithConstructor(const Entry *root) bool typePtrType = false; QCString type; Definition *ctx = 0; - FileDef *fd = 0; + FileDef *fd = root->fileDef(); int ti; + SymbolResolver resolver(fd); //printf("isVarWithConstructor(%s)\n",rootNav->name().data()); if (root->parent()->section & Entry::COMPOUND_MASK) @@ -2442,9 +2446,7 @@ static bool isVarWithConstructor(const Entry *root) result=FALSE; goto done; } - else if ((fd = root->fileDef()) && - (fd->name().right(2)==".c" || fd->name().right(2)==".h") - ) + else if (fd->name().right(2)==".c" || fd->name().right(2)==".h") { // inside a .c file result=FALSE; goto done; @@ -2467,10 +2469,10 @@ static bool isVarWithConstructor(const Entry *root) //if (type.left(6)=="const ") type=type.right(type.length()-6); if (!typePtrType) { - typeIsClass = getResolvedClass(ctx,fd,type)!=0; + typeIsClass = resolver.resolveClass(ctx,type)!=0; if (!typeIsClass && (ti=type.find('<'))!=-1) { - typeIsClass=getResolvedClass(ctx,fd,type.left(ti))!=0; + typeIsClass=resolver.resolveClass(ctx,type.left(ti))!=0; } } if (typeIsClass) // now we still have to check if the arguments are @@ -2505,7 +2507,7 @@ static bool isVarWithConstructor(const Entry *root) result=FALSE; goto done; } - if (a.type.isEmpty() || getResolvedClass(ctx,fd,a.type)!=0) + if (a.type.isEmpty() || resolver.resolveClass(ctx,a.type)!=0) { result=FALSE; // arg type is a known type goto done; @@ -3810,13 +3812,14 @@ static ClassDef *findClassWithinClassContext(Definition *context,ClassDef *cd,co return result; } FileDef *fd=cd->getFileDef(); + SymbolResolver resolver(fd); if (context && cd!=context) { - result = const_cast(getResolvedClass(context,0,name,0,0,TRUE,TRUE)); + result = const_cast(resolver.resolveClass(context,name,true,true)); } if (result==0) { - result = const_cast(getResolvedClass(cd,fd,name,0,0,TRUE,TRUE)); + result = const_cast(resolver.resolveClass(cd,name,true,true)); } if (result==0) // try direct class, needed for namespaced classes imported via tag files (see bug624095) { @@ -3875,12 +3878,8 @@ static void findUsedClassesForClass(const Entry *root, while (!found && extractClassNameFromType(type,pos,usedClassName,templSpec,root->lang)!=-1) { // find the type (if any) that matches usedClassName - const ClassDef *typeCd = getResolvedClass(masterCd, - masterCd->getFileDef(), - usedClassName, - 0,0, - FALSE,TRUE - ); + SymbolResolver resolver(masterCd->getFileDef()); + const ClassDef *typeCd = resolver.resolveClass(masterCd,usedClassName,false,true); //printf("====> usedClassName=%s -> typeCd=%s\n", // usedClassName.data(),typeCd?typeCd->name().data():""); if (typeCd) @@ -4259,17 +4258,15 @@ static bool findClassRelation( //baseClassName=stripTemplateSpecifiersFromScope // (removeRedundantWhiteSpace(baseClassName),TRUE, // &stripped); - const MemberDef *baseClassTypeDef=0; - QCString templSpec; + SymbolResolver resolver(cd->getFileDef()); ClassDef *baseClass=const_cast( - getResolvedClass(explicitGlobalScope ? Doxygen::globalScope : context, - cd->getFileDef(), + resolver.resolveClass(explicitGlobalScope ? Doxygen::globalScope : context, baseClassName, - &baseClassTypeDef, - &templSpec, mode==Undocumented, - TRUE + true )); + const MemberDef *baseClassTypeDef = resolver.getTypedef(); + QCString templSpec = resolver.getTemplateSpec(); //printf("baseClassName=%s baseClass=%p cd=%p explicitGlobalScope=%d\n", // baseClassName.data(),baseClass,cd,explicitGlobalScope); //printf(" scope='%s' baseClassName='%s' baseClass=%s templSpec=%s\n", @@ -4321,14 +4318,12 @@ static bool findClassRelation( templSpec=removeRedundantWhiteSpace(baseClassName.mid(i,e-i)); baseClassName=baseClassName.left(i)+baseClassName.right(baseClassName.length()-e); baseClass=const_cast( - getResolvedClass(explicitGlobalScope ? Doxygen::globalScope : context, - cd->getFileDef(), + resolver.resolveClass(explicitGlobalScope ? Doxygen::globalScope : context, baseClassName, - &baseClassTypeDef, - 0, //&templSpec, mode==Undocumented, - TRUE + true )); + baseClassTypeDef = resolver.getTypedef(); //printf("baseClass=%p -> baseClass=%s templSpec=%s\n", // baseClass,baseClassName.data(),templSpec.data()); } @@ -4354,18 +4349,16 @@ static bool findClassRelation( //printf("1. found=%d\n",found); if (!found && si!=-1) { - QCString tmpTemplSpec; // replace any namespace aliases replaceNamespaceAliases(baseClassName,si); baseClass=const_cast( - getResolvedClass(explicitGlobalScope ? Doxygen::globalScope : context, - cd->getFileDef(), + resolver.resolveClass(explicitGlobalScope ? Doxygen::globalScope : context, baseClassName, - &baseClassTypeDef, - &tmpTemplSpec, mode==Undocumented, - TRUE + true )); + baseClassTypeDef = resolver.getTypedef(); + QCString tmpTemplSpec = resolver.getTemplateSpec(); found=baseClass!=0 && baseClass!=cd; if (found) templSpec = tmpTemplSpec; } @@ -5025,7 +5018,8 @@ static void addMemberDocs(const Entry *root, static const ClassDef *findClassDefinition(FileDef *fd,NamespaceDef *nd, const char *scopeName) { - const ClassDef *tcd = getResolvedClass(nd,fd,scopeName,0,0,TRUE,TRUE); + SymbolResolver resolver(fd); + const ClassDef *tcd = resolver.resolveClass(nd,scopeName,true,true); return tcd; } diff --git a/src/pycode.l b/src/pycode.l index 853e5cd..58f3daa 100644 --- a/src/pycode.l +++ b/src/pycode.l @@ -53,6 +53,7 @@ #include "namespacedef.h" #include "tooltip.h" #include "scopedtypevariant.h" +#include "symbolresolver.h" // Toggle for some debugging info //#define DBG_CTX(x) fprintf x @@ -107,6 +108,7 @@ struct pycodeYY_state VariableContext theVarContext; CallContext theCallContext; TooltipManager tooltipManager; + SymbolResolver symbolResolver; }; @@ -407,7 +409,7 @@ TARGET ({IDENTIFIER}|"("{TARGET_LIST}")"|"["{TARGET_LIST}"]"|{ATTRIBU // Try to find class in global scope if (baseDefToAdd==0) { - baseDefToAdd=getResolvedClass(yyextra->currentDefinition,yyextra->sourceFileDef,s); + baseDefToAdd=yyextra->symbolResolver.resolveClass(yyextra->currentDefinition,s); } if (baseDefToAdd && baseDefToAdd->name()!=yyextra->curClassName) @@ -966,11 +968,11 @@ static const ClassDef *stripClassName(yyscan_t yyscanner,const char *s,Definitio const ClassDef *cd=0; if (!yyextra->classScope.isEmpty()) { - cd=getResolvedClass(d,yyextra->sourceFileDef,yyextra->classScope+"::"+clName); + cd=yyextra->symbolResolver.resolveClass(d,yyextra->classScope+"::"+clName); } if (cd==0) { - cd=getResolvedClass(d,yyextra->sourceFileDef,clName); + cd=yyextra->symbolResolver.resolveClass(d,clName); } if (cd) { @@ -1280,7 +1282,8 @@ static void generateClassOrGlobalLink(yyscan_t yyscanner, Definition *d = yyextra->currentDefinition; QCString scope = substitute(className,".","::"); - cd = getResolvedClass(d,yyextra->sourceFileDef,substitute(className,".","::"),&md); + cd = yyextra->symbolResolver.resolveClass(d,substitute(className,".","::")); + md = yyextra->symbolResolver.getTypedef(); DBG_CTX((stderr,"d=%s yyextra->sourceFileDef=%s\n", d?d->displayName().data():"", @@ -1578,6 +1581,7 @@ void PythonCodeParser::parseCode(CodeOutputInterface &codeOutIntf, yyextra->exampleBlock = isExampleBlock; yyextra->exampleName = exampleName; yyextra->sourceFileDef = fileDef; + yyextra->symbolResolver.setFileScope(fileDef); bool cleanupSourceDef = FALSE; if (yyextra->exampleBlock && fileDef==0) diff --git a/src/symbolresolver.cpp b/src/symbolresolver.cpp new file mode 100644 index 0000000..1c6baf4 --- /dev/null +++ b/src/symbolresolver.cpp @@ -0,0 +1,1149 @@ +/****************************************************************************** + * + * Copyright (C) 1997-2020 by Dimitri van Heesch. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation under the terms of the GNU General Public License is hereby + * granted. No representations are made about the suitability of this software + * for any purpose. It is provided "as is" without express or implied warranty. + * See the GNU General Public License for more details. + * + * Documents produced by Doxygen are derivative works derived from the + * input used in their production; they are not affected by this license. + * + */ + +#include +#include +#include + +#include "symbolresolver.h" +#include "util.h" +#include "doxygen.h" +#include "namespacedef.h" +#include "config.h" +#include "defargs.h" + +static std::mutex g_cacheMutex; + +//-------------------------------------------------------------------------------------- + +/** Helper class representing the stack of items considered while resolving + * the scope. + */ +class AccessStack +{ + /** Element in the stack. */ + struct AccessElem + { + AccessElem(const Definition *d,const FileDef *f,const Definition *i,QCString e = QCString()) : scope(d), fileScope(f), item(i), expScope(e) {} + const Definition *scope; + const FileDef *fileScope; + const Definition *item; + QCString expScope; + }; + public: + void push(const Definition *scope,const FileDef *fileScope,const Definition *item) + { + m_elements.push_back(AccessElem(scope,fileScope,item)); + } + void push(const Definition *scope,const FileDef *fileScope,const Definition *item,const QCString &expScope) + { + m_elements.push_back(AccessElem(scope,fileScope,item,expScope)); + } + void pop() + { + if (!m_elements.empty()) m_elements.pop_back(); + } + bool find(const Definition *scope,const FileDef *fileScope, const Definition *item) + { + auto it = std::find_if(m_elements.begin(),m_elements.end(), + [&](const AccessElem &e) { return e.scope==scope && e.fileScope==fileScope && e.item==item; }); + return it!=m_elements.end(); + } + bool find(const Definition *scope,const FileDef *fileScope, const Definition *item,const QCString &expScope) + { + auto it = std::find_if(m_elements.begin(),m_elements.end(), + [&](const AccessElem &e) { return e.scope==scope && e.fileScope==fileScope && e.item==item && e.expScope==expScope; }); + return it!=m_elements.end(); + } + void clear() + { + m_elements.clear(); + } + + private: + std::vector m_elements; +}; + +//-------------------------------------------------------------------------------------- + +using VisitedNamespaces = std::unordered_map; + +//-------------------------------------------------------------------------------------- + +struct SymbolResolver::Private +{ + public: + Private(const FileDef *f) : m_fileScope(f) {} + void reset() + { + m_resolvedTypedefs.clear(); + resolvedType.resize(0); + typeDef = 0; + templateSpec.resize(0); + } + void setFileScope(const FileDef *fileScope) + { + m_fileScope = fileScope; + } + + QCString resolvedType; + const MemberDef *typeDef = 0; + QCString templateSpec; + + const ClassDef *getResolvedClassRec( + const Definition *scope, // in + const char *n, // in + const MemberDef **pTypeDef, // out + QCString *pTemplSpec, // out + QCString *pResolvedType); // out + + int isAccessibleFrom( AccessStack &accessStack, + const Definition *scope, + const Definition *item); + + int isAccessibleFromWithExpScope( + VisitedNamespaces &visitedNamespaces, + AccessStack &accessStack, + const Definition *scope, + const Definition *item, + const QCString &explicitScopePart); + + private: + void getResolvedSymbol(const Definition *scope, // in + const Definition *d, // in + const QCString &explicitScopePart, // in + const std::unique_ptr &actTemplParams, // in + int &minDistance, // input + const ClassDef *&bestMatch, // out + const MemberDef *&bestTypedef, // out + QCString &bestTemplSpec, // out + QCString &bestResolvedType // out + ); + + const ClassDef *newResolveTypedef( + const Definition *scope, // in + const MemberDef *md, // in + const MemberDef **pMemType, // out + QCString *pTemplSpec, // out + QCString *pResolvedType, // out + const std::unique_ptr &actTemplParams = std::unique_ptr() + ); + + const Definition *followPath(const Definition *start,const QCString &path); + + const Definition *endOfPathIsUsedClass(const SDict *cl,const QCString &localName); + + bool accessibleViaUsingNamespace(StringUnorderedSet &visited, + const NamespaceSDict *nl, + const Definition *item, + const QCString &explicitScopePart=""); + bool accessibleViaUsingClass(const SDict *cl, + const Definition *item, + const QCString &explicitScopePart="" + ); + QCString substTypedef(const Definition *scope,const QCString &name, + const MemberDef **pTypeDef=0); + + const FileDef *m_fileScope; + std::unordered_map m_resolvedTypedefs; +}; + + + +const ClassDef *SymbolResolver::Private::getResolvedClassRec( + const Definition *scope, + const char *n, + const MemberDef **pTypeDef, + QCString *pTemplSpec, + QCString *pResolvedType) +{ + if (n==0 || *n=='\0') return 0; + //static int level=0; + //fprintf(stderr,"%d [getResolvedClassRec(%s,%s)\n",level++,scope?scope->name().data():"",n); + QCString name; + QCString explicitScopePart; + QCString strippedTemplateParams; + name=stripTemplateSpecifiersFromScope + (removeRedundantWhiteSpace(n),TRUE, + &strippedTemplateParams); + std::unique_ptr actTemplParams; + if (!strippedTemplateParams.isEmpty()) // template part that was stripped + { + actTemplParams = stringToArgumentList(scope->getLanguage(),strippedTemplateParams); + } + + int qualifierIndex = computeQualifiedIndex(name); + //printf("name=%s qualifierIndex=%d\n",name.data(),qualifierIndex); + if (qualifierIndex!=-1) // qualified name + { + // split off the explicit scope part + explicitScopePart=name.left(qualifierIndex); + // todo: improve namespace alias substitution + replaceNamespaceAliases(explicitScopePart,explicitScopePart.length()); + name=name.mid(qualifierIndex+2); + } + + if (name.isEmpty()) + { + //fprintf(stderr,"%d ] empty name\n",--level); + return 0; // empty name + } + + //printf("Looking for symbol %s\n",name.data()); + auto range = Doxygen::symbolMap.find(name); + // the -g (for C# generics) and -p (for ObjC protocols) are now already + // stripped from the key used in the symbolMap, so that is not needed here. + if (range.first==range.second) + { + range = Doxygen::symbolMap.find(name+"-p"); + if (range.first==range.second) + { + //fprintf(stderr,"%d ] no such symbol!\n",--level); + return 0; + } + } + //printf("found symbol!\n"); + + bool hasUsingStatements = + (m_fileScope && ((m_fileScope->getUsedNamespaces() && + m_fileScope->getUsedNamespaces()->count()>0) || + (m_fileScope->getUsedClasses() && + m_fileScope->getUsedClasses()->count()>0)) + ); + //printf("hasUsingStatements=%d\n",hasUsingStatements); + // Since it is often the case that the same name is searched in the same + // scope over an over again (especially for the linked source code generation) + // we use a cache to collect previous results. This is possible since the + // result of a lookup is deterministic. As the key we use the concatenated + // scope, the name to search for and the explicit scope prefix. The speedup + // achieved by this simple cache can be enormous. + int scopeNameLen = scope->name().length()+1; + int nameLen = name.length()+1; + int explicitPartLen = explicitScopePart.length(); + int fileScopeLen = hasUsingStatements ? 1+m_fileScope->absFilePath().length() : 0; + + // below is a more efficient coding of + // QCString key=scope->name()+"+"+name+"+"+explicitScopePart; + QCString key(scopeNameLen+nameLen+explicitPartLen+fileScopeLen+1); + char *pk=key.rawData(); + qstrcpy(pk,scope->name()); *(pk+scopeNameLen-1)='+'; + pk+=scopeNameLen; + qstrcpy(pk,name); *(pk+nameLen-1)='+'; + pk+=nameLen; + qstrcpy(pk,explicitScopePart); + pk+=explicitPartLen; + + // if a file scope is given and it contains using statements we should + // also use the file part in the key (as a class name can be in + // two different namespaces and a using statement in a file can select + // one of them). + if (hasUsingStatements) + { + // below is a more efficient coding of + // key+="+"+m_fileScope->name(); + *pk++='+'; + qstrcpy(pk,m_fileScope->absFilePath()); + pk+=fileScopeLen-1; + } + *pk='\0'; + + LookupInfo *pval = 0; + { + std::lock_guard lock(g_cacheMutex); + pval=Doxygen::lookupCache->find(key.str()); + //printf("Searching for %s result=%p\n",key.data(),pval); + if (pval) + { + //printf("LookupInfo %p %p '%s' %p\n", + // pval->classDef, pval->typeDef, pval->templSpec.data(), + // pval->resolvedType.data()); + if (pTemplSpec) *pTemplSpec=pval->templSpec; + if (pTypeDef) *pTypeDef=pval->typeDef; + if (pResolvedType) *pResolvedType=pval->resolvedType; + //fprintf(stderr,"%d ] cachedMatch=%s\n",--level, + // pval->classDef?pval->classDef->name().data():""); + //if (pTemplSpec) + // printf("templSpec=%s\n",pTemplSpec->data()); + return pval->classDef; + } + else // not found yet; we already add a 0 to avoid the possibility of + // endless recursion. + { + pval = Doxygen::lookupCache->insert(key.str(),LookupInfo()); + } + } + + const ClassDef *bestMatch=0; + const MemberDef *bestTypedef=0; + QCString bestTemplSpec; + QCString bestResolvedType; + int minDistance=10000; // init at "infinite" + + for (auto it=range.first ; it!=range.second; ++it) + { + Definition *d = it->second; + getResolvedSymbol(scope,d,explicitScopePart,actTemplParams, + minDistance,bestMatch,bestTypedef,bestTemplSpec,bestResolvedType); + } + + if (pTypeDef) + { + *pTypeDef = bestTypedef; + } + if (pTemplSpec) + { + *pTemplSpec = bestTemplSpec; + } + if (pResolvedType) + { + *pResolvedType = bestResolvedType; + } + + //printf("getResolvedClassRec: bestMatch=%p pval->resolvedType=%s\n", + // bestMatch,bestResolvedType.data()); + + if (pval) + { + std::lock_guard lock(g_cacheMutex); + pval->classDef = bestMatch; + pval->typeDef = bestTypedef; + pval->templSpec = bestTemplSpec; + pval->resolvedType = bestResolvedType; + } + //fprintf(stderr,"%d ] bestMatch=%s distance=%d\n",--level, + // bestMatch?bestMatch->name().data():"",minDistance); + //if (pTemplSpec) + // printf("templSpec=%s\n",pTemplSpec->data()); + return bestMatch; +} + +void SymbolResolver::Private::getResolvedSymbol( + const Definition *scope, // in + const Definition *d, // in + const QCString &explicitScopePart, // in + const std::unique_ptr &actTemplParams, // in + int &minDistance, // inout + const ClassDef *&bestMatch, // out + const MemberDef *&bestTypedef, // out + QCString &bestTemplSpec, // out + QCString &bestResolvedType // out + ) +{ + //fprintf(stderr,"getResolvedSymbol(%s,%s)\n",scope->name().data(),d->qualifiedName().data()); + // only look at classes and members that are enums or typedefs + if (d->definitionType()==Definition::TypeClass || + (d->definitionType()==Definition::TypeMember && + ((dynamic_cast(d))->isTypedef() || + (dynamic_cast(d))->isEnumerate()) + ) + ) + { + VisitedNamespaces visitedNamespaces; + AccessStack accessStack; + // test accessibility of definition within scope. + int distance = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,d,explicitScopePart); + //fprintf(stderr," %s; distance %s (%p) is %d\n",scope->name().data(),d->name().data(),d,distance); + if (distance!=-1) // definition is accessible + { + // see if we are dealing with a class or a typedef + if (d->definitionType()==Definition::TypeClass) // d is a class + { + const ClassDef *cd = dynamic_cast(d); + //printf("cd=%s\n",cd->name().data()); + if (!cd->isTemplateArgument()) // skip classes that + // are only there to + // represent a template + // argument + { + //printf("is not a templ arg\n"); + if (distancequalifiedName(); + } + else if (distance==minDistance && + m_fileScope && bestMatch && + m_fileScope->getUsedNamespaces() && + d->getOuterScope()->definitionType()==Definition::TypeNamespace && + bestMatch->getOuterScope()==Doxygen::globalScope + ) + { + // in case the distance is equal it could be that a class X + // is defined in a namespace and in the global scope. When searched + // in the global scope the distance is 0 in both cases. We have + // to choose one of the definitions: we choose the one in the + // namespace if the fileScope imports namespaces and the definition + // found was in a namespace while the best match so far isn't. + // Just a non-perfect heuristic but it could help in some situations + // (kdecore code is an example). + minDistance=distance; + bestMatch = cd; + bestTypedef = 0; + bestTemplSpec.resize(0); + bestResolvedType = cd->qualifiedName(); + } + } + else + { + //printf(" is a template argument!\n"); + } + } + else if (d->definitionType()==Definition::TypeMember) + { + const MemberDef *md = dynamic_cast(d); + //fprintf(stderr," member isTypedef()=%d\n",md->isTypedef()); + if (md->isTypedef()) // d is a typedef + { + QCString args=md->argsString(); + if (args.isEmpty()) // do not expand "typedef t a[4];" + { + //printf(" found typedef!\n"); + + // we found a symbol at this distance, but if it didn't + // resolve to a class, we still have to make sure that + // something at a greater distance does not match, since + // that symbol is hidden by this one. + if (distancequalifiedName(); + } + else if (md->isReference()) // external reference + { + bestMatch = 0; + bestTypedef = md; + bestTemplSpec = spec; + bestResolvedType = type; + } + else + { + bestMatch = 0; + bestTypedef = md; + bestTemplSpec.resize(0); + bestResolvedType.resize(0); + //printf(" no match\n"); + } + } + else + { + //printf(" not the best match %d min=%d\n",distance,minDistance); + } + } + else + { + //printf(" not a simple typedef\n") + } + } + else if (md->isEnumerate()) + { + if (distancequalifiedName(); + } + } + } + } // if definition accessible + else + { + //printf(" Not accessible!\n"); + } + } // if definition is a class or member + //printf(" bestMatch=%p bestResolvedType=%s\n",bestMatch,bestResolvedType.data()); +} + +const ClassDef *SymbolResolver::Private::newResolveTypedef( + const Definition *scope, // in + const MemberDef *md, // in + const MemberDef **pMemType, // out + QCString *pTemplSpec, // out + QCString *pResolvedType, // out + const std::unique_ptr &actTemplParams) // in +{ + //printf("newResolveTypedef(md=%p,cachedVal=%p)\n",md,md->getCachedTypedefVal()); + bool isCached = md->isTypedefValCached(); // value already cached + if (isCached) + { + //printf("Already cached %s->%s [%s]\n", + // md->name().data(), + // md->getCachedTypedefVal()?md->getCachedTypedefVal()->name().data():"", + // md->getCachedResolvedTypedef()?md->getCachedResolvedTypedef().data():""); + + if (pTemplSpec) *pTemplSpec = md->getCachedTypedefTemplSpec(); + if (pResolvedType) *pResolvedType = md->getCachedResolvedTypedef(); + return md->getCachedTypedefVal(); + } + //printf("new typedef\n"); + QCString qname = md->qualifiedName(); + if (m_resolvedTypedefs.find(qname.str())!=m_resolvedTypedefs.end()) + { + return 0; // typedef already done + } + + auto typedef_it = m_resolvedTypedefs.insert({qname.str(),md}).first; // put on the trace list + + const ClassDef *typeClass = md->getClassDef(); + QCString type = md->typeString(); // get the "value" of the typedef + if (typeClass && typeClass->isTemplate() && + actTemplParams && !actTemplParams->empty()) + { + type = substituteTemplateArgumentsInString(type, + typeClass->templateArguments(),actTemplParams); + } + QCString typedefValue = type; + int tl=type.length(); + int ip=tl-1; // remove * and & at the end + while (ip>=0 && (type.at(ip)=='*' || type.at(ip)=='&' || type.at(ip)==' ')) + { + ip--; + } + type=type.left(ip+1); + type.stripPrefix("const "); // strip leading "const" + type.stripPrefix("struct "); // strip leading "struct" + type.stripPrefix("union "); // strip leading "union" + int sp=0; + tl=type.length(); // length may have been changed + while (spgetOuterScope(),type, + &memTypeDef,0,pResolvedType); + // if type is a typedef then return what it resolves to. + if (memTypeDef && memTypeDef->isTypedef()) + { + result=newResolveTypedef(m_fileScope,memTypeDef,pMemType,pTemplSpec,0); + goto done; + } + else if (memTypeDef && memTypeDef->isEnumerate() && pMemType) + { + *pMemType = memTypeDef; + } + + //printf("type=%s result=%p\n",type.data(),result); + if (result==0) + { + // try unspecialized version if type is template + int si=type.findRev("::"); + int i=type.find('<'); + if (si==-1 && i!=-1) // typedef of a template => try the unspecialized version + { + if (pTemplSpec) *pTemplSpec = type.mid(i); + result = getResolvedClassRec(md->getOuterScope(),type.left(i),0,0,pResolvedType); + //printf("result=%p pRresolvedType=%s sp=%d ip=%d tl=%d\n", + // result,pResolvedType?pResolvedType->data():"",sp,ip,tl); + } + else if (si!=-1) // A::B + { + i=type.find('<',si); + if (i==-1) // Something like A::B => lookup A::B + { + i=type.length(); + } + else // Something like A::B => lookup A::B, spec= + { + if (pTemplSpec) *pTemplSpec = type.mid(i); + } + result = getResolvedClassRec(md->getOuterScope(), + stripTemplateSpecifiersFromScope(type.left(i),FALSE),0,0,pResolvedType); + } + + //if (result) ip=si+sp+1; + } + +done: + if (pResolvedType) + { + if (result) + { + *pResolvedType = result->qualifiedName(); + //printf("*pResolvedType=%s\n",pResolvedType->data()); + if (sp>0) pResolvedType->prepend(typedefValue.left(sp)); + if (ipappend(typedefValue.right(tl-ip-1)); + } + else + { + *pResolvedType = typedefValue; + } + } + + // remember computed value for next time + if (result && result->getDefFileName()!="") + // this check is needed to prevent that temporary classes that are + // introduced while parsing code fragments are being cached here. + { + //printf("setting cached typedef %p in result %p\n",md,result); + //printf("==> %s (%s,%d)\n",result->name().data(),result->getDefFileName().data(),result->getDefLine()); + //printf("*pResolvedType=%s\n",pResolvedType?pResolvedType->data():""); + const_cast(md)->cacheTypedefVal(result, + pTemplSpec ? *pTemplSpec : QCString(), + pResolvedType ? *pResolvedType : QCString() + ); + } + + m_resolvedTypedefs.erase(typedef_it); // remove from the trace list + + return result; +} + +int SymbolResolver::Private::isAccessibleFromWithExpScope( + VisitedNamespaces &visitedNamespaces, + AccessStack &accessStack, + const Definition *scope, + const Definition *item, + const QCString &explicitScopePart) +{ + if (explicitScopePart.isEmpty()) + { + // handle degenerate case where there is no explicit scope. + return isAccessibleFrom(accessStack,scope,item); + } + + if (accessStack.find(scope,m_fileScope,item,explicitScopePart)) + { + return -1; + } + accessStack.push(scope,m_fileScope,item,explicitScopePart); + + + //printf(" name().data():"", + // item?item->name().data():"", + // explicitScopePart.data()); + int result=0; // assume we found it + const Definition *newScope = followPath(scope,explicitScopePart); + if (newScope) // explicitScope is inside scope => newScope is the result + { + Definition *itemScope = item->getOuterScope(); + //printf(" scope traversal successful %s<->%s!\n",itemScope->name().data(),newScope->name().data()); + //if (newScope && newScope->definitionType()==Definition::TypeClass) + //{ + // ClassDef *cd = (ClassDef *)newScope; + // printf("---> Class %s: bases=%p\n",cd->name().data(),cd->baseClasses()); + //} + if (itemScope==newScope) // exact match of scopes => distance==0 + { + //printf("> found it\n"); + } + else if (itemScope && newScope && + itemScope->definitionType()==Definition::TypeClass && + newScope->definitionType()==Definition::TypeClass && + (dynamic_cast(newScope))->isBaseClass(dynamic_cast(itemScope),TRUE,0) + ) + { + // inheritance is also ok. Example: looking for B::I, where + // class A { public: class I {} }; + // class B : public A {} + // but looking for B::I, where + // class A { public: class I {} }; + // class B { public: class I {} }; + // will find A::I, so we still prefer a direct match and give this one a distance of 1 + result=1; + + //printf("scope(%s) is base class of newScope(%s)\n", + // scope->name().data(),newScope->name().data()); + } + else + { + int i=-1; + if (newScope->definitionType()==Definition::TypeNamespace) + { + visitedNamespaces.insert({newScope->name().str(),newScope}); + // this part deals with the case where item is a class + // A::B::C but is explicit referenced as A::C, where B is imported + // in A via a using directive. + //printf("newScope is a namespace: %s!\n",newScope->name().data()); + const NamespaceDef *nscope = dynamic_cast(newScope); + const SDict *cl = nscope->getUsedClasses(); + if (cl) + { + SDict::Iterator cli(*cl); + const Definition *cd; + for (cli.toFirst();(cd=cli.current());++cli) + { + //printf("Trying for class %s\n",cd->name().data()); + if (cd==item) + { + //printf("> class is used in this scope\n"); + goto done; + } + } + } + const NamespaceSDict *nl = nscope->getUsedNamespaces(); + if (nl) + { + NamespaceSDict::Iterator nli(*nl); + const NamespaceDef *nd; + for (nli.toFirst();(nd=nli.current());++nli) + { + if (visitedNamespaces.find(nd->name().str())==visitedNamespaces.end()) + { + //printf("Trying for namespace %s\n",nd->name().data()); + i = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,item,nd->name()); + if (i!=-1) + { + //printf("> found via explicit scope of used namespace\n"); + goto done; + } + } + } + } + } + // repeat for the parent scope + if (scope!=Doxygen::globalScope) + { + i = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope->getOuterScope(),item,explicitScopePart); + } + //printf(" | result=%d\n",i); + result = (i==-1) ? -1 : i+2; + } + } + else // failed to resolve explicitScope + { + //printf(" failed to resolve: scope=%s\n",scope->name().data()); + if (scope->definitionType()==Definition::TypeNamespace) + { + const NamespaceDef *nscope = dynamic_cast(scope); + const NamespaceSDict *nl = nscope->getUsedNamespaces(); + StringUnorderedSet visited; + if (accessibleViaUsingNamespace(visited,nl,item,explicitScopePart)) + { + //printf("> found in used namespace\n"); + goto done; + } + } + if (scope==Doxygen::globalScope) + { + if (m_fileScope) + { + const NamespaceSDict *nl = m_fileScope->getUsedNamespaces(); + StringUnorderedSet visited; + if (accessibleViaUsingNamespace(visited,nl,item,explicitScopePart)) + { + //printf("> found in used namespace\n"); + goto done; + } + } + //printf("> not found\n"); + result=-1; + } + else // continue by looking into the parent scope + { + int i=isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope->getOuterScope(),item,explicitScopePart); + //printf("> result=%d\n",i); + result= (i==-1) ? -1 : i+2; + } + } + +done: + //printf(" > result=%d\n",result); + accessStack.pop(); + return result; +} + +const Definition *SymbolResolver::Private::followPath(const Definition *start,const QCString &path) +{ + int is,ps; + int l; + const Definition *current=start; + ps=0; + //printf("followPath: start='%s' path='%s'\n",start?start->name().data():"",path.data()); + // for each part of the explicit scope + while ((is=getScopeFragment(path,ps,&l))!=-1) + { + // try to resolve the part if it is a typedef + const MemberDef *memTypeDef=0; + QCString qualScopePart = substTypedef(current,path.mid(is,l),&memTypeDef); + //printf(" qualScopePart=%s\n",qualScopePart.data()); + if (memTypeDef) + { + const ClassDef *type = newResolveTypedef(m_fileScope,memTypeDef,0,0,0); + if (type) + { + //printf("Found type %s\n",type->name().data()); + return type; + } + } + const Definition *next = current->findInnerCompound(qualScopePart); + //printf("++ Looking for %s inside %s result %s\n", + // qualScopePart.data(), + // current->name().data(), + // next?next->name().data():""); + if (next==0) // failed to follow the path + { + //printf("==> next==0!\n"); + if (current->definitionType()==Definition::TypeNamespace) + { + next = endOfPathIsUsedClass( + (dynamic_cast(current))->getUsedClasses(),qualScopePart); + } + else if (current->definitionType()==Definition::TypeFile) + { + next = endOfPathIsUsedClass( + (dynamic_cast(current))->getUsedClasses(),qualScopePart); + } + current = next; + if (current==0) break; + } + else // continue to follow scope + { + current = next; + //printf("==> current = %p\n",current); + } + ps=is+l; + } + //printf("followPath(start=%s,path=%s) result=%s\n", + // start->name().data(),path.data(),current?current->name().data():""); + return current; // path could be followed +} + +const Definition *SymbolResolver::Private::endOfPathIsUsedClass(const SDict *cl,const QCString &localName) +{ + if (cl) + { + SDict::Iterator cli(*cl); + Definition *cd; + for (cli.toFirst();(cd=cli.current());++cli) + { + if (cd->localName()==localName) + { + return cd; + } + } + } + return 0; +} + +bool SymbolResolver::Private::accessibleViaUsingNamespace(StringUnorderedSet &visited, + const NamespaceSDict *nl, + const Definition *item, + const QCString &explicitScopePart) +{ + if (nl) // check used namespaces for the class + { + NamespaceSDict::Iterator nli(*nl); + NamespaceDef *und; + int count=0; + for (nli.toFirst();(und=nli.current());++nli,count++) + { + //printf("[Trying via used namespace %s: count=%d/%d\n",und->name().data(), + // count,nl->count()); + const Definition *sc = explicitScopePart.isEmpty() ? und : followPath(und,explicitScopePart); + if (sc && item->getOuterScope()==sc) + { + //printf("] found it\n"); + return true; + } + if (item->getLanguage()==SrcLangExt_Cpp) + { + QCString key=und->name(); + if (und->getUsedNamespaces() && visited.find(key.str())==visited.end()) + { + visited.insert(key.str()); + + if (accessibleViaUsingNamespace(visited,und->getUsedNamespaces(),item,explicitScopePart)) + { + //printf("] found it via recursion\n"); + return true; + } + + visited.erase(key.str()); + } + } + //printf("] Try via used namespace done\n"); + } + } + return false; +} + + +bool SymbolResolver::Private::accessibleViaUsingClass(const SDict *cl, + const Definition *item, + const QCString &explicitScopePart) +{ + //printf("accessibleViaUsingClass(%p)\n",cl); + if (cl) // see if the class was imported via a using statement + { + SDict::Iterator cli(*cl); + Definition *ucd; + bool explicitScopePartEmpty = explicitScopePart.isEmpty(); + for (cli.toFirst();(ucd=cli.current());++cli) + { + //printf("Trying via used class %s\n",ucd->name().data()); + const Definition *sc = explicitScopePartEmpty ? ucd : followPath(ucd,explicitScopePart); + if (sc && sc==item) return TRUE; + //printf("Try via used class done\n"); + } + } + return FALSE; +} + +int SymbolResolver::Private::isAccessibleFrom(AccessStack &accessStack, + const Definition *scope, + const Definition *item) +{ + //printf("name().data(),item->name().data(),item->getOuterScope()->name().data()); + + if (accessStack.find(scope,m_fileScope,item)) + { + return -1; + } + accessStack.push(scope,m_fileScope,item); + + int result=0; // assume we found it + int i; + + Definition *itemScope=item->getOuterScope(); + bool memberAccessibleFromScope = + (item->definitionType()==Definition::TypeMember && // a member + itemScope && itemScope->definitionType()==Definition::TypeClass && // of a class + scope->definitionType()==Definition::TypeClass && // accessible + (dynamic_cast(scope))->isAccessibleMember(dynamic_cast(item)) // from scope + ); + bool nestedClassInsideBaseClass = + (item->definitionType()==Definition::TypeClass && // a nested class + itemScope && itemScope->definitionType()==Definition::TypeClass && // inside a base + scope->definitionType()==Definition::TypeClass && // class of scope + (dynamic_cast(scope))->isBaseClass(dynamic_cast(itemScope),TRUE) + ); + + if (itemScope==scope || memberAccessibleFromScope || nestedClassInsideBaseClass) + { + //printf("> found it\n"); + if (nestedClassInsideBaseClass) result++; // penalty for base class to prevent + // this is preferred over nested class in this class + // see bug 686956 + } + else if (scope==Doxygen::globalScope) + { + if (m_fileScope) + { + SDict *cl = m_fileScope->getUsedClasses(); + if (accessibleViaUsingClass(cl,item)) + { + //printf("> found via used class\n"); + goto done; + } + NamespaceSDict *nl = m_fileScope->getUsedNamespaces(); + StringUnorderedSet visited; + if (accessibleViaUsingNamespace(visited,nl,item)) + { + //printf("> found via used namespace\n"); + goto done; + } + } + //printf("> reached global scope\n"); + result=-1; // not found in path to globalScope + } + else // keep searching + { + // check if scope is a namespace, which is using other classes and namespaces + if (scope->definitionType()==Definition::TypeNamespace) + { + const NamespaceDef *nscope = dynamic_cast(scope); + //printf(" %s is namespace with %d used classes\n",nscope->name().data(),nscope->getUsedClasses()); + const SDict *cl = nscope->getUsedClasses(); + if (accessibleViaUsingClass(cl,item)) + { + //printf("> found via used class\n"); + goto done; + } + const NamespaceSDict *nl = nscope->getUsedNamespaces(); + StringUnorderedSet visited; + if (accessibleViaUsingNamespace(visited,nl,item)) + { + //printf("> found via used namespace\n"); + goto done; + } + } + // repeat for the parent scope + i=isAccessibleFrom(accessStack,scope->getOuterScope(),item); + //printf("> result=%d\n",i); + result= (i==-1) ? -1 : i+2; + } +done: + accessStack.pop(); + return result; +} + +QCString SymbolResolver::Private::substTypedef( + const Definition *scope,const QCString &name, + const MemberDef **pTypeDef) +{ + QCString result=name; + if (name.isEmpty()) return result; + + auto range = Doxygen::symbolMap.find(name); + if (range.first==range.second) + return result; // no matches + + MemberDef *bestMatch=0; + int minDistance=10000; // init at "infinite" + + for (auto it = range.first; it!=range.second; ++it) + { + Definition *d = it->second; + // only look at members + if (d->definitionType()==Definition::TypeMember) + { + // that are also typedefs + MemberDef *md = dynamic_cast(d); + if (md->isTypedef()) // d is a typedef + { + VisitedNamespaces visitedNamespaces; + AccessStack accessStack; + // test accessibility of typedef within scope. + int distance = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,d,""); + if (distance!=-1 && distancetypeString(); + if (pTypeDef) *pTypeDef=bestMatch; + } + + //printf("substTypedef(%s,%s)=%s\n",scope?scope->name().data():"", + // name.data(),result.data()); + return result; +} + +//---------------------------------------------------------------------------------------------- + + +SymbolResolver::SymbolResolver(const FileDef *fileScope) + : p(std::make_unique(fileScope)) +{ +} + +SymbolResolver::~SymbolResolver() +{ +} + + +const ClassDef *SymbolResolver::resolveClass(const Definition *scope, + const char *name, + bool mayBeUnlinkable, + bool mayBeHidden) +{ + p->reset(); + + if (scope==0 || + (scope->definitionType()!=Definition::TypeClass && + scope->definitionType()!=Definition::TypeNamespace + ) || + (scope->getLanguage()==SrcLangExt_Java && QCString(name).find("::")!=-1) + ) + { + scope=Doxygen::globalScope; + } + //fprintf(stderr,"------------ resolveClass(scope=%s,name=%s,mayUnlinkable=%d)\n", + // scope?scope->name().data():"", + // name, + // mayBeUnlinkable + // ); + const ClassDef *result; + if (Config_getBool(OPTIMIZE_OUTPUT_VHDL)) + { + result = getClass(name); + } + else + { + result = p->getResolvedClassRec(scope,name,&p->typeDef,&p->templateSpec,&p->resolvedType); + if (result==0) // for nested classes imported via tag files, the scope may not + // present, so we check the class name directly as well. + // See also bug701314 + { + result = getClass(name); + } + } + if (!mayBeUnlinkable && result && !result->isLinkable()) + { + if (!mayBeHidden || !result->isHidden()) + { + //printf("result was %s\n",result?result->name().data():""); + result=0; // don't link to artificial/hidden classes unless explicitly allowed + } + } + //fprintf(stderr,"ResolvedClass(%s,%s)=%s\n",scope?scope->name().data():"", + // name,result?result->name().data():""); + return result; +} + +int SymbolResolver::isAccessibleFrom(const Definition *scope,const Definition *item) +{ + p->reset(); + AccessStack accessStack; + return p->isAccessibleFrom(accessStack,scope,item); +} + +int SymbolResolver::isAccessibleFromWithExpScope(const Definition *scope,const Definition *item, + const QCString &explicitScopePart) +{ + p->reset(); + VisitedNamespaces visitedNamespaces; + AccessStack accessStack; + return p->isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,item,explicitScopePart); +} + +void SymbolResolver::setFileScope(const FileDef *fileScope) +{ + p->setFileScope(fileScope); +} + +const MemberDef *SymbolResolver::getTypedef() const +{ + return p->typeDef; +} + +QCString SymbolResolver::getTemplateSpec() const +{ + return p->templateSpec; +} + +QCString SymbolResolver::getResolvedType() const +{ + return p->resolvedType; +} + diff --git a/src/symbolresolver.h b/src/symbolresolver.h new file mode 100644 index 0000000..e00569d --- /dev/null +++ b/src/symbolresolver.h @@ -0,0 +1,93 @@ +/****************************************************************************** + * + * Copyright (C) 1997-2020 by Dimitri van Heesch. + * + * Permission to use, copy, modify, and distribute this software and its + * documentation under the terms of the GNU General Public License is hereby + * granted. No representations are made about the suitability of this software + * for any purpose. It is provided "as is" without express or implied warranty. + * See the GNU General Public License for more details. + * + * Documents produced by Doxygen are derivative works derived from the + * input used in their production; they are not affected by this license. + * + */ + +#ifndef SYMBOLRESOLVER_H +#define SYMBOLRESOLVER_H + +#include +#include "qcstring.h" + +class Definition; +class ClassDef; +class FileDef; +class MemberDef; + +//! Helper class to find a class definition or check if +//! A symbol is accessible in a given scope. +class SymbolResolver +{ + public: + explicit SymbolResolver(const FileDef *fileScope = 0); + ~SymbolResolver(); + + // actions + + /** Find the class definition matching name within + * the scope set. + * @param scope The scope to search from. + * @param name The name of the symbol. + * @param maybeUnlinkable include unlinkable symbols in the search. + * @param myBeHidden include hidden symbols in the search. + * @note As a result of this call the getters getTypedef(), + * getTemplateSpec(), and getResolvedType() are set as well. + */ + const ClassDef *resolveClass(const Definition *scope, + const char *name, + bool maybeUnlinkable=false, + bool myBeHidden=false); + + /** Checks if symbol \a item is accessible from within \a scope. + * @returns -1 if \a item is not accessible or a number indicating how + * many scope levels up the nearest match was found. + */ + int isAccessibleFrom(const Definition *scope, + const Definition *item); + + /** Check if symbol \a item is accessible from within \a scope, + * where it has to match the \a explicitScopePart. + * @returns -1 if \a item is not accessible or a number indicating how + * many scope levels up the nearest match was found. + */ + int isAccessibleFromWithExpScope(const Definition *scope, + const Definition *item, + const QCString &explicitScopePart + ); + + /** Sets or updates the file scope using when resolving symbols. */ + void setFileScope(const FileDef *fd); + + // getters + + /** In case a call to resolveClass() resolves to a type member (e.g. an enum) + * this method will return it. + */ + const MemberDef *getTypedef() const; + + /** In case a call to resolveClass() points to a template specialization, the + * template part is return via this method. + */ + QCString getTemplateSpec() const; + + /** In case a call to resolveClass() points to a typedef or using declaration. + * The type name it resolved to is returned via this method. + */ + QCString getResolvedType() const; + + private: + struct Private; + std::unique_ptr p; +}; + +#endif diff --git a/src/util.cpp b/src/util.cpp index f518491..446fb4d 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -67,6 +67,7 @@ #include "membergroup.h" #include "dirdef.h" #include "htmlentity.h" +#include "symbolresolver.h" #define ENABLE_TRACINGSUPPORT 0 @@ -429,8 +430,8 @@ QCString resolveTypeDef(const Definition *context,const QCString &qualifiedName, // tmd->getOuterScope()->name().data(), mContext); if (tmd->isTypedef() /*&& tmd->getOuterScope()==resScope*/) { - AccessStack accessStack; - int dist=isAccessibleFrom(accessStack,resScope,0,tmd); + SymbolResolver resolver; + int dist=resolver.isAccessibleFrom(resScope,tmd); if (dist!=-1 && (md==0 || dist g_resolvedTypedefs; - -// forward declaration -static const ClassDef *getResolvedClassRec(const Definition *scope, - const FileDef *fileScope, - const char *n, - const MemberDef **pTypeDef, - QCString *pTemplSpec, - QCString *pResolvedType - ); - -/*! Returns the class representing the value of the typedef represented by \a md - * within file \a fileScope. - * - * Example: typedef A T; will return the class representing A if it is a class. - * - * Example: typedef int T; will return 0, since "int" is not a class. - */ -const ClassDef *newResolveTypedef(const FileDef *fileScope, - const MemberDef *md, - const MemberDef **pMemType, - QCString *pTemplSpec, - QCString *pResolvedType, - const std::unique_ptr &actTemplParams) -{ - //printf("newResolveTypedef(md=%p,cachedVal=%p)\n",md,md->getCachedTypedefVal()); - bool isCached = md->isTypedefValCached(); // value already cached - if (isCached) - { - //printf("Already cached %s->%s [%s]\n", - // md->name().data(), - // md->getCachedTypedefVal()?md->getCachedTypedefVal()->name().data():"", - // md->getCachedResolvedTypedef()?md->getCachedResolvedTypedef().data():""); - - if (pTemplSpec) *pTemplSpec = md->getCachedTypedefTemplSpec(); - if (pResolvedType) *pResolvedType = md->getCachedResolvedTypedef(); - return md->getCachedTypedefVal(); - } - //printf("new typedef\n"); - QCString qname = md->qualifiedName(); - if (g_resolvedTypedefs.find(qname)) return 0; // typedef already done - - g_resolvedTypedefs.insert(qname,md); // put on the trace list - - const ClassDef *typeClass = md->getClassDef(); - QCString type = md->typeString(); // get the "value" of the typedef - if (typeClass && typeClass->isTemplate() && - actTemplParams && !actTemplParams->empty()) - { - type = substituteTemplateArgumentsInString(type, - typeClass->templateArguments(),actTemplParams); - } - QCString typedefValue = type; - int tl=type.length(); - int ip=tl-1; // remove * and & at the end - while (ip>=0 && (type.at(ip)=='*' || type.at(ip)=='&' || type.at(ip)==' ')) - { - ip--; - } - type=type.left(ip+1); - type.stripPrefix("const "); // strip leading "const" - type.stripPrefix("struct "); // strip leading "struct" - type.stripPrefix("union "); // strip leading "union" - int sp=0; - tl=type.length(); // length may have been changed - while (spgetOuterScope(), - fileScope,type,&memTypeDef,0,pResolvedType); - // if type is a typedef then return what it resolves to. - if (memTypeDef && memTypeDef->isTypedef()) - { - result=newResolveTypedef(fileScope,memTypeDef,pMemType,pTemplSpec); - goto done; - } - else if (memTypeDef && memTypeDef->isEnumerate() && pMemType) - { - *pMemType = memTypeDef; - } - - //printf("type=%s result=%p\n",type.data(),result); - if (result==0) - { - // try unspecialized version if type is template - int si=type.findRev("::"); - int i=type.find('<'); - if (si==-1 && i!=-1) // typedef of a template => try the unspecialized version - { - if (pTemplSpec) *pTemplSpec = type.mid(i); - result = getResolvedClassRec(md->getOuterScope(),fileScope, - type.left(i),0,0,pResolvedType); - //printf("result=%p pRresolvedType=%s sp=%d ip=%d tl=%d\n", - // result,pResolvedType?pResolvedType->data():"",sp,ip,tl); - } - else if (si!=-1) // A::B - { - i=type.find('<',si); - if (i==-1) // Something like A::B => lookup A::B - { - i=type.length(); - } - else // Something like A::B => lookup A::B, spec= - { - if (pTemplSpec) *pTemplSpec = type.mid(i); - } - result = getResolvedClassRec(md->getOuterScope(),fileScope, - stripTemplateSpecifiersFromScope(type.left(i),FALSE),0,0, - pResolvedType); - } - - //if (result) ip=si+sp+1; - } - -done: - if (pResolvedType) - { - if (result) - { - *pResolvedType=result->qualifiedName(); - //printf("*pResolvedType=%s\n",pResolvedType->data()); - if (sp>0) pResolvedType->prepend(typedefValue.left(sp)); - if (ipappend(typedefValue.right(tl-ip-1)); - } - else - { - *pResolvedType=typedefValue; - } - } - - // remember computed value for next time - if (result && result->getDefFileName()!="") - // this check is needed to prevent that temporary classes that are - // introduced while parsing code fragments are being cached here. - { - //printf("setting cached typedef %p in result %p\n",md,result); - //printf("==> %s (%s,%d)\n",result->name().data(),result->getDefFileName().data(),result->getDefLine()); - //printf("*pResolvedType=%s\n",pResolvedType?pResolvedType->data():""); - const_cast(md)->cacheTypedefVal(result, - pTemplSpec ? *pTemplSpec : QCString(), - pResolvedType ? *pResolvedType : QCString() - ); - } - - g_resolvedTypedefs.remove(qname); // remove from the trace list - - return result; -} - -/*! Substitutes a simple unqualified \a name within \a scope. Returns the - * value of the typedef or \a name if no typedef was found. - */ -static QCString substTypedef(const Definition *scope,const FileDef *fileScope,const QCString &name, - const MemberDef **pTypeDef=0) -{ - QCString result=name; - if (name.isEmpty()) return result; - - auto range = Doxygen::symbolMap.find(name); - if (range.first==range.second) - return result; // no matches - - MemberDef *bestMatch=0; - int minDistance=10000; // init at "infinite" - - for (auto it = range.first; it!=range.second; ++it) - { - Definition *d = it->second; - // only look at members - if (d->definitionType()==Definition::TypeMember) - { - // that are also typedefs - MemberDef *md = dynamic_cast(d); - if (md->isTypedef()) // d is a typedef - { - VisitedNamespaces visitedNamespaces; - AccessStack accessStack; - // test accessibility of typedef within scope. - int distance = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,fileScope,d,""); - if (distance!=-1 && distancetypeString(); - if (pTypeDef) *pTypeDef=bestMatch; - } - - //printf("substTypedef(%s,%s)=%s\n",scope?scope->name().data():"", - // name.data(),result.data()); - return result; -} - -static const Definition *endOfPathIsUsedClass(const SDict *cl,const QCString &localName) -{ - if (cl) - { - SDict::Iterator cli(*cl); - Definition *cd; - for (cli.toFirst();(cd=cli.current());++cli) - { - if (cd->localName()==localName) - { - return cd; - } - } - } - return 0; -} - -/*! Starting with scope \a start, the string \a path is interpreted as - * a part of a qualified scope name (e.g. A::B::C), and the scope is - * searched. If found the scope definition is returned, otherwise 0 - * is returned. - */ -static const Definition *followPath(const Definition *start,const FileDef *fileScope,const QCString &path) -{ - int is,ps; - int l; - const Definition *current=start; - ps=0; - //printf("followPath: start='%s' path='%s'\n",start?start->name().data():"",path.data()); - // for each part of the explicit scope - while ((is=getScopeFragment(path,ps,&l))!=-1) - { - // try to resolve the part if it is a typedef - const MemberDef *typeDef=0; - QCString qualScopePart = substTypedef(current,fileScope,path.mid(is,l),&typeDef); - //printf(" qualScopePart=%s\n",qualScopePart.data()); - if (typeDef) - { - const ClassDef *type = newResolveTypedef(fileScope,typeDef); - if (type) - { - //printf("Found type %s\n",type->name().data()); - return type; - } - } - const Definition *next = current->findInnerCompound(qualScopePart); - //printf("++ Looking for %s inside %s result %s\n", - // qualScopePart.data(), - // current->name().data(), - // next?next->name().data():""); - if (next==0) // failed to follow the path - { - //printf("==> next==0!\n"); - if (current->definitionType()==Definition::TypeNamespace) - { - next = endOfPathIsUsedClass( - (dynamic_cast(current))->getUsedClasses(),qualScopePart); - } - else if (current->definitionType()==Definition::TypeFile) - { - next = endOfPathIsUsedClass( - (dynamic_cast(current))->getUsedClasses(),qualScopePart); - } - current = next; - if (current==0) break; - } - else // continue to follow scope - { - current = next; - //printf("==> current = %p\n",current); - } - ps=is+l; - } - //printf("followPath(start=%s,path=%s) result=%s\n", - // start->name().data(),path.data(),current?current->name().data():""); - return current; // path could be followed -} - -bool accessibleViaUsingClass(const SDict *cl, - const FileDef *fileScope, - const Definition *item, - const QCString &explicitScopePart="" - ) -{ - //printf("accessibleViaUsingClass(%p)\n",cl); - if (cl) // see if the class was imported via a using statement - { - SDict::Iterator cli(*cl); - Definition *ucd; - bool explicitScopePartEmpty = explicitScopePart.isEmpty(); - for (cli.toFirst();(ucd=cli.current());++cli) - { - //printf("Trying via used class %s\n",ucd->name().data()); - const Definition *sc = explicitScopePartEmpty ? ucd : followPath(ucd,fileScope,explicitScopePart); - if (sc && sc==item) return TRUE; - //printf("Try via used class done\n"); - } - } - return FALSE; -} - -bool accessibleViaUsingNamespace(StringUnorderedSet &visited, - const NamespaceSDict *nl, - const FileDef *fileScope, - const Definition *item, - const QCString &explicitScopePart="") -{ - if (nl) // check used namespaces for the class - { - NamespaceSDict::Iterator nli(*nl); - NamespaceDef *und; - int count=0; - for (nli.toFirst();(und=nli.current());++nli,count++) - { - //printf("[Trying via used namespace %s: count=%d/%d\n",und->name().data(), - // count,nl->count()); - const Definition *sc = explicitScopePart.isEmpty() ? und : followPath(und,fileScope,explicitScopePart); - if (sc && item->getOuterScope()==sc) - { - //printf("] found it\n"); - return TRUE; - } - if (item->getLanguage()==SrcLangExt_Cpp) - { - QCString key=und->name(); - if (und->getUsedNamespaces() && visited.find(key.str())==visited.end()) - { - visited.insert(key.str()); - - if (accessibleViaUsingNamespace(visited,und->getUsedNamespaces(),fileScope,item,explicitScopePart)) - { - //printf("] found it via recursion\n"); - return TRUE; - } - - visited.erase(key.str()); - } - } - //printf("] Try via used namespace done\n"); - } - } - return FALSE; -} - - -/* Returns the "distance" (=number of levels up) from item to scope, or -1 - * if item in not inside scope. - */ -int isAccessibleFrom(AccessStack &accessStack,const Definition *scope,const FileDef *fileScope,const Definition *item) -{ - //printf("name().data(),item->name().data(),item->getOuterScope()->name().data()); - - if (accessStack.find(scope,fileScope,item)) - { - return -1; - } - accessStack.push(scope,fileScope,item); - - int result=0; // assume we found it - int i; - - Definition *itemScope=item->getOuterScope(); - bool memberAccessibleFromScope = - (item->definitionType()==Definition::TypeMember && // a member - itemScope && itemScope->definitionType()==Definition::TypeClass && // of a class - scope->definitionType()==Definition::TypeClass && // accessible - (dynamic_cast(scope))->isAccessibleMember(dynamic_cast(item)) // from scope - ); - bool nestedClassInsideBaseClass = - (item->definitionType()==Definition::TypeClass && // a nested class - itemScope && itemScope->definitionType()==Definition::TypeClass && // inside a base - scope->definitionType()==Definition::TypeClass && // class of scope - (dynamic_cast(scope))->isBaseClass(dynamic_cast(itemScope),TRUE) - ); - - if (itemScope==scope || memberAccessibleFromScope || nestedClassInsideBaseClass) - { - //printf("> found it\n"); - if (nestedClassInsideBaseClass) result++; // penalty for base class to prevent - // this is preferred over nested class in this class - // see bug 686956 - } - else if (scope==Doxygen::globalScope) - { - if (fileScope) - { - SDict *cl = fileScope->getUsedClasses(); - if (accessibleViaUsingClass(cl,fileScope,item)) - { - //printf("> found via used class\n"); - goto done; - } - NamespaceSDict *nl = fileScope->getUsedNamespaces(); - StringUnorderedSet visited; - if (accessibleViaUsingNamespace(visited,nl,fileScope,item)) - { - //printf("> found via used namespace\n"); - goto done; - } - } - //printf("> reached global scope\n"); - result=-1; // not found in path to globalScope - } - else // keep searching - { - // check if scope is a namespace, which is using other classes and namespaces - if (scope->definitionType()==Definition::TypeNamespace) - { - const NamespaceDef *nscope = dynamic_cast(scope); - //printf(" %s is namespace with %d used classes\n",nscope->name().data(),nscope->getUsedClasses()); - const SDict *cl = nscope->getUsedClasses(); - if (accessibleViaUsingClass(cl,fileScope,item)) - { - //printf("> found via used class\n"); - goto done; - } - const NamespaceSDict *nl = nscope->getUsedNamespaces(); - StringUnorderedSet visited; - if (accessibleViaUsingNamespace(visited,nl,fileScope,item)) - { - //printf("> found via used namespace\n"); - goto done; - } - } - // repeat for the parent scope - i=isAccessibleFrom(accessStack,scope->getOuterScope(),fileScope,item); - //printf("> result=%d\n",i); - result= (i==-1) ? -1 : i+2; - } -done: - accessStack.pop(); - return result; -} - - -/* Returns the "distance" (=number of levels up) from item to scope, or -1 - * if item in not in this scope. The explicitScopePart limits the search - * to scopes that match \a scope (or its parent scope(s)) plus the explicit part. - * Example: - * - * class A { public: class I {}; }; - * class B { public: class J {}; }; - * - * - Looking for item=='J' inside scope=='B' will return 0. - * - Looking for item=='I' inside scope=='B' will return -1 - * (as it is not found in B nor in the global scope). - * - Looking for item=='A::I' inside scope=='B', first the match B::A::I is tried but - * not found and then A::I is searched in the global scope, which matches and - * thus the result is 1. - */ -int isAccessibleFromWithExpScope(VisitedNamespaces &visitedNamespaces, - AccessStack &accessStack, const Definition *scope,const FileDef *fileScope, - const Definition *item,const QCString &explicitScopePart) -{ - if (explicitScopePart.isEmpty()) - { - // handle degenerate case where there is no explicit scope. - return isAccessibleFrom(accessStack,scope,fileScope,item); - } - - if (accessStack.find(scope,fileScope,item,explicitScopePart)) - { - return -1; - } - accessStack.push(scope,fileScope,item,explicitScopePart); - - - //printf(" name().data():"", - // item?item->name().data():"", - // explicitScopePart.data()); - int result=0; // assume we found it - const Definition *newScope = followPath(scope,fileScope,explicitScopePart); - if (newScope) // explicitScope is inside scope => newScope is the result - { - Definition *itemScope = item->getOuterScope(); - //printf(" scope traversal successful %s<->%s!\n",itemScope->name().data(),newScope->name().data()); - //if (newScope && newScope->definitionType()==Definition::TypeClass) - //{ - // ClassDef *cd = (ClassDef *)newScope; - // printf("---> Class %s: bases=%p\n",cd->name().data(),cd->baseClasses()); - //} - if (itemScope==newScope) // exact match of scopes => distance==0 - { - //printf("> found it\n"); - } - else if (itemScope && newScope && - itemScope->definitionType()==Definition::TypeClass && - newScope->definitionType()==Definition::TypeClass && - (dynamic_cast(newScope))->isBaseClass(dynamic_cast(itemScope),TRUE,0) - ) - { - // inheritance is also ok. Example: looking for B::I, where - // class A { public: class I {} }; - // class B : public A {} - // but looking for B::I, where - // class A { public: class I {} }; - // class B { public: class I {} }; - // will find A::I, so we still prefer a direct match and give this one a distance of 1 - result=1; - - //printf("scope(%s) is base class of newScope(%s)\n", - // scope->name().data(),newScope->name().data()); - } - else - { - int i=-1; - if (newScope->definitionType()==Definition::TypeNamespace) - { - visitedNamespaces.insert({newScope->name().str(),newScope}); - // this part deals with the case where item is a class - // A::B::C but is explicit referenced as A::C, where B is imported - // in A via a using directive. - //printf("newScope is a namespace: %s!\n",newScope->name().data()); - const NamespaceDef *nscope = dynamic_cast(newScope); - const SDict *cl = nscope->getUsedClasses(); - if (cl) - { - SDict::Iterator cli(*cl); - const Definition *cd; - for (cli.toFirst();(cd=cli.current());++cli) - { - //printf("Trying for class %s\n",cd->name().data()); - if (cd==item) - { - //printf("> class is used in this scope\n"); - goto done; - } - } - } - const NamespaceSDict *nl = nscope->getUsedNamespaces(); - if (nl) - { - NamespaceSDict::Iterator nli(*nl); - const NamespaceDef *nd; - for (nli.toFirst();(nd=nli.current());++nli) - { - if (visitedNamespaces.find(nd->name().str())==visitedNamespaces.end()) - { - //printf("Trying for namespace %s\n",nd->name().data()); - i = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,fileScope,item,nd->name()); - if (i!=-1) - { - //printf("> found via explicit scope of used namespace\n"); - goto done; - } - } - } - } - } - // repeat for the parent scope - if (scope!=Doxygen::globalScope) - { - i = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope->getOuterScope(),fileScope, - item,explicitScopePart); - } - //printf(" | result=%d\n",i); - result = (i==-1) ? -1 : i+2; - } - } - else // failed to resolve explicitScope - { - //printf(" failed to resolve: scope=%s\n",scope->name().data()); - if (scope->definitionType()==Definition::TypeNamespace) - { - const NamespaceDef *nscope = dynamic_cast(scope); - const NamespaceSDict *nl = nscope->getUsedNamespaces(); - StringUnorderedSet visited; - if (accessibleViaUsingNamespace(visited,nl,fileScope,item,explicitScopePart)) - { - //printf("> found in used namespace\n"); - goto done; - } - } - if (scope==Doxygen::globalScope) - { - if (fileScope) - { - const NamespaceSDict *nl = fileScope->getUsedNamespaces(); - StringUnorderedSet visited; - if (accessibleViaUsingNamespace(visited,nl,fileScope,item,explicitScopePart)) - { - //printf("> found in used namespace\n"); - goto done; - } - } - //printf("> not found\n"); - result=-1; - } - else // continue by looking into the parent scope - { - int i=isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope->getOuterScope(),fileScope, - item,explicitScopePart); - //printf("> result=%d\n",i); - result= (i==-1) ? -1 : i+2; - } - } - -done: - //printf(" > result=%d\n",result); - accessStack.pop(); - return result; -} - int computeQualifiedIndex(const QCString &name) { int i = name.find('<'); return name.findRev("::",i==-1 ? name.length() : i); } -static void getResolvedSymbol(const Definition *scope, - const FileDef *fileScope, - const Definition *d, - const QCString &explicitScopePart, - const std::unique_ptr &actTemplParams, - int &minDistance, - const ClassDef *&bestMatch, - const MemberDef *&bestTypedef, - QCString &bestTemplSpec, - QCString &bestResolvedType - ) -{ - //printf(" => found type %x name=%s d=%p\n", - // d->definitionType(),d->name().data(),d); - - // only look at classes and members that are enums or typedefs - if (d->definitionType()==Definition::TypeClass || - (d->definitionType()==Definition::TypeMember && - ((dynamic_cast(d))->isTypedef() || (dynamic_cast(d))->isEnumerate()) - ) - ) - { - VisitedNamespaces visitedNamespaces; - AccessStack accessStack; - // test accessibility of definition within scope. - int distance = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,fileScope,d,explicitScopePart); - //printf(" %s; distance %s (%p) is %d\n",scope->name().data(),d->name().data(),d,distance); - if (distance!=-1) // definition is accessible - { - // see if we are dealing with a class or a typedef - if (d->definitionType()==Definition::TypeClass) // d is a class - { - const ClassDef *cd = dynamic_cast(d); - //printf("cd=%s\n",cd->name().data()); - if (!cd->isTemplateArgument()) // skip classes that - // are only there to - // represent a template - // argument - { - //printf("is not a templ arg\n"); - if (distancequalifiedName(); - } - else if (distance==minDistance && - fileScope && bestMatch && - fileScope->getUsedNamespaces() && - d->getOuterScope()->definitionType()==Definition::TypeNamespace && - bestMatch->getOuterScope()==Doxygen::globalScope - ) - { - // in case the distance is equal it could be that a class X - // is defined in a namespace and in the global scope. When searched - // in the global scope the distance is 0 in both cases. We have - // to choose one of the definitions: we choose the one in the - // namespace if the fileScope imports namespaces and the definition - // found was in a namespace while the best match so far isn't. - // Just a non-perfect heuristic but it could help in some situations - // (kdecore code is an example). - minDistance=distance; - bestMatch = cd; - bestTypedef = 0; - bestTemplSpec.resize(0); - bestResolvedType = cd->qualifiedName(); - } - } - else - { - //printf(" is a template argument!\n"); - } - } - else if (d->definitionType()==Definition::TypeMember) - { - const MemberDef *md = dynamic_cast(d); - //printf(" member isTypedef()=%d\n",md->isTypedef()); - if (md->isTypedef()) // d is a typedef - { - QCString args=md->argsString(); - if (args.isEmpty()) // do not expand "typedef t a[4];" - { - //printf(" found typedef!\n"); - - // we found a symbol at this distance, but if it didn't - // resolve to a class, we still have to make sure that - // something at a greater distance does not match, since - // that symbol is hidden by this one. - if (distancequalifiedName(); - } - else if (md->isReference()) // external reference - { - bestMatch = 0; - bestTypedef = md; - bestTemplSpec = spec; - bestResolvedType = type; - } - else - { - bestMatch = 0; - bestTypedef = md; - bestTemplSpec.resize(0); - bestResolvedType.resize(0); - //printf(" no match\n"); - } - } - else - { - //printf(" not the best match %d min=%d\n",distance,minDistance); - } - } - else - { - //printf(" not a simple typedef\n") - } - } - else if (md->isEnumerate()) - { - if (distancequalifiedName(); - } - } - } - } // if definition accessible - else - { - //printf(" Not accessible!\n"); - } - } // if definition is a class or member - //printf(" bestMatch=%p bestResolvedType=%s\n",bestMatch,bestResolvedType.data()); -} - -static std::mutex g_cacheMutex; - -/* Find the fully qualified class name referred to by the input class - * or typedef name against the input scope. - * Loops through scope and each of its parent scopes looking for a - * match against the input name. Can recursively call itself when - * resolving typedefs. - */ -static const ClassDef *getResolvedClassRec(const Definition *scope, - const FileDef *fileScope, - const char *n, - const MemberDef **pTypeDef, - QCString *pTemplSpec, - QCString *pResolvedType - ) -{ - if (n==0 || *n=='\0') return 0; - //static int level=0; - //printf("%d [getResolvedClassRec(%s,%s)\n",level++,scope?scope->name().data():"",n); - QCString name; - QCString explicitScopePart; - QCString strippedTemplateParams; - name=stripTemplateSpecifiersFromScope - (removeRedundantWhiteSpace(n),TRUE, - &strippedTemplateParams); - std::unique_ptr actTemplParams; - if (!strippedTemplateParams.isEmpty()) // template part that was stripped - { - actTemplParams = stringToArgumentList(scope->getLanguage(),strippedTemplateParams); - } - - int qualifierIndex = computeQualifiedIndex(name); - //printf("name=%s qualifierIndex=%d\n",name.data(),qualifierIndex); - if (qualifierIndex!=-1) // qualified name - { - // split off the explicit scope part - explicitScopePart=name.left(qualifierIndex); - // todo: improve namespace alias substitution - replaceNamespaceAliases(explicitScopePart,explicitScopePart.length()); - name=name.mid(qualifierIndex+2); - } - - if (name.isEmpty()) - { - //printf("%d ] empty name\n",--level); - return 0; // empty name - } - - //printf("Looking for symbol %s\n",name.data()); - auto range = Doxygen::symbolMap.find(name); - // the -g (for C# generics) and -p (for ObjC protocols) are now already - // stripped from the key used in the symbolMap, so that is not needed here. - if (range.first==range.second) - { - range = Doxygen::symbolMap.find(name+"-p"); - if (range.first==range.second) - { - //printf("%d ] no such symbol!\n",--level); - return 0; - } - } - //printf("found symbol!\n"); - - bool hasUsingStatements = - (fileScope && ((fileScope->getUsedNamespaces() && - fileScope->getUsedNamespaces()->count()>0) || - (fileScope->getUsedClasses() && - fileScope->getUsedClasses()->count()>0)) - ); - //printf("hasUsingStatements=%d\n",hasUsingStatements); - // Since it is often the case that the same name is searched in the same - // scope over an over again (especially for the linked source code generation) - // we use a cache to collect previous results. This is possible since the - // result of a lookup is deterministic. As the key we use the concatenated - // scope, the name to search for and the explicit scope prefix. The speedup - // achieved by this simple cache can be enormous. - int scopeNameLen = scope->name().length()+1; - int nameLen = name.length()+1; - int explicitPartLen = explicitScopePart.length(); - int fileScopeLen = hasUsingStatements ? 1+fileScope->absFilePath().length() : 0; - - // below is a more efficient coding of - // QCString key=scope->name()+"+"+name+"+"+explicitScopePart; - QCString key(scopeNameLen+nameLen+explicitPartLen+fileScopeLen+1); - char *p=key.rawData(); - qstrcpy(p,scope->name()); *(p+scopeNameLen-1)='+'; - p+=scopeNameLen; - qstrcpy(p,name); *(p+nameLen-1)='+'; - p+=nameLen; - qstrcpy(p,explicitScopePart); - p+=explicitPartLen; - - // if a file scope is given and it contains using statements we should - // also use the file part in the key (as a class name can be in - // two different namespaces and a using statement in a file can select - // one of them). - if (hasUsingStatements) - { - // below is a more efficient coding of - // key+="+"+fileScope->name(); - *p++='+'; - qstrcpy(p,fileScope->absFilePath()); - p+=fileScopeLen-1; - } - *p='\0'; - - LookupInfo *pval = 0; - { - std::lock_guard lock(g_cacheMutex); - pval=Doxygen::lookupCache->find(key.str()); - //printf("Searching for %s result=%p\n",key.data(),pval); - if (pval) - { - //printf("LookupInfo %p %p '%s' %p\n", - // pval->classDef, pval->typeDef, pval->templSpec.data(), - // pval->resolvedType.data()); - if (pTemplSpec) *pTemplSpec=pval->templSpec; - if (pTypeDef) *pTypeDef=pval->typeDef; - if (pResolvedType) *pResolvedType=pval->resolvedType; - //printf("%d ] cachedMatch=%s\n",--level, - // pval->classDef?pval->classDef->name().data():""); - //if (pTemplSpec) - // printf("templSpec=%s\n",pTemplSpec->data()); - return pval->classDef; - } - else // not found yet; we already add a 0 to avoid the possibility of - // endless recursion. - { - pval = Doxygen::lookupCache->insert(key.str(),LookupInfo()); - } - } - - const ClassDef *bestMatch=0; - const MemberDef *bestTypedef=0; - QCString bestTemplSpec; - QCString bestResolvedType; - int minDistance=10000; // init at "infinite" - - for (auto it=range.first ; it!=range.second; ++it) - { - Definition *d = it->second; - getResolvedSymbol(scope,fileScope,d,explicitScopePart,actTemplParams, - minDistance,bestMatch,bestTypedef,bestTemplSpec, - bestResolvedType); - } - - if (pTypeDef) - { - *pTypeDef = bestTypedef; - } - if (pTemplSpec) - { - *pTemplSpec = bestTemplSpec; - } - if (pResolvedType) - { - *pResolvedType = bestResolvedType; - } - //printf("getResolvedClassRec: bestMatch=%p pval->resolvedType=%s\n", - // bestMatch,bestResolvedType.data()); - - if (pval) - { - std::lock_guard lock(g_cacheMutex); - pval->classDef = bestMatch; - pval->typeDef = bestTypedef; - pval->templSpec = bestTemplSpec; - pval->resolvedType = bestResolvedType; - } - //printf("%d ] bestMatch=%s distance=%d\n",--level, - // bestMatch?bestMatch->name().data():"",minDistance); - //if (pTemplSpec) - // printf("templSpec=%s\n",pTemplSpec->data()); - return bestMatch; -} - -/* Find the fully qualified class name referred to by the input class - * or typedef name against the input scope. - * Loops through scope and each of its parent scopes looking for a - * match against the input name. - */ -const ClassDef *getResolvedClass(const Definition *scope, - const FileDef *fileScope, - const char *n, - const MemberDef **pTypeDef, - QCString *pTemplSpec, - bool mayBeUnlinkable, - bool mayBeHidden, - QCString *pResolvedType - ) -{ - static bool optimizeOutputVhdl = Config_getBool(OPTIMIZE_OUTPUT_VHDL); - g_resolvedTypedefs.clear(); - if (scope==0 || - (scope->definitionType()!=Definition::TypeClass && - scope->definitionType()!=Definition::TypeNamespace - ) || - (scope->getLanguage()==SrcLangExt_Java && QCString(n).find("::")!=-1) - ) - { - scope=Doxygen::globalScope; - } - //printf("------------ getResolvedClass(scope=%s,file=%s,name=%s,mayUnlinkable=%d)\n", - // scope?scope->name().data():"", - // fileScope?fileScope->name().data():"", - // n, - // mayBeUnlinkable - // ); - const ClassDef *result; - if (optimizeOutputVhdl) - { - result = getClass(n); - } - else - { - result = getResolvedClassRec(scope,fileScope,n,pTypeDef,pTemplSpec,pResolvedType); - } - if (result==0) // for nested classes imported via tag files, the scope may not - // present, so we check the class name directly as well. - // See also bug701314 - { - result = getClass(n); - } - if (!mayBeUnlinkable && result && !result->isLinkable()) - { - if (!mayBeHidden || !result->isHidden()) - { - //printf("result was %s\n",result?result->name().data():""); - result=0; // don't link to artificial/hidden classes unless explicitly allowed - } - } - //printf("getResolvedClass(%s,%s)=%s\n",scope?scope->name().data():"", - // n,result?result->name().data():""); - return result; -} - //------------------------------------------------------------------------- //------------------------------------------------------------------------- //------------------------------------------------------------------------- @@ -1957,8 +959,9 @@ void linkifyText(const TextGeneratorIntf &out, const Definition *scope, const GroupDef *gd=0; //printf("** Match word '%s'\n",matchWord.data()); - const MemberDef *typeDef=0; - cd=getResolvedClass(scope,fileScope,matchWord,&typeDef); + SymbolResolver resolver(fileScope); + cd=resolver.resolveClass(scope,matchWord); + const MemberDef *typeDef = resolver.getTypedef(); if (typeDef) // First look at typedef then class, see bug 584184. { //printf("Found typedef %s\n",typeDef->name().data()); @@ -2787,18 +1790,21 @@ static QCString getCanonicalTypeForIdentifier( // d ? d->name().data() : "",fs ? fs->name().data() : "", // tSpec?tSpec->data():"",templSpec.data()); - const ClassDef *cd = 0; - const MemberDef *mType = 0; - QCString ts; - QCString resolvedType; - // lookup class / class template instance - cd = getResolvedClass(d,fs,word+templSpec,&mType,&ts,TRUE,TRUE,&resolvedType); + SymbolResolver resolver(fs); + const ClassDef *cd = resolver.resolveClass(d,word+templSpec,true,true); + const MemberDef *mType = resolver.getTypedef(); + QCString ts = resolver.getTemplateSpec(); + QCString resolvedType = resolver.getResolvedType(); + bool isTemplInst = cd && !templSpec.isEmpty(); if (!cd && !templSpec.isEmpty()) { // class template specialization not known, look up class template - cd = getResolvedClass(d,fs,word,&mType,&ts,TRUE,TRUE,&resolvedType); + cd = resolver.resolveClass(d,word,true,true); + mType = resolver.getTypedef(); + ts = resolver.getTemplateSpec(); + resolvedType = resolver.getResolvedType(); } if (cd && cd->isUsedOnly()) cd=0; // ignore types introduced by usage relations @@ -3426,12 +2432,15 @@ bool getDefs(const QCString &scName, className=mScope; } - const MemberDef *tmd=0; - const ClassDef *fcd=getResolvedClass(Doxygen::globalScope,0,className,&tmd); + SymbolResolver resolver; + const ClassDef *fcd=resolver.resolveClass(Doxygen::globalScope,className); + const MemberDef *tmd=resolver.getTypedef(); + if (fcd==0 && className.find('<')!=-1) // try without template specifiers as well { QCString nameWithoutTemplates = stripTemplateSpecifiersFromScope(className,FALSE); - fcd=getResolvedClass(Doxygen::globalScope,0,nameWithoutTemplates,&tmd); + fcd=resolver.resolveClass(Doxygen::globalScope,nameWithoutTemplates); + tmd=resolver.getTypedef(); } //printf("Trying class scope %s: fcd=%p tmd=%p\n",className.data(),fcd,tmd); // todo: fill in correct fileScope! @@ -5603,7 +4612,8 @@ QCString normalizeNonTemplateArgumentsInString( if (!found) { // try to resolve the type - const ClassDef *cd = getResolvedClass(context,0,n); + SymbolResolver resolver; + const ClassDef *cd = resolver.resolveClass(context,n); if (cd) { result+=cd->name(); @@ -6700,7 +5710,7 @@ QCString getFileNameExtension(QCString fn) //-------------------------------------------------------------------------- -MemberDef *getMemberFromSymbol(const Definition *scope,const FileDef *fileScope, +static MemberDef *getMemberFromSymbol(const Definition *scope,const FileDef *fileScope, const char *n) { if (scope==0 || @@ -6739,9 +5749,8 @@ MemberDef *getMemberFromSymbol(const Definition *scope,const FileDef *fileScope, Definition *d = it->second; if (d->definitionType()==Definition::TypeMember) { - VisitedNamespaces visitedNamespaces; - AccessStack accessStack; - int distance = isAccessibleFromWithExpScope(visitedNamespaces,accessStack,scope,fileScope,d,explicitScopePart); + SymbolResolver resolver(fileScope); + int distance = resolver.isAccessibleFromWithExpScope(scope,d,explicitScopePart); if (distance!=-1 && distance //-------------------------------------------------------------------- -const int MAX_STACK_SIZE = 100; - -/** Helper class representing the stack of items considered while resolving - * the scope. - */ -class AccessStack -{ - /** Element in the stack. */ - struct AccessElem - { - AccessElem(const Definition *d,const FileDef *f,const Definition *i,QCString e = QCString()) : scope(d), fileScope(f), item(i), expScope(e) {} - const Definition *scope; - const FileDef *fileScope; - const Definition *item; - QCString expScope; - }; - public: - void push(const Definition *scope,const FileDef *fileScope,const Definition *item) - { - m_elements.push_back(AccessElem(scope,fileScope,item)); - } - void push(const Definition *scope,const FileDef *fileScope,const Definition *item,const QCString &expScope) - { - m_elements.push_back(AccessElem(scope,fileScope,item,expScope)); - } - void pop() - { - if (!m_elements.empty()) m_elements.pop_back(); - } - bool find(const Definition *scope,const FileDef *fileScope, const Definition *item) - { - auto it = std::find_if(m_elements.begin(),m_elements.end(), - [&](const AccessElem &e) { return e.scope==scope && e.fileScope==fileScope && e.item==item; }); - return it!=m_elements.end(); - } - bool find(const Definition *scope,const FileDef *fileScope, const Definition *item,const QCString &expScope) - { - auto it = std::find_if(m_elements.begin(),m_elements.end(), - [&](const AccessElem &e) { return e.scope==scope && e.fileScope==fileScope && e.item==item && e.expScope==expScope; }); - return it!=m_elements.end(); - } - - private: - std::vector m_elements; -}; - -using VisitedNamespaces = std::unordered_map; - -//-------------------------------------------------------------------- - QCString langToString(SrcLangExt lang); QCString getLanguageSpecificSeparator(SrcLangExt lang,bool classScope=FALSE); @@ -256,15 +206,6 @@ QCString resolveDefines(const char *n); ClassDef *getClass(const char *key); -const ClassDef *getResolvedClass(const Definition *scope, - const FileDef *fileScope, - const char *key, - const MemberDef **pTypeDef=0, - QCString *pTemplSpec=0, - bool mayBeUnlinkable=FALSE, - bool mayBeHidden=FALSE, - QCString *pResolvedType=0); - NamespaceDef *getResolvedNamespace(const char *key); FileDef *findFileDef(const FileNameLinkedMap *fnMap,const char *n, @@ -363,8 +304,6 @@ QCString substituteTemplateArgumentsInString( const ArgumentList &formalArgs, const std::unique_ptr &actualArgs); -//QList *copyArgumentLists(const QList *srcLists); - QCString stripTemplateSpecifiersFromScope(const QCString &fullName, bool parentOnly=TRUE, QCString *lastScopeStripped=0); @@ -430,18 +369,6 @@ QCString stripExtension(const char *fName); void replaceNamespaceAliases(QCString &scope,int i); -int isAccessibleFrom(AccessStack &accessStack, - const Definition *scope, - const FileDef *fileScope, - const Definition *item); - -int isAccessibleFromWithExpScope(VisitedNamespaces &visitedNamespaces, - AccessStack &accessStack, - const Definition *scope, - const FileDef *fileScope, - const Definition *item, - const QCString &explicitScopePart); - int computeQualifiedIndex(const QCString &name); void addDirPrefix(QCString &fileName); @@ -464,17 +391,8 @@ QCString getFileNameExtension(QCString fn); void initDefaultExtensionMapping(); void addCodeOnlyMappings(); -MemberDef *getMemberFromSymbol(const Definition *scope,const FileDef *fileScope, - const char *n); bool checkIfTypedef(const Definition *scope,const FileDef *fileScope,const char *n); -const ClassDef *newResolveTypedef(const FileDef *fileScope, - const MemberDef *md, - const MemberDef **pMemType=0, - QCString *pTemplSpec=0, - QCString *pResolvedType=0, - const std::unique_ptr &actTemplParams=std::unique_ptr()); - QCString parseCommentAsText(const Definition *scope,const MemberDef *member,const QCString &doc,const QCString &fileName,int lineNr); QCString transcodeCharacterStringToUTF8(const QCString &input); @@ -485,8 +403,6 @@ QCString extractAliasArgs(const QCString &args,int pos); int countAliasArguments(const QCString argList); -//QCString replaceAliasArguments(const QCString &aliasValue,const QCString &argList); - QCString resolveAliasCmd(const QCString aliasCmd); QCString expandAlias(const QCString &aliasName,const QCString &aliasValue); -- cgit v0.12