18

How can I inspect the overload resolution set?

I have 4 competing functions used in multiple call sites. In one call site, I'm expecting one function to be called, but another one is picked up by the compiler. I don't know why/it's not trivial. To learn what is going on, I'm using enable_if/disable_if to turn functions on/off but this is really slow/tedious/annoying.

So I would like the compiler to tell me "Why?". That is, for this single call site:

  • all functions found by ADL,
  • all functions in the overload resolution set,
  • all functions rejected from the overload resolution set and why they were rejected, and
  • the ranks of the functions in the overload resolution set as well as the reason for their ranks.

Information about access control is not required.

Basically I'm hoping for marking the call site with a #pragma or similar (__builtin...). But libclang would also be an option.

I have access to tip-of-trunk clang and gcc but can install other compiler/tools if necessary.

gnzlbg
  • 7,135
  • 5
  • 53
  • 106
  • 2
    Slightly offtopic, but when you cannot steadily predict what particular function will be called from the overloaded bunch, it is a poor design. Your overloading versions should be obviously differentiated. – Mikhail Nov 20 '13 at 10:04
  • 1
    @Mikhail I was predicting the correct overload. It wasn't being picked up because it called a non-const member function in a const object (I had forgotten to add a const overload... my bad). I know, don't mix universal references + overloading... unless its the right thing to do. The compiler could make dealing with it a lot easier tho. – gnzlbg Nov 20 '13 at 10:29
  • @Mikhail, I've got a third-party framework that generates code for other third-party thing. And the resulting types work fine when comparing objects in an inline function, but they fail in a nearby class. Would be nice to have a log of the overload resolution to figure out which resolution it has actually found in the inline-function case (the indexer of the IDE just gave up). – Velkan Jul 05 '18 at 14:50

1 Answers1

9

I can imagine writing a clang plug-in inspecting which function is being called and what others are in the overload set. I'd think tracing the look-up rules and finding out why the candidates from the overload set are discarded and why the chosen function is the best candidate in the overload set is something quite different, though.

I haven't played with determining overload sets, etc. However, below is a simple starting point: a clang plug-in which prints the function called if a function with a specific name (currently hard-coded to be "foo") is found. It also print the found overloads.

I'm compiling the code and running it with the commands (obviously, these are stored in a make file):

/opt/llvm-debug/bin/clang -I/usr/include/c++/4.2.1 -I/opt/llvm-debug/include -D__STDC_LIMIT_MACROS -D__STDC_CONSTANT_MACROS -g -fno-exceptions -fno-rtti -c -o MacOS/overloads.o overloads.cpp
/opt/llvm-debug/bin/clang -L/opt/llvm-debug/lib -Wl,-undefined,dynamic_lookup -dynamiclib -o MacOS/overloads.dylib MacOS/overloads.o 
/opt/llvm-debug/bin/clang -cc1 -load MacOS/overloads.dylib -plugin overloads -plugin-arg-overloads argument -fexceptions tst.cpp

The version of clang used is built with debug information: otherwise it doesn't seem to find a debug symbol. I should probably find out how to build a tool directly and not run from within clang.

#include <clang/Frontend/FrontendPluginRegistry.h>
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Lex/Preprocessor.h>
#include <clang/Lex/PPCallbacks.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/AST.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include <clang/Sema/Sema.h>
#include <clang/Sema/Lookup.h>
#include <llvm/Support/raw_ostream.h>
#include <string>

using namespace clang;
using namespace llvm;
typedef clang::CompilerInstance  CI;
typedef clang::DeclGroupRef      DGR;
typedef clang::DiagnosticsEngine DE;

// ----------------------------------------------------------------------------

namespace
{
    struct Consumer: clang::ASTConsumer
    {
        Consumer(CI& c, std::string const& name): c_(&c), name_(name) {}
        bool HandleTopLevelDecl(clang::DeclGroupRef DG);
        CI*         c_;
        std::string name_;
    };
}

// ----------------------------------------------------------------------------

struct Visitor: RecursiveASTVisitor<Visitor>
{
    CI*         c_;
    std::string name_;
    Visitor(CI* c, std::string const& name): c_(c), name_(name) {}

    bool VisitCallExpr(CallExpr* d);
};

bool Visitor::VisitCallExpr(CallExpr* c) {
    FunctionDecl* fun = c->getDirectCallee();
    if (fun && fun->getNameAsString() == this->name_) {
        SourceLocation w(c->getExprLoc());
        DE &de(this->c_->getDiagnostics());
        int id = de.getCustomDiagID(DE::Warning, "function call: %0");
        int info = de.getCustomDiagID(DE::Note, "function called");
        DiagnosticBuilder(de.Report(w, id))
            << fun->getNameAsString()
            ;
        DiagnosticBuilder(de.Report(fun->getLocStart(), info))
            << fun->getNameAsString()
            ;
        Sema& sema = this->c_->getSema();
        LookupResult result(sema, fun->getDeclName(), w, Sema::LookupOrdinaryName);
        DeclContext* context = fun->getDeclContext();
        if (sema.LookupName(result, sema.getScopeForContext(context))) {
            int over = de.getCustomDiagID(DE::Note, "function overload");
            LookupResult::Filter filter = result.makeFilter();
            while (filter.hasNext()) {
                DiagnosticBuilder(de.Report(filter.next()->getLocStart(), over))
                    ;
            }
            filter.done();
        }
    }
    //else {
    //    // I think the callee was a function object or a function pointer
    //}

    return true;
}

void doDecl(Consumer* c, Decl* d) {
    Visitor(c->c_, c->name_).TraverseDecl(d);
}

// ----------------------------------------------------------------------------

bool Consumer::HandleTopLevelDecl(DeclGroupRef DG) {
    std::for_each(DG.begin(), DG.end(),
        std::bind1st(std::ptr_fun(&doDecl), this));
    return true;
}

// ----------------------------------------------------------------------------

namespace
{
    class Plug
        : public clang::PluginASTAction
    {
    protected:
        ASTConsumer*
        CreateASTConsumer(CompilerInstance& c, llvm::StringRef);
        bool ParseArgs(clang::CompilerInstance const&,
                       std::vector<std::string> const&) {
            return true;
        }
    };
}

ASTConsumer*
Plug::CreateASTConsumer(CompilerInstance& c, llvm::StringRef) {
    return new Consumer(c, "foo");
}

static clang::FrontendPluginRegistry::Add<Plug>
    registerPlugin("overloads", "report overloads of a function at a call");

The code isn't pretty and isn't really doing what you are looking for. However, formatting the function declarations a bit nicer, possibly investigating a bit with the Sema object why it isn't a match, etc. could get the code reasonably close to the tool you are looking for, I think.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380