I would like to modify the clang AST using a plugin.
I have following the suggestions of using Clang Rewriter object.
I use compiler.createDefaultOutputFile() to obtain the output stream to write the changed file into it.
the compilation works fine but the generated object file is broken.
I get the following linker error: "File format not recognized".
Build command line:
clang++-8 -Xclang -load -Xclang libcashpp.so -Xclang -add-plugin -Xclang cash-pp test.cpp
Build Output:
/tmp/test-eb7d3d.o: file not recognized: File format not recognized
Question:
Is it actually possible to update the AST using a clang plugin?
Can someone point me to an example program that does that?
Source Code:
#include <clang/Frontend/FrontendPluginRegistry.h>
#include <clang/AST/AST.h>
#include <clang/AST/ASTConsumer.h>
#include <clang/AST/RecursiveASTVisitor.h>
#include "clang/Rewrite/Core/Rewriter.h"
#include <clang/Frontend/CompilerInstance.h>
#include <clang/Sema/Sema.h>
#include <llvm/Support/raw_ostream.h>
#include <string>
#include <sstream>
namespace {
class VarDeclVisitor : public clang::RecursiveASTVisitor<VarDeclVisitor> {
public:
VarDeclVisitor(clang::Rewriter& rewriter)
: rewriter_(rewriter)
{}
bool VisitCallExpr(clang::CallExpr *ce) {
auto q = ce->getType();
auto t = q.getTypePtrOrNull();
if (t != NULL) {
auto callee = ce->getDirectCallee();
auto funcName = callee->getNameInfo().getAsString();
if (funcName == "getInstanceName") {
rewriter_.ReplaceText(ce->getBeginLoc(), callee->getIdentifier()->getName().size(), "getInstanceName2");
}
}
return true;
}
private:
clang::Rewriter& rewriter_;
};
class MyASTConsumer : public clang::ASTConsumer {
using Base = clang::ASTConsumer;
public:
MyASTConsumer(std::unique_ptr<llvm::raw_pwrite_stream>&& os)
: os_(std::move(os))
{}
void HandleTranslationUnit(clang::ASTContext &context) override {
clang::Rewriter rewriter(context.getSourceManager(), context.getLangOpts());
VarDeclVisitor visitor(rewriter);
auto &source_manager = context.getSourceManager();
const auto &mainFileID = source_manager.getMainFileID();
const auto &decls = context.getTranslationUnitDecl()->decls();
for (auto &decl : decls) {
const auto &fileID = source_manager.getFileID(decl->getLocation());
if (fileID != mainFileID)
continue;
visitor.TraverseDecl(decl);
}
auto rwbuf = rewriter.getRewriteBufferFor(mainFileID);
if (rwbuf) {
*os_ << std::string(rwbuf->begin(), rwbuf->end());
} else {
*os_ << source_manager.getBufferData(mainFileID);
}
}
private:
std::unique_ptr<llvm::raw_pwrite_stream> os_;
};
class MyPlugin : public clang::PluginASTAction {
public:
std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(clang::CompilerInstance &compiler,
llvm::StringRef inFile) override {
auto os = compiler.createDefaultOutputFile(false, inFile, "cpp");
if (!os)
return nullptr;
return std::make_unique<MyASTConsumer>(std::move(os));
}
bool ParseArgs(const clang::CompilerInstance &compiler,
const std::vector<std::string> &args) override {
return true;
}
void PrintHelp(llvm::raw_ostream &ros) {}
};
}
static clang::FrontendPluginRegistry::Add<MyPlugin>
X("cash-pp", "My plugin");
Related
For some fun purposes, I'm writing a Tetris game. I created two classes: "GameBackground" and "Tetromino". Since the "Tetromino" class has a dependency on the "GameBackground" class, I injected this dependency in the constructor using an interface such that I can test the "Tetromino" class independent of the GameBackground class by mocking it. When I use gmock to create the "GameBackgroundMock", the test fails. When I use my own mock like
class MyOwnMock : public IGameBackground {
public:
MyOwnMock() : IGameBackground() {
};
bool RequestSpaceOnGrid(TetrominoPositionType requested_coordinates) override{
return true;
}
};
then the test passes. Apparently, I don't use the gmock in the right way. I would appreciate any suggestion leading to a green test by using gmock.
For implementation details see the following code basis.
My initial folder structure looks as follows:
[Project structure][1]
[1]: https://i.stack.imgur.com/7h8zT.png
The corresponding files at the project's top-level:
CMakeLists.txt.in:
# see https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project
cmake_minimum_required(VERSION 2.8.2)
project(googletest-download NONE)
include(ExternalProject)
ExternalProject_Add(googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG master
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src"
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build"
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""
TEST_COMMAND ""
)
CMakeLists.txt:
cmake_minimum_required(VERSION 3.11.3)
project(Test)
set(CMAKE_CXX_STANDARD 17)
# Download and unpack googletest at configure time
# (see https://github.com/google/googletest/blob/master/googletest/README.md#incorporating-into-an-existing-cmake-project)
configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt)
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "CMake step for googletest failed: ${result}")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} --build .
RESULT_VARIABLE result
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download )
if(result)
message(FATAL_ERROR "Build step for googletest failed: ${result}")
endif()
# Add googletest directly to our build. This defines the gtest and gtest_main targets.
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src
${CMAKE_CURRENT_BINARY_DIR}/googletest-build
EXCLUDE_FROM_ALL)
add_subdirectory(src)
add_subdirectory(test)
The content of the src folder:
CMakeLists.txt:
cmake_minimum_required(VERSION 3.11.3)
add_library(GameBackground_Lib STATIC GameBackground.cpp)
add_library(Tetromino_Lib STATIC Tetromino.cpp)
IGameBackground.h
#ifndef TEST_IGAMEBACKGROUND_H
#define TEST_IGAMEBACKGROUND_H
#include <utility>
#include <vector>
#include <iostream>
using TetrominoPositionType = std::vector<std::pair<int, int>>;
class IGameBackground {
public:
virtual ~IGameBackground() = default;
virtual bool RequestSpaceOnGrid(TetrominoPositionType) = 0;
};
#endif //TEST_IGAMEBACKGROUND_H
GameBackground.h
#ifndef TEST_GAMEBACKGROUND_H
#define TEST_GAMEBACKGROUND_H
#include "IGameBackground.h"
#include <tuple>
#include <utility>
#include <vector>
class GameBackground : public IGameBackground {
public:
GameBackground(int, int);
bool RequestSpaceOnGrid(TetrominoPositionType) override;
private:
int m_horizontal_grid_size;
int m_vertical_grid_size;
int m_nr_buttomlines_filled{};
std::vector<std::vector<bool>> m_occupancy_grid{};
};
#endif //TEST_GAMEBACKGROUND_H
GameBackground.cpp
#include "GameBackground.h"
GameBackground::GameBackground(int vertical_grid_size, int horizontal_grid_size)
: m_horizontal_grid_size{horizontal_grid_size},
m_vertical_grid_size{vertical_grid_size} {
m_occupancy_grid.resize(vertical_grid_size);
for (auto &row : m_occupancy_grid) {
row.resize(horizontal_grid_size);
}
}
bool GameBackground::RequestSpaceOnGrid(
TetrominoPositionType requested_coordinates) {
bool is_every_coordinate_within_bounds{true};
for (const auto &position : requested_coordinates) {
int pos_x{position.first}, pos_y{position.second};
if (pos_x < 0 || pos_x >= m_vertical_grid_size || pos_y < 0 ||
pos_y >= m_horizontal_grid_size) {
is_every_coordinate_within_bounds = false;
break;
}
}
bool is_request_successfull{false};
if (is_every_coordinate_within_bounds) {
try {
bool is_occupied{false};
for (const auto &position : requested_coordinates) {
int row{position.first}, column{position.second};
is_occupied |= m_occupancy_grid.at(row).at(column);
}
if (!is_occupied) {
for (const auto &position : requested_coordinates) {
int row{position.first}, column{position.second};
m_occupancy_grid.at(row).at(column) = true;
}
is_request_successfull = true;
}
} catch (std::out_of_range const &e) {
std::cerr << e.what() << '\n';
}
}
return is_request_successfull;
}
Tetromino.h
#ifndef TEST_TETROMINO_H
#define TEST_TETROMINO_H
#include <memory>
#include <utility>
#include <vector>
#include "IGameBackground.h"
enum class Direction { left, right, down };
using TetrominoPositionType = std::vector<std::pair<int, int>>;
class Tetromino {
public:
Tetromino(IGameBackground&, TetrominoPositionType);
TetrominoPositionType getPosition();
void setPosition(TetrominoPositionType);
void moveOneStep(Direction);
private:
TetrominoPositionType m_position;
IGameBackground& m_game_background;
};
#endif //TEST_TETROMINO_H
Tetromino.cpp
#include "Tetromino.h"
#include <utility>
Tetromino::Tetromino(IGameBackground &game_background,
TetrominoPositionType init_position)
: m_game_background{game_background},
m_position{std::move(init_position)} {};
TetrominoPositionType Tetromino::getPosition() { return m_position; }
void Tetromino::setPosition(TetrominoPositionType position) {
m_position = std::move(position);
}
void Tetromino::moveOneStep(Direction direction) {
TetrominoPositionType position = getPosition();
switch (direction) {
case Direction::down:
for (auto &pos : position) {
++pos.first;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
case Direction::left:
for (auto &pos : position) {
--pos.second;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
case Direction::right:
for (auto &pos : position) {
++pos.second;
}
if (m_game_background.RequestSpaceOnGrid(position)) {
setPosition(position);
}
break;
}
}
The content of the test folder
CMakeLists.txt
cmake_minimum_required(VERSION 3.11.3)
add_executable(TetrominoTest TetrominoTest.cpp)
target_link_libraries(TetrominoTest gmock_main gtest_main Tetromino_Lib)
TetrominoTest.cpp
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "../src/IGameBackground.h"
#include "../src/Tetromino.h"
//class MyOwnMock : public IGameBackground {
//public:
// MyOwnMock() : IGameBackground() {
// };
// bool RequestSpaceOnGrid(TetrominoPositionType requested_coordinates) override{
// return true;
// }
//};
class GameBackgroundMock : public IGameBackground {
public:
GameBackgroundMock() : IGameBackground() {
};
MOCK_METHOD(bool, RequestSpaceOnGrid,
(TetrominoPositionType requested_coordinates), (override));
};
class MoveTetromino : public ::testing::Test {
protected:
MoveTetromino() : unit_under_test(a_mock, init_position) {};
TetrominoPositionType init_position{{0, 0},
{0, 1},
{0, 2},
{0, 3}};
Tetromino unit_under_test;
GameBackgroundMock a_mock;
//MyOwnMock a_mock;
};
TEST_F(MoveTetromino, move_right) {
TetrominoPositionType current_position{init_position};
TetrominoPositionType expected_position{init_position};
for (auto &elem : expected_position) {
elem.second++;
}
ON_CALL(a_mock, RequestSpaceOnGrid(current_position)).WillByDefault(::testing::Return(true));
unit_under_test.moveOneStep(Direction::right);
TetrominoPositionType actual_position = unit_under_test.getPosition();
EXPECT_EQ(expected_position, actual_position);
}
You're using ON_CALL which sets a default value to be returned when a mocked method is being called. This is useful if you don't care how many times given method will be called in your tests and you just set a default value to be returned any number of times (however, expect calls are being matched first, it's a longer story).
It's better to use EXPECT_CALL in your case to explicitly state what actions you anticipate and validate them:
EXPECT_CALL(a_mock, RequestSpaceOnGrid(current_position)).WillOnce(::testing::Return(true));
If you will use this, gmock will let you know what's wrong with the test: the RequestSpaceOnGrid was called, however not with current_position but with expected_position. Not sure if this is expected, but this is what happens in the test.
The test passes by calling either
ON_CALL(a_mock, RequestSpaceOnGrid(expected_position)).WillByDefault(::testing::Return(true));
or as Quarra suggested
EXPECT_CALL(a_mock, RequestSpaceOnGrid(expected_position)).WillOnce(::testing::Return(true));
The crucial point was to give expected_position instead of current_position to the mocked RequestSpaceOnGrid method.
Another possibility would be to get the test green to call the mocked method with ::testing::_
ON_CALL(a_mock, RequestSpaceOnGrid(::testing::_)).WillByDefault(::testing::Return(true));
In this case, the mocked method would always return true independent of the given argument.
I've seen quite a numerous amount of examples that go over creating functions passes (e.g. Brandon Holt and Adrian Sampson), but I am curious as to the difficulty in creating a module pass to do these very similar problems. I've tried to implement a module pass to display the global variable names using this example and llvm source code to understand how you have to iterate through members.
I am using a source compiled version of LLVM, and using the example from the above links to add the pass, and then running:
$ clang -Xclang -load -Xclang build/Skeleton/libSkeletonPass.so something.c
Which then returns this gibberish. However, if I implement a functionPass and just use Auto to determine the type to be initialized it's very straight forward and works. Am I just going about printing the global variables the wrong way?
This is a pastebin of the error output from the terminal. link
Skeleton.cpp
#include "llvm/Pass.h"
#include "llvm/IR/Function.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/IR/LegacyPassManager.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/Transforms/IPO/PassManagerBuilder.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/IR/LLVMContext.h"
using namespace llvm;
namespace {
// Helper method for converting the name of a LLVM type to a string
static std::string LLVMTypeAsString(const Type *T) {
std::string TypeName;
raw_string_ostream N(TypeName);
T->print(N);
return N.str();
}
struct SkeletonPass : public ModulePass {
static char ID;
SkeletonPass() : ModulePass(ID) {}
virtual bool runOnModule(Module &M) {
for (Module::const_global_iterator GI = M.global_begin(),
GE = M.global_end(); GI != GE; ++GI) {
errs() << "Found global named: " << GI->getName()
<< "\tType: " << LLVMTypeAsString(GI->getType()) << "!\n";
}
return false;
}
};
}
char SkeletonPass::ID = 0;
// Automatically enable the pass.
// http://adriansampson.net/blog/clangpass.html
static void registerSkeletonPass(const PassManagerBuilder &,
legacy::PassManagerBase &PM) {
PM.add(new SkeletonPass());
}
static RegisterStandardPasses
RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible,
registerSkeletonPass);
something.c
int value0 = 5;
int main(int argc, char const *argv[])
{
int value = 4;
value += 1;
return 0;
}
I was able to figure this out after some extensive github searching. Here is the answer from which I was following a tutorial to help others who may be curious how to implement a Module Pass.
I'm new to llvm , and was trying to find lock declaration statement and then do some instrumention work,the code like this:
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;
int share = 42;
mutex m;
void f()
{
m.lock();
--share;
cout << "function f -> share: " << share << '\n';
m.unlock();
}
int main()
{
thread thf{f};
thf.join();
return 0;
}
I want to find the lock declaration instruction eg:
mutex m;
the llvm instrumention pass like this:
struct SkeletonPass : public FunctionPass {
static char ID;
SkeletonPass() : FunctionPass(ID) {}
virtual bool runOnFunction(Function &F) {
// Get the function to call from our runtime library.
LLVMContext &Ctx = F.getContext();
Constant *logFunc = F.getParent()->getOrInsertFunction(
"logop", Type::getVoidTy(Ctx), Type::getInt32Ty(Ctx), NULL
);
for (auto &B : F) {
for (auto &I : B) {
***if ((&I) is lock declaration instruction)*** {
// Insert something *after* `op`.
IRBuilder<> builder(op);
builder.SetInsertPoint(&B, ++builder.GetInsertPoint());
// Insert a call to function.
builder.CreateCall(logFunc, ConstantInt::get(Type::getInt32Ty(Ctx), 2));
return true;
}
}
}
In short, could you please tell me how to discover lock declaration instruction, thanks!
The declaration would appear as a global, so you should write a module pass to find it, not a function pass. It should appear as something like:
#m = global %mutex zeroinitializer
In fact, using the demo at http://ellcc.org/demo/index.cgi to try this, you can indeed see that:
...
%"class.std::__1::mutex" = type { %struct.pthread_mutex_t }
%struct.pthread_mutex_t = type { %union.anon }
%union.anon = type { [5 x i8*] }
...
#m = global %"class.std::__1::mutex" zeroinitializer, align 8
You can use LLVM's CppBackend to compile your code. This would produce a C++ code that makes up the source. You can then easily find out how mutex m; definition is constructed via LLVM API.
Run clang -march=cpp foo.cpp to use CppBackend. Alternatively, you can use this demo page to compile your code online.
I am using Qt 5.3 and trying to develop application for IOS.
Problem is, QWidget application in a iPhone Retina simulator:
QMessage becomes full-screen.
In Application output panel I see: This plugin does not support
propagateSizeHints().
So looking for alternative solution for QMessageBox. I don't want to learn QML yet.
If you do an overlay on top of your widget you can make something similar to the iOS popups.
Basically you create another widget, and you parent it to the widget you want it to be drawn on top of.
Here are some helpful flags and lines of code to put in your overlay constructor:
setPalette(Qt::transparent);
// if you have buttons on this overlay you probably don't want this one
setAttribute(Qt::WA_TransparentForMouseEvents);
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(20);
this->setGraphicsEffect(dse);
Then be sure to command a resize of your overlay when the parent widget resizes:
void ParentWidget::resizeEvent(QResizeEvent *event)
{
overlay->resize(event->size());
event->accept();
}
http://www.qtcentre.org/wiki/index.php?title=Widget_Overlay
UPDATE: Awesome example
main.cpp
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.resize(300,600);
return a.exec();
}
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "overlaydialogbox.h"
#include <QResizeEvent>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
public slots:
void resizeEvent(QResizeEvent *event);
private:
OverlayDialogBox * m_overlay;
};
#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
m_overlay = new OverlayDialogBox(this);
}
MainWindow::~MainWindow() { }
void MainWindow::resizeEvent(QResizeEvent *event)
{
m_overlay->resize(event->size());
event->accept();
}
overlaydialogbox.h
#ifndef OVERLAYDIALOGBOX_H
#define OVERLAYDIALOGBOX_H
#include <QWidget>
class OverlayDialogBox : public QWidget
{
Q_OBJECT
public:
explicit OverlayDialogBox(QWidget *parent = 0);
signals:
void accepted();
void rejected();
void finished(int);
public slots:
};
#endif // OVERLAYDIALOGBOX_H
overlaydialogbox.cpp
#include "overlaydialogbox.h"
#include <QGridLayout>
#include <QGraphicsEffect>
#include <QLabel>
#include <QDialogButtonBox>
#include <QMessageBox>
#include <QIcon>
OverlayDialogBox::OverlayDialogBox(QWidget *parent) :
QWidget(parent)
{
setPalette(Qt::transparent);
// if you have buttons on this overlay you probably don't want this one
// setAttribute(Qt::WA_TransparentForMouseEvents);
QGraphicsDropShadowEffect * dse = new QGraphicsDropShadowEffect();
dse->setBlurRadius(20);
this->setGraphicsEffect(dse);
QGridLayout * grid = new QGridLayout();
this->setLayout(grid);
QMessageBox * msg = new QMessageBox(QMessageBox::Warning,"Testing","This is a test QMessageBox.");
QObject::connect(msg, SIGNAL(accepted()), this, SIGNAL(accepted()));
QObject::connect(msg, SIGNAL(finished(int)), this, SIGNAL(finished(int)));
QObject::connect(msg, SIGNAL(rejected()), this, SIGNAL(rejected()));
QObject::connect(msg, SIGNAL(finished(int)), this, SLOT(close()));
msg->setPalette(Qt::white);
grid->addWidget(msg);
}
Hope that helps.
I want to find out type informations of every functions using by Clang libtool.
However, VisitReturnStmt sometimes cannot find any return statements.
Also, class type return(eg. ASTConsumer * in "CreateASTConsumer" method) is converted to "int *" type.
(another case: bool -> _Bool)
How can I find out real return types for every functions?
Thanks in advance for your help.
The tool source and the input cpp source are same as follows.
#include "clang/Driver/Options.h"
#include "clang/AST/AST.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Tooling.h"
using namespace std;
using namespace clang;
using namespace clang::driver;
using namespace clang::tooling;
using namespace llvm;
Rewriter TheRewriter;
class ExampleVisitor : public RecursiveASTVisitor<ExampleVisitor> {
private:
ASTContext *astContext; // used for getting additional AST info
public:
explicit ExampleVisitor(CompilerInstance *CI)
: astContext(&(CI->getASTContext())) // initialize private members
{
TheRewriter.setSourceMgr(astContext->getSourceManager(), astContext->getLangOpts());
}
virtual bool VisitReturnStmt(ReturnStmt *ReturnStatement) {
ReturnStatement->getRetValue()->dump(TheRewriter.getSourceMgr());
return true;
}
virtual bool VisitStmt(Stmt *S) {
S->dump(TheRewriter.getSourceMgr());
return true;
}
};
class ExampleASTConsumer : public ASTConsumer {
private:
ExampleVisitor *visitor; // doesn't have to be private
public:
// override the constructor in order to pass CI
explicit ExampleASTConsumer(CompilerInstance *CI)
: visitor(new ExampleVisitor(CI)) // initialize the visitor
{ }
// override this to call our ExampleVisitor on the entire source file
virtual void HandleTranslationUnit(ASTContext &Context) {
/* we can use ASTContext to get the TranslationUnitDecl, which is
a single Decl that collectively represents the entire source file */
visitor->TraverseDecl(Context.getTranslationUnitDecl());
}
};
class ExampleFrontendAction : public ASTFrontendAction {
public:
virtual ASTConsumer *CreateASTConsumer(CompilerInstance &CI, StringRef file) {
return new ExampleASTConsumer(&CI); // pass CI pointer to ASTConsumer
}
};
int main(int argc, const char **argv) {
// parse the command-line args passed to your code
CommonOptionsParser op(argc, argv);
// create a new Clang Tool instance (a LibTooling environment)
ClangTool Tool(op.getCompilations(), op.getSourcePathList());
// run the Clang Tool, creating a new FrontendAction (explained below)
int result = Tool.run(newFrontendActionFactory<ExampleFrontendAction>());
return result;
}
If I'm interpreting the clang docs correctly
Note that GCC allows return with no argument in a function declared to return a value, and it allows returning a value in functions declared to return void. We explicitly model this in the AST, which means you can't depend on the return type of the function and the presence of an argument.
this implies that you can't reliably infer the return type of a function from its return statement.
If you want to find the return type of a function, you could visit FunctionDecl nodes and call FunctionDecl::getReturnType() on them