7

Consider the following struct definition in foo.cc:

struct Foo
{
  int &bar;
};

Because bar has reference type, the implicit trivial default constructor Foo::Foo() is implicitly deleted. However, this fact does not seem to be reflected in the AST generated by clang. E.g. running clang -Xclang -ast-dump foo.cc results in:

`-CXXRecordDecl 0x5612ba4c03b8 <test.cc:1:1, col:24> col:8 struct Foo definition
  |-DefinitionData pass_in_registers aggregate trivially_copyable trivial literal
  | |-DefaultConstructor exists trivial needs_implicit
  | |-CopyConstructor simple trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveConstructor exists simple trivial needs_implicit
  | |-CopyAssignment trivial has_const_param needs_implicit implicit_has_const_param
  | |-MoveAssignment exists trivial needs_implicit
  | `-Destructor simple irrelevant trivial needs_implicit
  |-CXXRecordDecl 0x5612ba4c04e0 <col:1, col:8> col:8 implicit struct Foo
  `-FieldDecl 0x5612ba4c05b8 <col:14, col:19> col:19 bar 'int &'

So here it looks like an implicit trivial default constructor exists, but there is no mention of it being deleted. Similarly, the clang::CXXRecordDecl API seems to offer no way of determining this either. But shouldn't this information be available at this point (after semantic analysis)? How can I use the clang AST API to find out whether some class's implicit trivial default constructor is implicitly deleted?

Peter
  • 2,919
  • 1
  • 16
  • 35

2 Answers2

1

Information about deleted implicit constructors is available after using the ForceDeclarationOfImplicitMembers of the Sema (semantic analysis) class. That will populate the CXXRecordDecl with CXXConstructorDecl nodes, which can then be iterated over with CXXRecordDecl::decls or visited using a RecursiveASTVisitor.

One tricky bit is getting ahold of the Sema object in order to invoke the required method. If you are using the LoadFromCommandLine method of ASTUnit, that returns an ASTUnit object, which has a getSema method.

If you are using ClangTool, you can either use buildASTs to get ASTUnits directly, or use run with a FrontendAction and use its getCurrentASTUnit method. (The tutorial makes this somewhat difficult because it uses newFrontendActionFactory. Dealing with that complication could be the subject of a new question.)

In either case, once the AST nodes for the implicit methods have been created, the isDeleted method (declared on FunctionDecl) will return true if the method has been implicitly or explicitly deleted.

Finally, be aware that if you use a RecursiveASTVisitor to visit the methods, by default it skips implicitly-defined methods. To change that, implement the shouldVisitImplicitCode method and have it return true.

Complete example

Below are sources and tests to make and run a program that report all constructors, including those that are implicit, and whether they are deleted.

deleted-ctor.cc:

// deleted-ctor.cc
// Report deleted constructors.

#include "clang/AST/RecursiveASTVisitor.h"                 // clang::RecursiveASTVisitor
#include "clang/Basic/Diagnostic.h"                        // clang::DiagnosticsEngine
#include "clang/Basic/DiagnosticOptions.h"                 // clang::DiagnosticOptions
#include "clang/Frontend/ASTUnit.h"                        // clang::ASTUnit
#include "clang/Frontend/CompilerInstance.h"               // clang::CompilerInstance
#include "clang/Sema/Sema.h"                               // clang::Sema
#include "clang/Serialization/PCHContainerOperations.h"    // clang::PCHContainerOperations

#include <iostream>                                        // std::cout
#include <string>                                          // std::string

using std::cout;
using std::string;


class Visitor : public clang::RecursiveASTVisitor<Visitor> {
public:      // data
  clang::ASTUnit *m_astUnit;
  clang::ASTContext &m_astContext;

public:      // methods
  Visitor(clang::ASTUnit *astUnit)
    : m_astUnit(astUnit),
      m_astContext(astUnit->getASTContext())
  {}

  // Convenience methods to stringify some things.
  string locStr(clang::SourceLocation loc);
  string declLocStr(clang::Decl const *decl);
  string typeStr(clang::QualType qualType);

  // By default, AST visitors skip implicit nodes even if they are
  // present in the AST.  Returning true here ensures we will not skip
  // them.
  bool shouldVisitImplicitCode() { return true; }

  // Visitor methods.
  bool VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl);
  bool VisitCXXConstructorDecl(clang::CXXConstructorDecl *methodDecl);

  // Kick off the traversal.
  void traverseTU();
};

string Visitor::locStr(clang::SourceLocation loc)
{
  return loc.printToString(m_astContext.getSourceManager());
}

string Visitor::declLocStr(clang::Decl const *decl)
{
  return locStr(decl->getLocation());
}

string Visitor::typeStr(clang::QualType qualType)
{
  return qualType.getAsString();
}

bool Visitor::VisitCXXRecordDecl(clang::CXXRecordDecl *recordDecl)
{
  if (recordDecl->getDefinition() != recordDecl) {
    // This is not the definition, so ignore it.
  }
  else {
    cout << recordDecl->Decl::getDeclKindName()
         << " \"" << recordDecl->getQualifiedNameAsString()
         << "\" at " << declLocStr(recordDecl)
         << ":\n";

    // Without this, the implicit members are not in the AST.
    m_astUnit->getSema().ForceDeclarationOfImplicitMembers(recordDecl);
  }

  return true;
}

bool Visitor::VisitCXXConstructorDecl(clang::CXXConstructorDecl *ctor)
{
  cout << "  " << ctor->Decl::getDeclKindName()
       << " \"" << ctor->getQualifiedNameAsString()
       << "\" type=\"" << typeStr(ctor->getType())
       << "\" at "
       << declLocStr(ctor)
       << ": deleted=" << ctor->isDeleted()
       << "\n";

  return true;
}


void Visitor::traverseTU()
{
  this->TraverseDecl(m_astContext.getTranslationUnitDecl());
}


int main(int argc, char const **argv)
{
  // Point 'LoadFromCommandLine' at the clang binary so it will be able
  // to find its compiler headers such as stddef.h.
  argv[0] = CLANG_LLVM_INSTALL_DIR "/bin/clang";

  // Boilerplate setup for 'LoadFromCommandLine'.
  std::shared_ptr<clang::PCHContainerOperations> pchContainerOps(
    new clang::PCHContainerOperations());
  clang::IntrusiveRefCntPtr<clang::DiagnosticsEngine> diagnosticsEngine(
    clang::CompilerInstance::createDiagnostics(
      new clang::DiagnosticOptions));

  // Run the Clang parser to produce an AST.
  std::unique_ptr<clang::ASTUnit> ast(clang::ASTUnit::LoadFromCommandLine(
    argv,
    argv + argc,
    pchContainerOps,
    diagnosticsEngine,
    llvm::StringRef() /*ResourceFilesPath, evidently ignored*/));

  if (ast == nullptr ||
      diagnosticsEngine->getNumErrors() > 0) {
    // Error messages have already been printed.
    return 2;
  }

  Visitor visitor(ast.get());
  visitor.traverseTU();

  return 0;
}


// EOF

Makefile:

# Makefile

# Default target.
all:
.PHONY: all


# ---- Configuration ----
# Installation directory from a binary distribution.
# Has five subdirectories: bin include lib libexec share.
# Downloaded from: https://github.com/llvm/llvm-project/releases/download/llvmorg-14.0.0/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04.tar.xz
CLANG_LLVM_INSTALL_DIR = $(HOME)/opt/clang+llvm-14.0.0-x86_64-linux-gnu-ubuntu-18.04

# ---- llvm-config query results ----
# Program to query the various LLVM configuration options.
LLVM_CONFIG := $(CLANG_LLVM_INSTALL_DIR)/bin/llvm-config

# C++ compiler options to ensure ABI compatibility.
LLVM_CXXFLAGS := $(shell $(LLVM_CONFIG) --cxxflags)

# Directory containing the clang library files, both static and dynamic.
LLVM_LIBDIR := $(shell $(LLVM_CONFIG) --libdir)

# Other flags needed for linking, whether statically or dynamically.
LLVM_LDFLAGS_AND_SYSTEM_LIBS := $(shell $(LLVM_CONFIG) --ldflags --system-libs)


# ---- Compiler options ----
# C++ compiler.
#CXX = g++
CXX := $(CLANG_LLVM_INSTALL_DIR)/bin/clang++

# Compiler options, including preprocessor options.
CXXFLAGS =
CXXFLAGS += -Wall
CXXFLAGS += -Werror

# Silence a warning about a multi-line comment in DeclOpenMP.h.
CXXFLAGS += -Wno-comment

# Get llvm compilation flags.
CXXFLAGS += $(LLVM_CXXFLAGS)

# Tell the source code where the clang installation directory is.
CXXFLAGS += -DCLANG_LLVM_INSTALL_DIR='"$(CLANG_LLVM_INSTALL_DIR)"'

# Linker options.
LDFLAGS =

# Pull in clang+llvm via libclang-cpp.so, which has everything, but is
# only available as a dynamic library.
LDFLAGS += -lclang-cpp

# Arrange for the compiled binary to search the libdir for that library.
# Otherwise, one can set the LD_LIBRARY_PATH envvar before running it.
# Note: the -rpath switch does not work on Windows.
LDFLAGS += -Wl,-rpath=$(LLVM_LIBDIR)

# Get the needed -L search path, plus things like -ldl.
LDFLAGS += $(LLVM_LDFLAGS_AND_SYSTEM_LIBS)


# ---- Recipes ----
# Compile a C++ source file.
%.o: %.cc
    $(CXX) -c -o $@ $(CXXFLAGS) $<

# Executable.
all: deleted-ctor.exe
deleted-ctor.exe: deleted-ctor.o
    $(CXX) -g -Wall -o $@ $^ $(LDFLAGS)

# Test.
.PHONY: run
run: deleted-ctor.exe
    ./deleted-ctor.exe test.cc

.PHONY: clean
clean:
    $(RM) *.o *.exe


# EOF

test.cc:

// test.cc
// Test for deleted-ctor.exe.

// Explicit ctor, not deleted.
class END {
public:
  END();
};

// Explicit ctor, deleted.
class ED {
public:
  ED()=delete;
};


// Implicit ctor, not deleted.
class IND {
public:
};

// Implicit ctor, deleted.
class ID {
public:
  int &i;
};

// EOF

Example run:

$ make run
./deleted-ctor.exe test.cc
CXXRecord "END" at test.cc:5:7:
  CXXConstructor "END::END" type="void (void)" at test.cc:7:3: deleted=0
  CXXConstructor "END::END" type="void (const class END &)" at test.cc:5:7: deleted=0
  CXXConstructor "END::END" type="void (class END &&)" at test.cc:5:7: deleted=0
CXXRecord "ED" at test.cc:11:7:
  CXXConstructor "ED::ED" type="void (void)" at test.cc:13:3: deleted=1
  CXXConstructor "ED::ED" type="void (const class ED &)" at test.cc:11:7: deleted=0
  CXXConstructor "ED::ED" type="void (class ED &&)" at test.cc:11:7: deleted=0
CXXRecord "IND" at test.cc:18:7:
  CXXConstructor "IND::IND" type="void (void)" at test.cc:18:7: deleted=0
  CXXConstructor "IND::IND" type="void (const class IND &)" at test.cc:18:7: deleted=0
  CXXConstructor "IND::IND" type="void (class IND &&)" at test.cc:18:7: deleted=0
CXXRecord "ID" at test.cc:23:7:
  CXXConstructor "ID::ID" type="void (void)" at test.cc:23:7: deleted=1
  CXXConstructor "ID::ID" type="void (const class ID &)" at test.cc:23:7: deleted=0
  CXXConstructor "ID::ID" type="void (class ID &&)" at test.cc:23:7: deleted=0

Observe that the first constructor for each of ED and ID is labeled with deleted=1.

Scott McPeak
  • 8,803
  • 2
  • 40
  • 79
0

Implicit constructors don't exist in the AST under CXXRecordDecl nodes but only under DefinitionData.

You can use Sema::ForceDeclarationOfImplicitMembers in AST plugin to get the constructors, then check whether is deleted.

// force declaration of implict constructors
clang::Sema &sema = Instance.getSema();
sema.ForceDeclarationOfImplicitMembers(const_cast<CXXRecordDecl*>(cxxRecordDecl));

// then check whether constructor is deleted
for (const auto* ctor : cxxRecordDecl->ctors()) {
    if (ctor->isDeleted() {
       // do something
    }
}