Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

ABI Generator: Add detection of EOSIO_ABI macro #2051

Merged
merged 3 commits into from
Apr 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions contracts/eosiolib/dispatcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ namespace eosio {
return eosio::dispatch<Contract,SecondAction,Actions...>( code, act );
}

template<typename T, typename... Args>
bool execute_action( T* obj, void (T::*func)(Args...) ) {
template<typename T, typename Q, typename... Args>
bool execute_action( T* obj, void (Q::*func)(Args...) ) {
char buffer[action_data_size()];
read_action_data( buffer, sizeof(buffer) );

Expand Down
116 changes: 78 additions & 38 deletions libraries/abi_generator/abi_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace eosio {

void abi_generator::set_target_contract(const string& contract, const vector<string>& actions) {
target_contract = contract;
target_actions = actions;
}

void abi_generator::enable_optimizaton(abi_generator::optimization o) {
optimizations |= o;
}
Expand Down Expand Up @@ -82,58 +87,77 @@ bool abi_generator::inspect_type_methods_for_actions(const Decl* decl) { try {
const auto* rec_decl = dyn_cast<CXXRecordDecl>(decl);
if(rec_decl == nullptr) return false;

if( rec_decl->getName().str() != target_contract )
return false;

const auto* type = rec_decl->getTypeForDecl();
ABI_ASSERT(type != nullptr);
if( !type->isStructureOrClassType() ) {
return false;
}

bool at_least_one_action = false;
for(const auto* method : rec_decl->methods()) {

const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(method);
if( raw_comment == nullptr) continue;
auto export_method = [&](const CXXMethodDecl* method) {

SourceManager& source_manager = ast_context->getSourceManager();
string raw_text = raw_comment->getRawText(source_manager);
regex r(R"(@abi (action))");
auto method_name = method->getNameAsString();

smatch smatch;
if(regex_search(raw_text, smatch, r)){

auto method_name = method->getNameAsString();
ABI_ASSERT(find_struct(method_name) == nullptr, "action already exists ${method_name}", ("method_name",method_name));
if( std::find(target_actions.begin(), target_actions.end(), method_name) == target_actions.end() )
return;

struct_def abi_struct;
for(const auto* p : method->parameters() ) {
clang::QualType qt = p->getOriginalType().getNonReferenceType();
qt.setLocalFastQualifiers(0);
ABI_ASSERT(find_struct(method_name) == nullptr, "action already exists ${method_name}", ("method_name",method_name));

string field_name = p->getNameAsString();
string field_type_name = add_type(qt);
struct_def abi_struct;
for(const auto* p : method->parameters() ) {
clang::QualType qt = p->getOriginalType().getNonReferenceType();
qt.setLocalFastQualifiers(0);

field_def struct_field{field_name, field_type_name};
ABI_ASSERT(is_builtin_type(get_vector_element_type(struct_field.type))
|| find_struct(get_vector_element_type(struct_field.type))
|| find_type(get_vector_element_type(struct_field.type))
, "Unknown type ${type} [${abi}]",("type",struct_field.type)("abi",*output));
string field_name = p->getNameAsString();
string field_type_name = add_type(qt);

type_size[string(struct_field.type)] = is_vector(struct_field.type) ? 0 : ast_context->getTypeSize(qt);
abi_struct.fields.push_back(struct_field);
}
field_def struct_field{field_name, field_type_name};
ABI_ASSERT(is_builtin_type(get_vector_element_type(struct_field.type))
|| find_struct(get_vector_element_type(struct_field.type))
|| find_type(get_vector_element_type(struct_field.type))
, "Unknown type ${type} [${abi}]",("type",struct_field.type)("abi",*output));

abi_struct.name = method_name;
abi_struct.base = "";
type_size[string(struct_field.type)] = is_vector(struct_field.type) ? 0 : ast_context->getTypeSize(qt);
abi_struct.fields.push_back(struct_field);
}

output->structs.push_back(abi_struct);
abi_struct.name = method_name;
abi_struct.base = "";

full_types[method_name] = method_name;
output->structs.push_back(abi_struct);

output->actions.push_back({method_name, method_name});
at_least_one_action = true;
}
full_types[method_name] = method_name;

output->actions.push_back({method_name, method_name});
at_least_one_action = true;
};

const auto export_methods = [&export_method](const CXXRecordDecl* rec_decl) {

}

auto export_methods_impl = [&export_method](const CXXRecordDecl* rec_decl, auto& ref) -> void {


auto tmp = rec_decl->bases();
auto rec_name = rec_decl->getName().str();

rec_decl->forallBases([&ref](const CXXRecordDecl* base) -> bool {
ref(base, ref);
return true;
});

for(const auto* method : rec_decl->methods()) {
export_method(method);
}

};

export_methods_impl(rec_decl, export_methods_impl);
};

export_methods(rec_decl);

return at_least_one_action;

Expand All @@ -145,23 +169,39 @@ void abi_generator::handle_decl(const Decl* decl) { try {
ABI_ASSERT(output != nullptr);
ABI_ASSERT(ast_context != nullptr);

// Only process declarations that has the `abi_context` folder as parent.
SourceManager& source_manager = ast_context->getSourceManager();
auto file_name = source_manager.getFilename(decl->getLocStart());
if ( !abi_context.empty() && !file_name.startswith(abi_context) ) {
return;
}

if(inspect_type_methods_for_actions(decl)) {
return;
// If EOSIO_ABI macro was found, check if the current declaration
// is of the type specified in the macro and export their methods (actions).
bool type_has_actions = false;
if( target_contract.size() ) {
type_has_actions = inspect_type_methods_for_actions(decl);
}

// The current Decl was the type referenced in EOSIO_ABI macro
if( type_has_actions ) return;

// The current Decl was not the type referenced in EOSIO_ABI macro
// so we try to see if it has comments attached to the declaration
const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(decl);
if(raw_comment == nullptr) {
return;
}

string raw_text = raw_comment->getRawText(source_manager);
regex r(R"(@abi (action|table)((?: [a-z0-9]+)*))");
regex r;

// If EOSIO_ABI macro was found, we will only check if the current Decl
// is intented to be an ABI table record, otherwise we check for both (action or table)
if( target_contract.size() )
r = regex(R"(@abi (table)((?: [a-z0-9]+)*))");
else
r = regex(R"(@abi (action|table)((?: [a-z0-9]+)*))");

smatch smatch;
while(regex_search(raw_text, smatch, r))
Expand Down
103 changes: 89 additions & 14 deletions libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Sema/Sema.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/MacroArgs.h"
#include "clang/Tooling/Tooling.h"
#include "clang/Tooling/CommonOptionsParser.h"
#include "clang/Tooling/Core/QualTypeNames.h"
#include "llvm/Support/raw_ostream.h"
#include <boost/algorithm/string.hpp>
#include <boost/range/algorithm_ext/erase.hpp>

using namespace clang;
using namespace std;
Expand All @@ -40,7 +43,7 @@ namespace cl = llvm::cl;
namespace eosio {

FC_DECLARE_EXCEPTION( abi_generation_exception, 999999, "Unable to generate abi" );

#define ABI_ASSERT( TEST, ... ) \
FC_EXPAND_MACRO( \
FC_MULTILINE_MACRO_BEGIN \
Expand All @@ -52,7 +55,7 @@ namespace eosio {
} \
FC_MULTILINE_MACRO_END \
)

/**
* @brief Generates eosio::abi_def struct handling events from ASTConsumer
*/
Expand All @@ -66,33 +69,35 @@ namespace eosio {
map<string, string> full_types;
string abi_context;
clang::ASTContext* ast_context;
string target_contract;
vector<string> target_actions;
public:

enum optimization {
OPT_SINGLE_FIELD_STRUCT
};

abi_generator()
:optimizations(0)
, output(nullptr)
, compiler_instance(nullptr)
, ast_context(nullptr)
{}

~abi_generator() {}

/**
* @brief Enable optimization when generating ABI
* @param o optimization to enable
*/
void enable_optimizaton(optimization o);

/**
* @brief Check if an optimization is enabled
* @param o optimization to check
*/
bool is_opt_enabled(optimization o);

/**
* @brief Set the destination ABI struct to write
* @param output ABI destination
Expand Down Expand Up @@ -123,6 +128,8 @@ namespace eosio {
*/
void handle_tagdecl_definition(TagDecl* tag_decl);

void set_target_contract(const string& contract, const vector<string>& actions);

private:
bool inspect_type_methods_for_actions(const Decl* decl);

Expand Down Expand Up @@ -186,37 +193,105 @@ namespace eosio {

QualType get_vector_element_type(const clang::QualType& qt);
string get_vector_element_type(const string& type_name);

clang::QualType get_named_type_if_elaborated(const clang::QualType& qt);

const clang::RecordDecl::field_range get_struct_fields(const clang::QualType& qt);
clang::CXXRecordDecl::base_class_range get_struct_bases(const clang::QualType& qt);
};

struct abi_generator_astconsumer : public ASTConsumer {
abi_generator& abi_gen;

abi_generator_astconsumer(CompilerInstance& compiler_instance, abi_generator& abi_gen)
:abi_gen(abi_gen)
{
abi_gen.set_compiler_instance(compiler_instance);
}

void HandleTagDeclDefinition(TagDecl* tag_decl) override {
abi_gen.handle_tagdecl_definition(tag_decl);
}
};


struct find_eosio_abi_macro_action : public PreprocessOnlyAction {

string& contract;
vector<string>& actions;
const string& abi_context;

find_eosio_abi_macro_action(string& contract, vector<string>& actions, const string& abi_context
): contract(contract),
actions(actions), abi_context(abi_context) {
}

struct callback_handler : public PPCallbacks {

CompilerInstance& compiler_instance;
find_eosio_abi_macro_action& act;

callback_handler(CompilerInstance& compiler_instance, find_eosio_abi_macro_action& act)
: compiler_instance(compiler_instance), act(act) {}

void MacroExpands (const Token &token, const MacroDefinition &md, SourceRange range, const MacroArgs *args) override {

auto* id = token.getIdentifierInfo();
if( id == nullptr ) return;
if( id->getName() != "EOSIO_ABI" ) return;

const auto& sm = compiler_instance.getSourceManager();
auto file_name = sm.getFilename(range.getBegin());
if ( !act.abi_context.empty() && !file_name.startswith(act.abi_context) ) {
return;
}

ABI_ASSERT( md.getMacroInfo()->getNumArgs() == 2 );

clang::SourceLocation b(range.getBegin()), _e(range.getEnd());
clang::SourceLocation e(clang::Lexer::getLocForEndOfToken(_e, 0, sm, compiler_instance.getLangOpts()));
auto macrostr = string(sm.getCharacterData(b), sm.getCharacterData(e)-sm.getCharacterData(b));

regex r(R"(EOSIO_ABI\s*\(\s*(.+?)\s*,((?:.+?)*)\s*\))");
smatch smatch;
auto res = regex_search(macrostr, smatch, r);
ABI_ASSERT( res );

act.contract = smatch[1].str();

auto actions_str = smatch[2].str();
boost::trim(actions_str);
actions_str = actions_str.substr(1);
actions_str.pop_back();
boost::remove_erase_if(actions_str, boost::is_any_of(" ("));

boost::split(act.actions, actions_str, boost::is_any_of(")"));
}
};

void ExecuteAction() override {
getCompilerInstance().getPreprocessor().addPPCallbacks(
llvm::make_unique<callback_handler>(getCompilerInstance(), *this)
);
PreprocessOnlyAction::ExecuteAction();
};

};

class generate_abi_action : public ASTFrontendAction {

private:
set<string> parsed_templates;
abi_generator abi_gen;

public:
generate_abi_action(bool verbose, bool opt_sfs, string abi_context, abi_def& output) {

generate_abi_action(bool verbose, bool opt_sfs, string abi_context,
abi_def& output, const string& contract, const vector<string>& actions) {

abi_gen.set_output(output);
abi_gen.set_verbose(verbose);
abi_gen.set_abi_context(abi_context);
abi_gen.set_target_contract(contract, actions);

if(opt_sfs)
abi_gen.enable_optimizaton(abi_generator::OPT_SINGLE_FIELD_STRUCT);
Expand Down
Loading