How to use custom C++ attributes with Clang libTooling without modifying Clang code?

1.1k views Asked by At

I'm writing some kind of tool that extracts the interface definitions of C++ code. In process of writing, I decided to restrict the parser to process only the code that was explicitly marked for processing, and I thought that C++ attributes are the best way to do it. I'd prefer to add e.g. [[export]] annotations to entities I want to export, but I realised that libTooling is unable to see custom attributes without registering them in Clang code itself (I mean adding the attribute to tools/clang/include/clang/Basic/Attr.td).

Thus, my question is: is there a way to register the attribute without modifying that file (e.g. by registering the attribute programmatically or writing own Attr.td file)?

UPD: I'm using ASTMatchers library for source code analysis, so visitor-based approach probably does not work for me.

1

There are 1 answers

2
vandench On

From what I can tell it is not possible to register custom attributes without directly modifying libtooling.

If you're willing to use pre-processor macros instead of attributes there is a workaround that I've done in the past. The basics are that we'll declare an empty macro, write a pre-processor callback to identify the location of the macro and store it in a queue, then in an AST visitor we'll visit records for either classes, methods, or variables, and check to see if preceeding the entity is our macro.

For the preprocessor you'll need to extend clang::PPCallbacks and implement the MacroExpands method.

void MyPreProcessorCallback::MacroExpands(const clang::Token& MacroNameTok, const clang::MacroDefinition&, const clang::SourceRange Range, const clang::MacroArgs* const Args)
{
    // Macro is not named for some reason.
    if(!MacroNameTok.isAnyIdentifier())
    { return; }

    if(MacroNameTok.getIdentifierInfo()->getName() == "EXPORT")
    {
        // Save Range into a queue.
    }
    else
    {
        return;
    }

    // If you want arguments you can declare the macro to have varargs and loop 
    // through the tokens, you can have any syntax you want as they're raw tokens.


    // /*   Get the argument list for this macro, because it's a
    //   varargs function all arguments are stored in argument 0. */
    // const ::clang::Token* token = Args->getUnexpArgument(0u);

    // // All tokens for the argument are stored in sequence.
    // for(; token->isNot(::clang::tok::eof); ++token)
    // {
    // }
}

Inside your RecursiveAstVisitor you can implement visitors that will pop off the top of the queue and check to see if the top macro is before in the translation unit. IIRC visitors of a type are all executed in order of declaration, so the queue should maintain the order. It is worth noting that all Decl's of a type are visited in order, so care has to be taken when distinguishing between function, variables, and classes.

bool MyAstVisitor::VisitFunctionDecl(::clang::FunctionDecl* const function)
{
    if(::llvm::isa<::clang::CXXMethodDecl>(function))
    {
        // If you want to handle class member methods separately you 
        // can check here or implement `VisitCXXMethodDecl` and fast exit here.
    }

    if(ourExportTags.empty())
    {
        return true;
    }

    const ::clang::SourceLocation tagLoc = ourExportTags.front().getBegin();
    const ::clang::SourceLocation declLoc = function->getBeginLoc();

    if(getAstContext().getSourceManager().isBeforeInTranslationUnit(tagLoc, declLoc))
    {
        ourExportTags.pop_front();

        // Handle export;
    }

    return true;
}

EDIT
I haven't used ASTMatchers before, but you could probably accomplish a similar result by writing a matcher, storing all of the declarations to a list, sorting based on location, and then comparing to the original export tag queue.

DeclarationMatcher matcher = functionDecl().bind("funcDecl");


class MyFuncMatcher : public clang::ast_matchers::MatchFinder::MatchCallback
{
public:
    virtual void run(const clang::ast_matchers::MatchFinder::MatchResult& Result)
    {
        if(const FunctionDecl* func = Result.Nodes.getNodeAs<clang::FunctionDecl>("funcDecl"))
        {
            // Add to list for future processing
        }
    }
};

void joinTagsToDeclarations()
{
    // Sort the declaration list

    for(auto decl : myDeclList)
    {
        if(ourExportTags.empty())
        {
            break;
        }

        const ::clang::SourceLocation tagLoc = ourExportTags.front().getBegin();
        const ::clang::SourceLocation declLoc = decl->getBeginLoc();

        if(getAstContext().getSourceManager().isBeforeInTranslationUnit(tagLoc, declLoc))
        {
            ourExportTags.pop_front();

            // Handle export;
        }
    }
}