From 68081d87c972c63e2742a2d8f25b4334684c9179 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 3 Apr 2018 02:21:08 -0300 Subject: [PATCH 1/3] Allow a contract that uses EOSIO_ABI macro to inherit from another contract --- contracts/eosiolib/dispatcher.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/eosiolib/dispatcher.hpp b/contracts/eosiolib/dispatcher.hpp index fb4cf7891bf..8493b56c7f2 100644 --- a/contracts/eosiolib/dispatcher.hpp +++ b/contracts/eosiolib/dispatcher.hpp @@ -37,8 +37,8 @@ namespace eosio { return eosio::dispatch( code, act ); } - template - bool execute_action( T* obj, void (T::*func)(Args...) ) { + template + bool execute_action( T* obj, void (Q::*func)(Args...) ) { char buffer[action_data_size()]; read_action_data( buffer, sizeof(buffer) ); From 20a88a1f77837259d72b6bdd8652ce81e08c6c35 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 3 Apr 2018 02:23:49 -0300 Subject: [PATCH 2/3] eosio-abigen: Add detection of EOSIO_ABI macro --- libraries/abi_generator/abi_generator.cpp | 116 ++++++++++++------ .../eosio/abi_generator/abi_generator.hpp | 103 +++++++++++++--- programs/eosio-abigen/main.cpp | 74 ++++++++--- 3 files changed, 223 insertions(+), 70 deletions(-) diff --git a/libraries/abi_generator/abi_generator.cpp b/libraries/abi_generator/abi_generator.cpp index 35466a8fff1..eb34b80dceb 100644 --- a/libraries/abi_generator/abi_generator.cpp +++ b/libraries/abi_generator/abi_generator.cpp @@ -2,6 +2,11 @@ namespace eosio { +void abi_generator::set_target_contract(const string& contract, const vector& actions) { + target_contract = contract; + target_actions = actions; +} + void abi_generator::enable_optimizaton(abi_generator::optimization o) { optimizations |= o; } @@ -82,58 +87,77 @@ bool abi_generator::inspect_type_methods_for_actions(const Decl* decl) { try { const auto* rec_decl = dyn_cast(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; @@ -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)) diff --git a/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp b/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp index 4949c8cc506..ee0855661c9 100644 --- a/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp +++ b/libraries/abi_generator/include/eosio/abi_generator/abi_generator.hpp @@ -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 +#include using namespace clang; using namespace std; @@ -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 \ @@ -52,7 +55,7 @@ namespace eosio { } \ FC_MULTILINE_MACRO_END \ ) - + /** * @brief Generates eosio::abi_def struct handling events from ASTConsumer */ @@ -66,33 +69,35 @@ namespace eosio { map full_types; string abi_context; clang::ASTContext* ast_context; + string target_contract; + vector 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 @@ -123,6 +128,8 @@ namespace eosio { */ void handle_tagdecl_definition(TagDecl* tag_decl); + void set_target_contract(const string& contract, const vector& actions); + private: bool inspect_type_methods_for_actions(const Decl* decl); @@ -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& actions; + const string& abi_context; + + find_eosio_abi_macro_action(string& contract, vector& 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(getCompilerInstance(), *this) + ); + PreprocessOnlyAction::ExecuteAction(); + }; + + }; + class generate_abi_action : public ASTFrontendAction { + private: set 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& 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); diff --git a/programs/eosio-abigen/main.cpp b/programs/eosio-abigen/main.cpp index 02116bb0542..bff5b6fd696 100644 --- a/programs/eosio-abigen/main.cpp +++ b/programs/eosio-abigen/main.cpp @@ -8,19 +8,52 @@ using namespace eosio::chain::contracts; using mvo = fc::mutable_variant_object; -std::unique_ptr create_factory(bool verbose, bool opt_sfs, string abi_context, abi_def& output) { - class abi_frontend_action_factory : public FrontendActionFactory { - bool verbose; - bool opt_sfs; +std::unique_ptr create_factory(bool verbose, bool opt_sfs, string abi_context, abi_def& output, const string& contract, const vector& actions) { + + struct abi_frontend_action_factory : public FrontendActionFactory { + + bool verbose; + bool opt_sfs; + string abi_context; + abi_def& output; + const string& contract; + const vector& actions; + + abi_frontend_action_factory(bool verbose, bool opt_sfs, string abi_context, + abi_def& output, const string& contract, const vector& actions) : verbose(verbose), + abi_context(abi_context), output(output), contract(contract), actions(actions) {} + + clang::FrontendAction *create() override { + return new generate_abi_action(verbose, opt_sfs, abi_context, output, contract, actions); + } + + }; + + return std::unique_ptr( + new abi_frontend_action_factory(verbose, opt_sfs, abi_context, output, contract, actions) + ); +} + +std::unique_ptr create_find_macro_factory(string& contract, vector& actions, string abi_context) { + + struct abi_frontend_macro_action_factory : public FrontendActionFactory { + + string& contract; + vector& actions; string abi_context; - abi_def& output; - public: - abi_frontend_action_factory(bool verbose, bool opt_sfs, string abi_context, abi_def& output) : verbose(verbose), abi_context(abi_context), output(output) {} - clang::FrontendAction *create() override { return new generate_abi_action(verbose, opt_sfs, abi_context, output); } + + abi_frontend_macro_action_factory (string& contract, vector& actions, + string abi_context ) : contract(contract), actions(actions), abi_context(abi_context) {} + + clang::FrontendAction *create() override { + return new find_eosio_abi_macro_action(contract, actions, abi_context); + } + }; return std::unique_ptr( - new abi_frontend_action_factory(verbose, opt_sfs, abi_context, output)); + new abi_frontend_macro_action_factory(contract, actions, abi_context) + ); } static cl::OptionCategory abi_generator_category("ABI generator options"); @@ -49,20 +82,25 @@ int main(int argc, const char **argv) { abi_def output; try { CommonOptionsParser op(argc, argv, abi_generator_category); ClangTool Tool(op.getCompilations(), op.getSourcePathList()); - int result = Tool.run(create_factory(abi_verbose, abi_opt_sfs, abi_context, output).get()); + string contract; + vector actions; + int result = Tool.run(create_find_macro_factory(contract, actions, abi_context).get()); if(!result) { - abi_serializer(output).validate(); + result = Tool.run(create_factory(abi_verbose, abi_opt_sfs, abi_context, output, contract, actions).get()); + if(!result) { + abi_serializer(output).validate(); - fc::variant vabi; - to_variant(output, vabi); + fc::variant vabi; + to_variant(output, vabi); - auto comment = fc::format_string( - "This file was generated by eosio-abigen. DO NOT EDIT - ${ts}", - mvo("ts",fc::time_point_sec(fc::time_point::now()).to_iso_string())); + auto comment = fc::format_string( + "This file was generated by eosio-abigen. DO NOT EDIT - ${ts}", + mvo("ts",fc::time_point_sec(fc::time_point::now()).to_iso_string())); - auto abi_with_comment = mvo("____comment", comment)(mvo(vabi)); + auto abi_with_comment = mvo("____comment", comment)(mvo(vabi)); - fc::json::save_to_file(abi_with_comment, abi_destination, true); + fc::json::save_to_file(abi_with_comment, abi_destination, true); + } } return result; } FC_CAPTURE_AND_LOG((output)); return -1; } From a7899404aabc5295b088ffba413dee1daa9cced7 Mon Sep 17 00:00:00 2001 From: Matias Romeo Date: Tue, 3 Apr 2018 02:25:38 -0300 Subject: [PATCH 3/3] eosio-abigen: Add new tests --- tests/tests/abi_tests.cpp | 154 +++++++++++++++++++++++++++++++++++++- tests/tests/config.hpp.in | 1 + 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/tests/tests/abi_tests.cpp b/tests/tests/abi_tests.cpp index 5e224a1aa26..f5a908e7b4e 100644 --- a/tests/tests/abi_tests.cpp +++ b/tests/tests/abi_tests.cpp @@ -390,15 +390,32 @@ struct abi_gen_helper { bool generate_abi(const char* source, const char* abi, bool opt_sfs=false) { std::string include_param = std::string("-I") + eosiolib_path; + std::string pfr_include_param = std::string("-I") + pfr_include_path; std::string boost_include_param = std::string("-I") + boost_include_path; std::string stdcpp_include_param = std::string("-I") + eosiolib_path + "/libc++/upstream/include"; std::string stdc_include_param = std::string("-I") + eosiolib_path + "/musl/upstream/include"; - abi_def output; - bool res = runToolOnCodeWithArgs(new generate_abi_action(false, opt_sfs, "", output), source, - {"-fparse-all-comments", "--std=c++14", "--target=wasm32", "-ffreestanding", "-nostdlib", "-nostdlibinc", "-fno-threadsafe-statics", "-fno-rtti", "-fno-exceptions", include_param, boost_include_param, stdcpp_include_param, stdc_include_param }); + abi_def output; + std::string contract; + std::vector actions; + + bool res = runToolOnCodeWithArgs(new find_eosio_abi_macro_action(contract, actions, ""), source, + {"-fparse-all-comments", "--std=c++14", "--target=wasm32", "-ffreestanding", "-nostdlib", + "-nostdlibinc", "-fno-threadsafe-statics", "-fno-rtti", "-fno-exceptions", + include_param, boost_include_param, stdcpp_include_param, + stdc_include_param, pfr_include_param } + ); + FC_ASSERT(res == true); + + res = runToolOnCodeWithArgs(new generate_abi_action(false, opt_sfs, "", output, contract, actions), source, + {"-fparse-all-comments", "--std=c++14", "--target=wasm32", "-ffreestanding", "-nostdlib", + "-nostdlibinc", "-fno-threadsafe-statics", "-fno-rtti", "-fno-exceptions", + include_param, boost_include_param, stdcpp_include_param, + stdc_include_param, pfr_include_param } + ); FC_ASSERT(res == true); + abi_serializer(chain_initializer::eos_contract_abi(output)).validate(); auto abi1 = fc::json::from_string(abi).as(); @@ -1612,6 +1629,137 @@ BOOST_FIXTURE_TEST_CASE(abgigen_vector_alias, abi_gen_helper) } FC_LOG_AND_RETHROW() } +BOOST_FIXTURE_TEST_CASE(abgigen_eosioabi_macro, abi_gen_helper) +{ try { + + const char* abgigen_eosioabi_macro = R"=====( + + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpointer-bool-conversion" + + #include + #include + + + using namespace eosio; + + struct hello : public eosio::contract { + public: + using contract::contract; + + void hi( account_name user ) { + print( "Hello, ", name{user} ); + } + + void bye( account_name user ) { + print( "Bye, ", name{user} ); + } + }; + + EOSIO_ABI(hello,(hi)) + + #pragma GCC diagnostic pop + + )====="; + + const char* abgigen_eosioabi_macro_abi = R"=====( + { + "types": [], + "structs": [{ + "name": "hi", + "base": "", + "fields": [{ + "name": "user", + "type": "account_name" + } + ] + } + ], + "actions": [{ + "name": "hi", + "type": "hi" + } + ], + "tables": [] + } + )====="; + + BOOST_TEST( generate_abi(abgigen_eosioabi_macro, abgigen_eosioabi_macro_abi) == true ); + +} FC_LOG_AND_RETHROW() } + +BOOST_FIXTURE_TEST_CASE(abgigen_contract_inheritance, abi_gen_helper) +{ try { + + const char* abgigen_contract_inheritance = R"=====( + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wpointer-bool-conversion" + + #include + #include + + + using namespace eosio; + + struct hello : public eosio::contract { + public: + using contract::contract; + + void hi( account_name user ) { + print( "Hello, ", name{user} ); + } + }; + + struct new_hello : hello { + public: + new_hello(account_name self) : hello(self) {} + void bye( account_name user ) { + print( "Bye, ", name{user} ); + } + }; + + EOSIO_ABI(new_hello,(hi)(bye)) + + #pragma GCC diagnostic pop + )====="; + + const char* abgigen_contract_inheritance_abi = R"=====( + { + "types": [], + "structs": [{ + "name": "hi", + "base": "", + "fields": [{ + "name": "user", + "type": "account_name" + } + ] + },{ + "name": "bye", + "base": "", + "fields": [{ + "name": "user", + "type": "account_name" + } + ] + } + ], + "actions": [{ + "name": "hi", + "type": "hi" + },{ + "name": "bye", + "type": "bye" + } + ], + "tables": [] + } + )====="; + + BOOST_TEST( generate_abi(abgigen_contract_inheritance, abgigen_contract_inheritance_abi) == true ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_CASE(general) { try { diff --git a/tests/tests/config.hpp.in b/tests/tests/config.hpp.in index 35ed7e8e8ca..7010fb336a1 100644 --- a/tests/tests/config.hpp.in +++ b/tests/tests/config.hpp.in @@ -5,5 +5,6 @@ namespace eosio { namespace tests { namespace config { constexpr char eosiolib_path[] = "${CMAKE_CURRENT_SOURCE_DIR}/../contracts"; + constexpr char pfr_include_path[] = "${CMAKE_CURRENT_SOURCE_DIR}/../externals/magic_get/include"; constexpr char boost_include_path[] = "${Boost_INCLUDE_DIR}"; }}}