├── .gitignore ├── test ├── data │ ├── 01.in.bz2 │ ├── 02.in.bz2 │ ├── 03.in.bz2 │ ├── 04.in.bz2 │ ├── 05.in.bz2 │ ├── 06.in.bz2 │ ├── 03.out │ ├── 06.out │ ├── 01.out │ ├── 02.out │ ├── 04.out │ └── 05.out ├── test_plantuml.sh.in ├── src │ ├── shared │ │ ├── shared.cpp │ │ ├── main.cpp │ │ └── shared.h │ └── demo │ │ └── main.cpp └── CMakeLists.txt ├── src ├── YamlGenerator.h ├── StmListGenerator.h ├── TextGenerator.h ├── ObjDumpParser.h ├── ScParser.h ├── ParserHelpers.h ├── StmListGenerator.cpp ├── AbstractGenerator.h ├── PlantUmlGenerator.h ├── TextGenerator.cpp ├── ScModel.cpp ├── YamlGenerator.cpp ├── ObjDumpParser.cpp ├── ScModel.h ├── PlantUmlGenerator.cpp ├── main.cpp └── ScParser.cpp ├── cmake ├── CompilerFlags.cmake ├── AddressSanitizer.cmake └── ClangTidy.cmake ├── doc └── make_all_img.sh ├── .circleci └── config.yml ├── .clang-tidy ├── CMakeLists.txt ├── LICENSE ├── .clang-format └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *.user 3 | -------------------------------------------------------------------------------- /test/data/01.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/01.in.bz2 -------------------------------------------------------------------------------- /test/data/02.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/02.in.bz2 -------------------------------------------------------------------------------- /test/data/03.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/03.in.bz2 -------------------------------------------------------------------------------- /test/data/04.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/04.in.bz2 -------------------------------------------------------------------------------- /test/data/05.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/05.in.bz2 -------------------------------------------------------------------------------- /test/data/06.in.bz2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kanje/bosce/HEAD/test/data/06.in.bz2 -------------------------------------------------------------------------------- /test/data/03.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "SM\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "First" as 2 { 5 | } 6 | 2-->3 : Event\n 7 | state "Second<0>" as 3 { 8 | } 9 | @enduml 10 | -------------------------------------------------------------------------------- /test/test_plantuml.sh.in: -------------------------------------------------------------------------------- 1 | bzip2 -cdk @CMAKE_CURRENT_LIST_DIR@/data/$1.in.bz2 > ./$1.in.tmp 2 | @CMAKE_BINARY_DIR@/bosce -O ./$1.in.tmp -s $2 > ./$1.out.tmp 3 | sed -i 's/[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{4\}/00.00.0000/' ./$1.out.tmp 4 | diff ./$1.out.tmp @CMAKE_CURRENT_LIST_DIR@/data/$1.out 5 | -------------------------------------------------------------------------------- /src/YamlGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "AbstractGenerator.h" 4 | 5 | class YamlGenerator final : public AbstractGenerator 6 | { 7 | using AbstractGenerator::AbstractGenerator; 8 | 9 | public: 10 | void generate(std::ostream &output, const ScName &stmName) override; 11 | 12 | private: 13 | ScName hlText(const ScName &name) const; 14 | }; 15 | -------------------------------------------------------------------------------- /test/src/shared/shared.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "shared.h" 10 | 11 | sc::result Off::react(const EvToggle &) 12 | { 13 | return transit(); 14 | } 15 | -------------------------------------------------------------------------------- /test/src/shared/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "shared.h" 10 | 11 | int main() 12 | { 13 | OnOff stm; 14 | stm.initiate(); 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /test/data/06.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "Plinio::OtherStateMachine\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "StateA" as 2 { 5 | } 6 | 2-->2 : EventA\n 7 | 2-->3 : EventB\n 8 | 2-->4 : EventC\n 9 | state "StateB" as 3 { 10 | } 11 | 3-->2 : EventA\n 12 | 3-->3 : EventB\n 13 | 3-->4 : EventC\n 14 | state "StateC" as 4 { 15 | } 16 | 4-->2 : EventA\n 17 | 4-->3 : EventB\n 18 | 4-->4 : EventC\n 19 | @enduml 20 | -------------------------------------------------------------------------------- /test/data/01.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "StopWatch\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "Active" as 2 { 5 | [*] --> 3 6 | state "Running" as 4 { 7 | } 8 | 4-->3 : EvStartStop\n 9 | state "Stopped" as 3 { 10 | } 11 | 3-->4 : EvStartStop\n 12 | } 13 | 2 : Deferrals: 14 | 2 : * EvTerminate 15 | 2-->2 : EvPlayMusic\nEvReset\n 16 | 2-->5 : EvPlayMusic\n 17 | state "MusicBoxMode" as 5 { 18 | } 19 | 5-->2 : EvReset\n 20 | @enduml 21 | -------------------------------------------------------------------------------- /test/data/02.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "StopWatch\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "Active" as 2 { 5 | [*] --> 3 6 | state "Running" as 4 { 7 | } 8 | 4-->3 : EvStartStop\n 9 | state "Stopped" as 3 { 10 | } 11 | 3-->4 : EvStartStop\n 12 | } 13 | 2 : Deferrals: 14 | 2 : * EvTerminate 15 | 2-->2 : EvPlayMusic\nEvReset\n 16 | 2-->5 : EvPlayMusic\n 17 | state "MusicBoxMode" as 5 { 18 | } 19 | 5-->2 : EvReset\n 20 | @enduml 21 | -------------------------------------------------------------------------------- /src/StmListGenerator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "AbstractGenerator.h" 12 | 13 | class StmListGenerator final : public AbstractGenerator 14 | { 15 | using AbstractGenerator::AbstractGenerator; 16 | 17 | public: 18 | void generate(std::ostream &output, const ScName &stmName) override; 19 | }; 20 | -------------------------------------------------------------------------------- /cmake/CompilerFlags.cmake: -------------------------------------------------------------------------------- 1 | ## 2 | ## Boost StateChart Extractor 3 | ## 4 | ## Distributed under the Boost Software License, Version 1.0. 5 | ## (See accompanying file LICENSE or copy at 6 | ## http://www.boost.org/LICENSE_1_0.txt) 7 | ## 8 | 9 | option(WITH_WARNINGS "Build with compiler warning enabled") 10 | 11 | set(CMAKE_CXX_EXTENSIONS OFF) 12 | set(CMAKE_CXX_STANDARD 17) 13 | 14 | if(WITH_WARNINGS) 15 | add_compile_options( 16 | -pedantic 17 | -Werror 18 | -Wall 19 | -Wextra 20 | ) 21 | endif() 22 | -------------------------------------------------------------------------------- /cmake/AddressSanitizer.cmake: -------------------------------------------------------------------------------- 1 | ## 2 | ## Boost StateChart Extractor 3 | ## 4 | ## Distributed under the Boost Software License, Version 1.0. 5 | ## (See accompanying file LICENSE or copy at 6 | ## http://www.boost.org/LICENSE_1_0.txt) 7 | ## 8 | 9 | set(CMAKE_CXX_FLAGS_ASAN 10 | "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined") 11 | set(CMAKE_EXE_LINKER_FLAGS_ASAN "${CMAKE_EXE_LINKER_FLAGS}") 12 | set(CMAKE_SHARED_LINKER_FLAGS_ASAN "${CMAKE_SHARED_LINKER_FLAGS_DEBUG}") 13 | set(CMAKE_STATIC_LINKER_FLAGS_ASAN "${CMAKE_STATIC_LINKER_FLAGS_DEBUG}") 14 | -------------------------------------------------------------------------------- /src/TextGenerator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "AbstractGenerator.h" 12 | 13 | class TextGenerator final : public AbstractGenerator 14 | { 15 | using AbstractGenerator::AbstractGenerator; 16 | 17 | public: 18 | void generate(std::ostream &output, const ScName &stmName) override; 19 | 20 | private: 21 | ScName hlText(const ScName &name) const; 22 | }; 23 | -------------------------------------------------------------------------------- /cmake/ClangTidy.cmake: -------------------------------------------------------------------------------- 1 | ## 2 | ## Boost StateChart Extractor 3 | ## 4 | ## Distributed under the Boost Software License, Version 1.0. 5 | ## (See accompanying file LICENSE or copy at 6 | ## http://www.boost.org/LICENSE_1_0.txt) 7 | ## 8 | 9 | option(WITH_CLANG_TIDY "Build with clang-tidy") 10 | 11 | if(NOT WITH_CLANG_TIDY) 12 | return() 13 | endif() 14 | 15 | find_program(CLANG_TIDY_EXECUTABLE 16 | NAMES 17 | clang-tidy 18 | ) 19 | 20 | if(NOT CLANG_TIDY_EXECUTABLE) 21 | message(FATAL_ERROR "Cannot find clang-tidy!") 22 | endif() 23 | 24 | set(CMAKE_CXX_CLANG_TIDY ${CLANG_TIDY_EXECUTABLE}) 25 | -------------------------------------------------------------------------------- /test/data/04.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "Camera\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "NotShooting" as 2 { 5 | [H*] --> 3 6 | [*] --> 3 7 | state "Configuring" as 4 { 8 | } 9 | 4-->3 : EvConfig\n 10 | state "Idle" as 3 { 11 | } 12 | 3-->4 : EvConfig\n 13 | } 14 | 2-->5 : EvShutterHalf\n 15 | state "Shooting" as 5 { 16 | [*] --> 6 17 | state "Focused" as 7 { 18 | } 19 | 7-->7 : EvShutterFull\n 20 | 7-->8 : EvShutterFull\n 21 | state "Focusing" as 6 { 22 | } 23 | 6 : Deferrals: 24 | 6 : * EvShutterFull 25 | 6-->7 : EvInFocus\n 26 | state "Storing" as 8 { 27 | } 28 | } 29 | 5-->2[H*] : EvShutterRelease\n 30 | @enduml 31 | -------------------------------------------------------------------------------- /src/ObjDumpParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | class ScParser; 15 | 16 | class ObjDumpParser final 17 | { 18 | public: 19 | explicit ObjDumpParser(ScParser &scParser); 20 | 21 | public: 22 | void parse(std::istream &input, bool doStripInput); 23 | 24 | private: 25 | bool parseFunctionDecl(char *&data, std::size_t size); 26 | bool parseFunctionCall(char *&data, std::size_t size); 27 | 28 | private: 29 | ScParser &m_scParser; 30 | }; 31 | -------------------------------------------------------------------------------- /src/ScParser.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "ScModel.h" 12 | 13 | class ScParser final 14 | { 15 | public: 16 | explicit ScParser(ScModel &model); 17 | 18 | public: 19 | bool parseFunctionDecl(char *&data); 20 | bool parseFunctionCall(char *&data); 21 | 22 | private: 23 | void parseStateMachine(char *&data); 24 | void parseSimpleState(char *&data); 25 | bool parseReactMethod(char *&data); 26 | void parseReactionList(char *mplList); 27 | 28 | private: 29 | ScModel &m_model; 30 | ScName m_currentState; 31 | ScName m_currentEvent; 32 | bool m_hasCurrentState; 33 | }; 34 | -------------------------------------------------------------------------------- /src/ParserHelpers.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | 14 | template 15 | inline bool eqString(const char *data, const char (&pattern)[N]) 16 | { 17 | return !::strncmp(data, pattern, N - 1); 18 | } 19 | 20 | template 21 | inline bool expectString(char *&data, const char (&pattern)[N]) 22 | { 23 | if (eqString(data, pattern)) { 24 | data += N - 1; 25 | return true; 26 | } 27 | return false; 28 | } 29 | 30 | inline bool expectAddress(char *&data) 31 | { 32 | const char *begin = data; 33 | return ::strtoul(begin, &data, 16); 34 | } 35 | -------------------------------------------------------------------------------- /doc/make_all_img.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Generate a PNG image for state-machine found in object dump. 4 | # This script needs plantuml.jar, which can be download at http://plantuml.com/download 5 | 6 | if [ $# -lt 2 ] 7 | then 8 | echo "Usage: $0 " 9 | exit 10 | fi 11 | 12 | # Script parameters 13 | OBJ_FILE_PATH=$1 14 | OUT_FOLDER_PATH=$2 15 | 16 | # Config 17 | BOSCE=./bosce 18 | PLANTUML_JAR=plantuml.jar 19 | FSM_LIST_FILENAME=fsm_list.txt 20 | 21 | # Generate fsm list 22 | ${BOSCE} --list -O ${OBJ_FILE_PATH} | grep "^ .*" > ${OUT_FOLDER_PATH}/${FSM_LIST_FILENAME} 23 | 24 | # Foreach fsm 25 | while read s; 26 | do 27 | echo "Processing $s" 28 | ${BOSCE} --stm $s -g plantuml -O ${OBJ_FILE_PATH} > "$OUT_FOLDER_PATH/$s.txt" 29 | cat "$s.txt" | java -jar ${PLANTUML_JAR} -tpng -pipe > "$OUT_FOLDER_PATH/$s.png" 30 | done < ${OUT_FOLDER_PATH}/${FSM_LIST_FILENAME} 31 | -------------------------------------------------------------------------------- /src/StmListGenerator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "StmListGenerator.h" 10 | 11 | void StmListGenerator::generate(std::ostream &output, const ScName &stmName) 12 | { 13 | const auto &rootState = m_model.states().at(ScModel::RootScName); 14 | 15 | if (!stmName.empty()) { 16 | output << "Note: discarding a state-machine name\n"; 17 | } 18 | 19 | if (rootState.regions.empty()) { 20 | output << "No state-machines found\n"; 21 | } else { 22 | output << "Available state-machines:\n"; 23 | for (const auto &stm : rootState.regions[0].states) { 24 | output << " " << stm << "\n"; 25 | } 26 | } 27 | 28 | output.flush(); 29 | } 30 | -------------------------------------------------------------------------------- /test/src/shared/shared.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace sc = boost::statechart; 17 | 18 | struct EvToggle : sc::event 19 | { 20 | }; 21 | 22 | struct Off; 23 | 24 | struct OnOff : sc::state_machine 25 | { 26 | }; 27 | 28 | struct Off : sc::simple_state 29 | { 30 | using reactions = sc::custom_reaction; 31 | sc::result react(const EvToggle &ev); 32 | }; 33 | 34 | struct On : sc::simple_state 35 | { 36 | using reactions = sc::transition; 37 | }; 38 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | jobs: 3 | build: 4 | docker: 5 | - image: cimg/base:2021.02 6 | steps: 7 | - checkout 8 | - run: 9 | name: Install dependencies 10 | command: | 11 | sudo apt-get update 12 | sudo apt-get install \ 13 | cmake libgtest-dev clang-tidy libboost-dev libboost-filesystem-dev \ 14 | libboost-program-options-dev 15 | - run: 16 | name: Build project 17 | command: | 18 | mkdir build; cd build 19 | cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr .. 20 | cmake --build . --target demo 21 | cmake --build . -- -j4 22 | - run: 23 | name: Test project 24 | command: | 25 | cd build 26 | ctest 27 | - run: 28 | name: Install project 29 | command: | 30 | cd build 31 | sudo cmake --build . --target install 32 | - run: 33 | name: Test installed binary 34 | command: | 35 | bosce build/demo -l 36 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,clang-analyzer*,-clang-analyzer-osx*,bugprone*,-bugprone-macro-parentheses,cert-dcl03-cpp,cert-dcl21-cpp,cert-dcl54-cpp,cert-dcl58-cpp,cert-env33-c,cert-err09-cpp,cert-err34-c,cert-err52-cpp,cert-err60-cpp,cert-fio38-c,cert-flp30-c,cert-oop11-cpp,cppcoreguidelines*,-cppcoreguidelines-avoid-c-arrays,-cppcoreguidelines-avoid-magic-numbers,-cppcoreguidelines-macro-usage,-cppcoreguidelines-non-private-member-variables-in-classes,-cppcoreguidelines-owning-memory,-cppcoreguidelines-pro-bounds*,-cppcoreguidelines-pro-type-vararg,-cppcoreguidelines-special-member-functions,google*,-google-build*,-google-objc*,-google-runtime-references,hicpp*,-hicpp-avoid-c-arrays,-hicpp-no-array-decay,-hicpp-use-equals-default,-hicpp-use-equals-delete,-hicpp-vararg,-hicpp-special-member-functions,-hicpp-signed-bitwise,misc*,-misc-macro-parentheses,-misc-non-private-member-variables-in-classes,performance*,-performance-avoid-endl,readability*,-readability-implicit-bool-cast,-readability-implicit-bool-conversion,-readability-magic-numbers,-readability-named-parameter,-readability-redundant-access-specifiers,boost*,portability*' 3 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## 2 | ## Boost StateChart Extractor 3 | ## 4 | ## Distributed under the Boost Software License, Version 1.0. 5 | ## (See accompanying file LICENSE or copy at 6 | ## http://www.boost.org/LICENSE_1_0.txt) 7 | ## 8 | 9 | cmake_minimum_required(VERSION 3.10) 10 | project(bosce VERSION 1.3.1 LANGUAGES CXX) 11 | 12 | enable_testing() 13 | 14 | find_package(Boost REQUIRED COMPONENTS program_options filesystem) 15 | find_package(Threads REQUIRED) 16 | 17 | include(GNUInstallDirs) 18 | include(cmake/AddressSanitizer.cmake) 19 | include(cmake/ClangTidy.cmake) 20 | include(cmake/CompilerFlags.cmake) 21 | 22 | add_executable(bosce 23 | src/main.cpp 24 | src/ScModel.cpp 25 | src/ScParser.cpp 26 | src/ObjDumpParser.cpp 27 | src/ParserHelpers.h 28 | src/AbstractGenerator.h 29 | src/StmListGenerator.cpp 30 | src/TextGenerator.cpp 31 | src/PlantUmlGenerator.cpp 32 | src/YamlGenerator.cpp 33 | ) 34 | 35 | target_link_libraries(bosce 36 | PUBLIC 37 | Threads::Threads 38 | Boost::program_options 39 | Boost::filesystem 40 | ) 41 | 42 | install( 43 | TARGETS 44 | bosce 45 | RUNTIME DESTINATION 46 | ${CMAKE_INSTALL_BINDIR} 47 | ) 48 | 49 | add_subdirectory(test) 50 | -------------------------------------------------------------------------------- /src/AbstractGenerator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "ScModel.h" 12 | 13 | #include 14 | 15 | class AbstractGenerator 16 | { 17 | public: 18 | explicit AbstractGenerator(const ScModel &model, const ScNameSet &highlightSet = {}) 19 | : m_model(model) 20 | , m_highlightSet(highlightSet) 21 | { 22 | } 23 | virtual ~AbstractGenerator() = default; 24 | 25 | virtual void generate(std::ostream &output, const ScName &stmName) = 0; 26 | 27 | public: 28 | bool isHighlighted(const ScName &name) const 29 | { 30 | return (m_highlightSet.find(name) != m_highlightSet.cend()); 31 | } 32 | 33 | protected: 34 | const ScModel &m_model; 35 | const ScNameSet m_highlightSet; 36 | }; 37 | 38 | inline const char *toString(ScHistoryMode mode) noexcept 39 | { 40 | switch (mode) { 41 | case ScHistoryMode::None: 42 | return "none"; 43 | case ScHistoryMode::Shallow: 44 | return "shallow"; 45 | case ScHistoryMode::Deep: 46 | return "deep"; 47 | case ScHistoryMode::Full: 48 | return "full"; 49 | } 50 | return ""; 51 | }; 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/PlantUmlGenerator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include "AbstractGenerator.h" 12 | 13 | class PlantUmlGenerator final : public AbstractGenerator 14 | { 15 | using AbstractGenerator::AbstractGenerator; 16 | 17 | public: 18 | void generate(std::ostream &output, const ScName &stmName) override; 19 | 20 | private: 21 | void addWelcomeNote(std::ostream &output, const ScName &stmName); 22 | void generate(std::ostream &output, const ScName &name, int indentLevel); 23 | const ScName &alias(const ScName &name); 24 | 25 | private: 26 | void beginState(std::ostream &output, std::string &indent, const ScName &name, 27 | const ScName &nameAlias); 28 | void endState(std::ostream &output, std::string &indent); 29 | void historyPseudoState(std::ostream &output, const std::string &indent, 30 | const ScName &targetAlias, ScHistoryMode historyMode); 31 | void deferralLink(std::ostream &output, std::string &indent, const ScName &stateAlias, 32 | const ScName &eventName); 33 | void transitionLink(std::ostream &output, std::string &indent, bool isTransitionHighlighted, 34 | const ScName &stateAliasOrigin, const ScName &stateAliasTarget, 35 | const ScNameSet &eventNames); 36 | 37 | private: 38 | std::map m_alias; 39 | int m_nextAlias = 1; 40 | }; 41 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## 2 | ## Boost StateChart Extractor 3 | ## 4 | ## Distributed under the Boost Software License, Version 1.0. 5 | ## (See accompanying file LICENSE or copy at 6 | ## http://www.boost.org/LICENSE_1_0.txt) 7 | ## 8 | 9 | configure_file(test_plantuml.sh.in test_plantuml.sh @ONLY) 10 | 11 | macro(add_plantuml_test TESTNAME DATANAME STMNAME) 12 | add_test( 13 | NAME ${TESTNAME} 14 | COMMAND test_plantuml.sh ${DATANAME} ${STMNAME} 15 | ) 16 | endmacro() 17 | 18 | add_plantuml_test(StopWatch 01 StopWatch) 19 | add_plantuml_test(InitialStateBeforeStm 02 StopWatch) 20 | add_plantuml_test(CustomTransitionToTemplateState 03 SM) 21 | add_plantuml_test(HistoryPseudoState 04 Camera) 22 | add_plantuml_test(OrthogonalHistory 05 finite_state_machine::SmMachine) 23 | add_plantuml_test(NDebug 06 Plinio::OtherStateMachine) 24 | 25 | add_custom_target(demo 26 | COMMAND 27 | ${CMAKE_CXX_COMPILER} -g ${CMAKE_CURRENT_LIST_DIR}/src/demo/main.cpp -o demo 28 | WORKING_DIRECTORY 29 | ${CMAKE_BINARY_DIR} 30 | SOURCES 31 | src/demo/main.cpp 32 | COMMENT 33 | "Building a demo statechert binary" 34 | ) 35 | 36 | add_custom_target(shared 37 | COMMAND 38 | ${CMAKE_CXX_COMPILER} -g ${CMAKE_CURRENT_LIST_DIR}/src/shared/shared.cpp -fPIC -shared 39 | -o libshared.so 40 | COMMAND 41 | ${CMAKE_CXX_COMPILER} -g ${CMAKE_CURRENT_LIST_DIR}/src/shared/main.cpp -L${CMAKE_BINARY_DIR} -lshared -o shared 42 | WORKING_DIRECTORY 43 | ${CMAKE_BINARY_DIR} 44 | SOURCES 45 | src/shared/shared.cpp 46 | src/shared/shared.h 47 | src/shared/main.cpp 48 | COMMENT 49 | "Building a shared statechert binary" 50 | ) 51 | -------------------------------------------------------------------------------- /src/TextGenerator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "TextGenerator.h" 10 | 11 | ScName TextGenerator::hlText(const ScName &name) const 12 | { 13 | if (isHighlighted(name)) { 14 | return "*" + name + "*"; 15 | } 16 | return name; 17 | } 18 | 19 | void TextGenerator::generate(std::ostream &output, const ScName &stmName) 20 | { 21 | const auto &states = m_model.states(); 22 | 23 | if (!stmName.empty()) { 24 | output << "Note: discarding a state-machine name\n"; 25 | } 26 | 27 | for (const auto &[name, state] : states) { 28 | output << "Name: " << hlText(name) << "\n"; 29 | output << "Parent: " << hlText(state.parent) << "\n"; 30 | for (ScRegionNum i = 0; i < state.regions.size(); i++) { 31 | output << "Substates (" << i << ") [" << state.regions[i].initial << "]:\n"; 32 | for (const auto &substate : state.regions[i].states) { 33 | output << " " << hlText(substate) << "\n"; 34 | } 35 | } 36 | if (!state.transitions.empty()) { 37 | output << "Transitions:\n"; 38 | for (const auto &[target, events] : state.transitions) { 39 | output << "==> " << hlText(target.name) 40 | << (target.historyMode != ScHistoryMode::None ? " (history)\n" : "\n"); 41 | for (const auto &event : events) { 42 | output << " ** " << hlText(event) << "\n"; 43 | } 44 | } 45 | } 46 | if (!state.deferrals.empty()) { 47 | output << "Deferrals:\n"; 48 | for (const auto &event : state.deferrals) { 49 | output << " " << hlText(event) << "\n"; 50 | } 51 | } 52 | output << "\n"; 53 | } 54 | 55 | output.flush(); 56 | } 57 | -------------------------------------------------------------------------------- /test/src/demo/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | * 8 | * This is a simple statechart implementation based on official boost::statechart 9 | * tutorial https://www.boost.org/doc/libs/release/libs/statechart/doc/tutorial.html 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace sc = boost::statechart; 20 | 21 | bool g_flag = true; 22 | 23 | struct EvStartStop : sc::event 24 | { 25 | }; 26 | struct EvReset : sc::event 27 | { 28 | }; 29 | struct EvPlayMusic : sc::event 30 | { 31 | }; 32 | struct EvTerminate : sc::event 33 | { 34 | }; 35 | 36 | struct Active; 37 | struct Stopped; 38 | 39 | struct StopWatch : sc::state_machine 40 | { 41 | }; 42 | 43 | struct Active : sc::simple_state 44 | { 45 | using reactions = boost::mpl::list, 46 | sc::custom_reaction, sc::deferral>; 47 | 48 | sc::result react(const EvPlayMusic &ev); 49 | }; 50 | 51 | struct Running : sc::simple_state 52 | { 53 | using reactions = sc::transition; 54 | }; 55 | 56 | struct Stopped : sc::simple_state 57 | { 58 | using reactions = sc::transition; 59 | }; 60 | 61 | struct MusicBoxMode : sc::simple_state 62 | { 63 | using reactions = sc::transition; 64 | }; 65 | 66 | sc::result Active::react(const EvPlayMusic &) 67 | { 68 | if (g_flag) { 69 | return transit(); 70 | } 71 | return transit(); 72 | } 73 | 74 | int main() 75 | { 76 | StopWatch myWatch; 77 | myWatch.initiate(); 78 | return 0; 79 | } 80 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | # disable formatting with // clang-format off 2 | # enable formatting with // clang-format on 3 | 4 | Language: Cpp 5 | Standard: Cpp11 6 | 7 | # --- Indentation 8 | 9 | TabWidth: 4 10 | 11 | UseTab: Never 12 | 13 | IndentCaseLabels: false 14 | 15 | IndentWidth: 4 16 | 17 | IndentWrappedFunctionNames: false 18 | 19 | ConstructorInitializerIndentWidth: 4 20 | 21 | ContinuationIndentWidth: 4 22 | 23 | AccessModifierOffset: -4 # how far e.g public is indented 24 | 25 | NamespaceIndentation: None 26 | 27 | # --- Braces 28 | 29 | BreakBeforeBraces: Custom 30 | 31 | BraceWrapping: { 32 | AfterClass: 'true' 33 | AfterControlStatement: 'false' 34 | AfterEnum : 'true' 35 | AfterFunction : 'true' 36 | AfterNamespace : 'false' 37 | AfterStruct : 'true' 38 | AfterUnion : 'true' 39 | BeforeCatch : 'false' 40 | BeforeElse : 'false' 41 | IndentBraces : 'false' 42 | } 43 | 44 | AllowShortBlocksOnASingleLine: false # if (1) {return;} 45 | 46 | AllowShortCaseLabelsOnASingleLine: false # case: break; 47 | 48 | AllowShortFunctionsOnASingleLine: false # None, Empty, Inline 49 | 50 | AllowShortIfStatementsOnASingleLine: false 51 | 52 | AllowShortLoopsOnASingleLine: false 53 | 54 | Cpp11BracedListStyle: true 55 | 56 | # --- Whitespace 57 | 58 | SpaceBeforeAssignmentOperators: true 59 | 60 | SpaceBeforeParens: ControlStatements 61 | 62 | SpaceInEmptyParentheses: false 63 | 64 | SpacesBeforeTrailingComments: 1 65 | 66 | SpacesInAngles: false 67 | 68 | SpacesInCStyleCastParentheses: false 69 | 70 | SpacesInParentheses: false 71 | 72 | SpacesInSquareBrackets: false 73 | 74 | MaxEmptyLinesToKeep: 3 75 | 76 | # --- Breaks, alignment & reflow 77 | 78 | PenaltyBreakBeforeFirstCallParameter: 200 79 | 80 | AlignAfterOpenBracket: Align 81 | 82 | AlignConsecutiveAssignments: false 83 | 84 | AlignConsecutiveDeclarations: false 85 | 86 | AlignEscapedNewlinesLeft: false 87 | 88 | AlignOperands: false 89 | 90 | AlignTrailingComments: true 91 | 92 | AllowAllParametersOfDeclarationOnNextLine: false 93 | 94 | AlwaysBreakAfterReturnType: None 95 | 96 | AlwaysBreakBeforeMultilineStrings: true 97 | 98 | AlwaysBreakTemplateDeclarations: true 99 | 100 | BinPackArguments: true 101 | 102 | BinPackParameters: true 103 | 104 | BreakBeforeBinaryOperators: NonAssignment 105 | 106 | BreakBeforeTernaryOperators: false 107 | 108 | BreakConstructorInitializersBeforeComma: true 109 | 110 | ColumnLimit: 100 111 | 112 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 113 | 114 | PointerAlignment: Right 115 | 116 | ReflowComments: true 117 | 118 | # --- Other 119 | 120 | SortIncludes: true 121 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] 122 | 123 | -------------------------------------------------------------------------------- /src/ScModel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "ScModel.h" 10 | 11 | const ScName ScModel::RootScName = "[root]"; 12 | 13 | void ScModel::addStateMachine(const ScName &name, const ScName &initialState) 14 | { 15 | addState(name, RootScName, 0, {initialState}, ScHistoryMode::None); 16 | } 17 | 18 | void ScModel::addState(const ScName &name, const ScName &parent, ScRegionNum regionNum, 19 | const ScTargetList &initialSubstates, ScHistoryMode historyMode) 20 | { 21 | auto &state = m_states[name]; 22 | m_activeState = &state; 23 | 24 | if (!state.isDefined()) { 25 | state.parent = parent; 26 | state.historyState.mode = historyMode; 27 | 28 | const auto nrOrthRegions = initialSubstates.size(); 29 | state.regions.resize(nrOrthRegions); 30 | 31 | for (std::size_t i = 0; i < nrOrthRegions; i++) { 32 | const ScName &substate = initialSubstates[i].name; 33 | ScRegion &substateSet = state.regions[i]; 34 | 35 | substateSet.initial = substate; 36 | substateSet.states.insert(substate); 37 | if (initialSubstates[i].historyMode != ScHistoryMode::None) { 38 | state.historyState.initial = substate; 39 | } 40 | } 41 | 42 | addSubstate(parent, name, regionNum); 43 | } 44 | } 45 | 46 | void ScModel::addTransition(const ScName &target, const ScName &event, ScHistoryMode historyMode) 47 | { 48 | if (historyMode == ScHistoryMode::None) { 49 | m_activeState->transitions[ScTarget(target)].insert(event); 50 | } else { 51 | auto &targetState = m_states[target]; 52 | if (targetState.isDefined()) { 53 | auto &parent = targetState.parent; 54 | m_activeState->transitions[ScTarget(parent, historyMode)].insert(event); 55 | addHistory(parent, target, historyMode); 56 | } 57 | } 58 | } 59 | 60 | void ScModel::addDeferral(const ScName &event) 61 | { 62 | m_activeState->deferrals.insert(event); 63 | } 64 | 65 | void ScModel::addSubstate(const ScName &name, const ScName &substate, ScRegionNum regionNum) 66 | { 67 | auto &substates = m_states[name].regions; 68 | if (substates.size() <= regionNum) { 69 | substates.resize(regionNum + 1); 70 | } 71 | substates[regionNum].states.insert(substate); 72 | } 73 | 74 | void ScModel::addHistory(const ScName &name, const ScName &initial, ScHistoryMode mode) noexcept 75 | { 76 | auto &historyState = m_states[name].historyState; 77 | historyState.initial = initial; 78 | historyState.mode = mode; 79 | } 80 | 81 | void ScModel::setActiveState(const ScName &name) 82 | { 83 | m_activeState = &m_states[name]; 84 | } 85 | -------------------------------------------------------------------------------- /src/YamlGenerator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "YamlGenerator.h" 10 | 11 | static void print(std::ostream &output, const ScHistoryState &historyState) noexcept 12 | { 13 | if (historyState.mode != ScHistoryMode::None) { 14 | output << " history:\n"; 15 | output << " mode: " << toString(historyState.mode) << "\n"; 16 | output << " initial: " << historyState.initial << "\n"; 17 | } 18 | } 19 | 20 | static void print(std::ostream &output, const ScRegionList ®ions) noexcept 21 | { 22 | if (!regions.empty()) { 23 | std::string indent; 24 | if (regions.size() > 1) { 25 | output << " regions:\n"; 26 | indent = " "; 27 | } 28 | for (const auto ®ion : regions) { 29 | output << indent << " initial: " << region.initial << "\n"; 30 | output << indent << " substates:\n"; 31 | for (const auto &substate : region.states) { 32 | output << indent << " - " << substate << "\n"; 33 | } 34 | } 35 | } 36 | } 37 | 38 | static void print(std::ostream &output, const ScTransitionMap &transitions) noexcept 39 | { 40 | if (!transitions.empty()) { 41 | output << " transitions:\n"; 42 | for (const auto &[target, events] : transitions) { 43 | output << " - target: " << target.name << "\n"; 44 | if (target.historyMode != ScHistoryMode::None) { 45 | output << " history: " << toString(target.historyMode) << "\n"; 46 | } 47 | output << " events:\n"; 48 | for (const auto &event : events) { 49 | output << " - " << event << "\n"; 50 | } 51 | } 52 | } 53 | } 54 | 55 | static void print(std::ostream &output, const ScNameSet &deferrals) noexcept 56 | { 57 | if (!deferrals.empty()) { 58 | output << " deferrals:\n"; 59 | for (const auto &event : deferrals) { 60 | output << " - " << event << "\n"; 61 | } 62 | } 63 | } 64 | 65 | void YamlGenerator::generate(std::ostream &output, const ScName &stmName) 66 | { 67 | const auto &states = m_model.states(); 68 | 69 | if (!stmName.empty()) { 70 | output << "Note: discarding a state-machine name\n"; 71 | } 72 | 73 | for (const auto &[name, state] : states) { 74 | output << name << ":\n"; 75 | if (state.isDefined()) { 76 | output << " parent: " << state.parent << "\n"; 77 | } 78 | 79 | print(output, state.historyState); 80 | print(output, state.regions); 81 | print(output, state.transitions); 82 | print(output, state.deferrals); 83 | 84 | output << "\n"; 85 | } 86 | 87 | output.flush(); 88 | } 89 | -------------------------------------------------------------------------------- /src/ObjDumpParser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "ObjDumpParser.h" 10 | #include "ParserHelpers.h" 11 | #include "ScParser.h" 12 | 13 | #include 14 | 15 | ObjDumpParser::ObjDumpParser(ScParser &scParser) 16 | : m_scParser(scParser) 17 | { 18 | } 19 | 20 | void ObjDumpParser::parse(std::istream &input, bool doStripInput) 21 | { 22 | std::string originalBuf; 23 | std::string buf; 24 | 25 | while (input) { 26 | std::getline(input, buf); 27 | 28 | if (doStripInput) { 29 | // Copy is needed because parsing modifies the original string for 30 | // performance reasons. 31 | originalBuf = buf; 32 | } 33 | 34 | char *data = buf.data(); 35 | bool isHandled = false; 36 | if (*data == ' ') { 37 | isHandled = parseFunctionCall(data, buf.size()); 38 | } else { 39 | isHandled = parseFunctionDecl(data, buf.size()); 40 | } 41 | 42 | if (doStripInput && isHandled) { 43 | boost::replace_all(originalBuf, "mpl_::na, ", ""); 44 | std::cout << originalBuf << "\n"; 45 | } 46 | } 47 | 48 | if (!input.eof()) { 49 | throw std::runtime_error("Input error"); 50 | } 51 | } 52 | 53 | // 0000000005c5c5d2 (void*)>: 54 | bool ObjDumpParser::parseFunctionDecl(char *&data, std::size_t size) 55 | { 56 | char *const endOfData = data + size - 2; 57 | 58 | if (!(expectAddress(data) && expectString(data, " <"))) { 59 | return false; 60 | } 61 | 62 | if (!eqString(endOfData, ">:")) { 63 | return false; 64 | } 65 | *endOfData = 0; 66 | 67 | return m_scParser.parseFunctionDecl(data); 68 | } 69 | 70 | inline bool expectAsmBytes(char *&data) 71 | { 72 | for (int i = 0; i < 7; i++) { 73 | if (!(isxdigit(data[0]) && isxdigit(data[1]) && isspace(data[2]))) { 74 | if (i == 0) { 75 | return false; 76 | } 77 | break; 78 | } 79 | data += 3; 80 | } 81 | 82 | while (isspace(*data)) { 83 | ++data; 84 | } 85 | return true; 86 | } 87 | 88 | // 5c5c5e5: e8 34 21 00 00 callq 5c5e71e (void*)> 89 | // 5c5c5e5: e8 34 21 00 00 call 5c5e71e (void*)> 90 | bool ObjDumpParser::parseFunctionCall(char *&data, std::size_t size) 91 | { 92 | const char *const endOfData = data + size - 1; 93 | 94 | if (!(expectAddress(data) && expectString(data, ":\t") && expectAsmBytes(data) 95 | && (expectString(data, "callq ") || expectString(data, "call ")) && expectAddress(data) 96 | && expectString(data, " <"))) { 97 | return false; 98 | } 99 | 100 | if (!eqString(endOfData, ">")) { 101 | return false; 102 | } 103 | 104 | return m_scParser.parseFunctionCall(data); 105 | } 106 | -------------------------------------------------------------------------------- /src/ScModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | // Name of state-machine, state or event. 17 | using ScName = std::string; 18 | 19 | // Set of names. 20 | using ScNameSet = std::set; 21 | using ScNameList = std::vector; 22 | 23 | // Orthogonal region. 24 | struct ScRegion final 25 | { 26 | // Initial state. 27 | ScName initial; 28 | 29 | // All substates. 30 | ScNameSet states; 31 | }; 32 | 33 | // List of orthogonal regions. 34 | using ScRegionList = std::vector; 35 | 36 | // Number of an orthogonal region. 37 | using ScRegionNum = ScRegionList::size_type; 38 | 39 | // History mode. See boost statechart's history_mode. 40 | enum class ScHistoryMode 41 | { 42 | None, 43 | Shallow, 44 | Deep, 45 | Full 46 | }; 47 | 48 | // History pseudo-state details. 49 | struct ScHistoryState final 50 | { 51 | ScHistoryMode mode; 52 | 53 | // Default (initial) target state. 54 | ScName initial; 55 | }; 56 | 57 | // Transition target. 58 | struct ScTarget final 59 | { 60 | // Target state name. 61 | ScName name; 62 | 63 | // Is target a history pseudo-state? 64 | ScHistoryMode historyMode; 65 | 66 | ScTarget(ScName name, ScHistoryMode historyMode) noexcept 67 | : name(std::move(name)) 68 | , historyMode(historyMode) 69 | { 70 | } 71 | 72 | ScTarget(ScName name) noexcept 73 | : name(std::move(name)) 74 | , historyMode(ScHistoryMode::None) 75 | { 76 | } 77 | 78 | bool operator<(const ScTarget &other) const noexcept 79 | { 80 | return std::tie(name, historyMode) < std::tie(other.name, other.historyMode); 81 | } 82 | }; 83 | 84 | // List of targets. 85 | using ScTargetList = std::vector; 86 | 87 | // Map of transitions (target state -> what events lead there). 88 | using ScTransitionMap = std::map; 89 | 90 | // State or state-machine. 91 | struct ScState final 92 | { 93 | ScName parent; // If empty, it is a state-machine. 94 | ScHistoryState historyState; // History pseudo-state. 95 | ScRegionList regions; 96 | ScTransitionMap transitions; 97 | ScNameSet deferrals; 98 | 99 | bool isDefined() const 100 | { 101 | return !parent.empty(); 102 | } 103 | }; 104 | 105 | // Map state name to state details. 106 | using ScStateMap = std::map; 107 | 108 | class ScModel final 109 | { 110 | public: 111 | static const ScName RootScName; 112 | 113 | public: 114 | void addStateMachine(const ScName &name, const ScName &initialState); 115 | void addState(const ScName &name, const ScName &parent, ScRegionNum regionNum, 116 | const ScTargetList &initialSubstates, ScHistoryMode historyMode); 117 | void addTransition(const ScName &target, const ScName &event, 118 | ScHistoryMode historyMode = ScHistoryMode::None); 119 | void addDeferral(const ScName &event); 120 | void setActiveState(const ScName &name); 121 | 122 | public: 123 | const ScStateMap &states() const 124 | { 125 | return m_states; 126 | } 127 | 128 | private: 129 | void addSubstate(const ScName &name, const ScName &substate, ScRegionNum regionNum); 130 | void addHistory(const ScName &name, const ScName &initial, ScHistoryMode mode) noexcept; 131 | 132 | private: 133 | ScStateMap m_states; 134 | ScState *m_activeState = nullptr; 135 | }; 136 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bosce 2 | Bosce, or **bo**ost::**s**tate**c**hart **e**xtractor, is a command line tool to extract 3 | information about 4 | boost::statecharts from an arbitrary binary with debug symbols and transform it to a 5 | user-friendly form, e.g. UML diagrams. 6 | 7 | It relies on objdump to 8 | extract information from binaries and is primarily intended to be used in a Linux environment. 9 | 10 | [![CircleCI](https://circleci.com/gh/kanje/bosce.svg?style=svg)](https://circleci.com/gh/kanje/bosce) 11 | 12 | ## Demo 13 | First, we need a binary with debug symbols to work on. 14 | 15 | $ g++ -g demo.cpp -o demo 16 | 17 | Then, we get a list of state machine in this binary. 18 | 19 | $ bosce demo -l 20 | Available state-machines: 21 | StopWatch 22 | 23 | Now, we generate a `StopWatch` state-machine representation in a 24 | PlantUML format. 25 | 26 | $ bosce demo -s StopWatch > uml.pu 27 | 28 | Finally, we use PlantUML to generate a PNG file and open it. 29 | 30 | $ plantuml uml.pu 31 | $ xdg-open uml.png 32 | 33 | ## Usage 34 | 35 | bosce [options] 36 | 37 | ### Input Files 38 | 39 | Bosce accepts one or more input files: 40 | - Binaries with debug symbols (by default), or 41 | - Text files with `objdump` output (if option `-O` is used). 42 | 43 | All information extracted from input files is merged into a single model. This is 44 | useful, e.g. if a state machine is split between a shared library and a binary using 45 | this library. 46 | 47 | ### Options 48 | 49 | - `-h`, `--help`: Show a help message 50 | - `-O`, `--objdump`: Input files is an objdump instead of binary 51 | - `-X`, `--extract`: Only extract an `objdump out of an input binary 52 | - `-S`, `--strip`: Strip an objdump to contain only relevant data 53 | - `-l`, `--list`: List all state-machine names 54 | - `-t `, `--highlight `: Highlight given names in the generated output 55 | - `-s `, `--stm `: Generate an output for a specific state-machine 56 | - `-g `, `--generator `: Specify a generator (plantuml, stmlist, text, yaml) 57 | 58 | ## Build Instructions 59 | Bosce depends on `boost::program_options` and `boost::process` and requires a C++17 compatible compiler. 60 | 61 | First, clone the repository. 62 | 63 | $ git clone https://github.com/kanje/bosce.git 64 | Cloning into 'bosce'... 65 | remote: Enumerating objects: 48, done. 66 | remote: Total 48 (delta 0), reused 0 (delta 0), pack-reused 48 67 | Unpacking objects: 100% (48/48), done. 68 | 69 | Then, create a build directory and run `cmake` to configure the project. 70 | 71 | $ mkdir bosce-build 72 | $ cd bosce-build 73 | $ cmake -DCMAKE_BUILD_TYPE=Release ../bosce 74 | -- The CXX compiler identification is GNU 9.2.1 75 | ... 76 | -- Configuring done 77 | -- Generating done 78 | -- Build files have been written to: /.../bosce-build 79 | 80 | Finally, build bosce. 81 | 82 | $ make -j10 83 | Scanning dependencies of target bosce 84 | [ 12%] Building CXX object CMakeFiles/bosce.dir/src/ScModel.cpp.o 85 | ... 86 | [100%] Built target bosce 87 | 88 | Optionally, build a demo statechart binary to experiment on. 89 | 90 | $ make demo 91 | Scanning dependencies of target demo 92 | [100%] Building a demo statechert binary 93 | [100%] Built target demo 94 | 95 | ## Reporting Issues 96 | If you have encountered a problem and want to report it, please do not forget to provide an 97 | objdump which shows the problem. If the objdump is too big or contains proprietary code, it 98 | can be stripped. 99 | 100 | $ bosce -SO original.objdump > stripped.objdump 101 | $ bosce -SX binary > stripped.objdump 102 | 103 | The stripped objdump contains only information related to `boost::statechart`s. Note, this 104 | includes state-machine, state and event names as well as assembly for `react()` methods. 105 | -------------------------------------------------------------------------------- /test/data/05.out: -------------------------------------------------------------------------------- 1 | @startuml 2 | note "finite_state_machine::SmMachine\nGenerated on 00.00.0000" as NoteGenerated #A2F2A2 3 | [*] --> 2 4 | state "StActive" as 2 { 5 | [*] --> 3 6 | state "StBreaking<(ControlGroup)0, true>" as 4 { 7 | } 8 | 4 : Deferrals: 9 | 4 : * EvPositionControlPressed<(ControlGroup)0> 10 | 4 : * EvThrustControlPressed<(ControlGroup)0> 11 | 4 : * EvVelocityControlPressed<(ControlGroup)0> 12 | 4-->3 : EvNegligibleVelocity<(ControlGroup)0>\nEvPositionCommandReceived<(ControlGroup)0>\n 13 | state "StJoystickControl<(ControlGroup)0, true>" as 5 { 14 | [H*] --> 6 15 | [*] --> 6 16 | state "StJoystickPositionControl<(ControlGroup)0, true>" as 7 { 17 | } 18 | 7-->6 : EvThrustControlPressed<(ControlGroup)0>\n 19 | 7-->8 : EvVelocityControlPressed<(ControlGroup)0>\n 20 | state "StJoystickThrustControl<(ControlGroup)0, true>" as 6 { 21 | } 22 | 6-->7 : EvPositionControlPressed<(ControlGroup)0>\n 23 | 6-->8 : EvVelocityControlPressed<(ControlGroup)0>\n 24 | state "StJoystickVelocityControl<(ControlGroup)0, true>" as 8 { 25 | } 26 | 8-->7 : EvPositionControlPressed<(ControlGroup)0>\n 27 | 8-->6 : EvThrustControlPressed<(ControlGroup)0>\n 28 | } 29 | 5-->4 : EvJoystickCommandTimeout<(ControlGroup)0>\n 30 | state "StPositionTracking<(ControlGroup)0, true>" as 3 { 31 | } 32 | 3 : Deferrals: 33 | 3 : * EvPositionControlPressed<(ControlGroup)0> 34 | 3 : * EvThrustControlPressed<(ControlGroup)0> 35 | 3 : * EvVelocityControlPressed<(ControlGroup)0> 36 | 3-->5 : EvJoystickCommandReceived<(ControlGroup)0>\n 37 | 3-->9 : EvVelocityCommandReceived<(ControlGroup)0>\n 38 | state "StVelocityTracking<(ControlGroup)0, true>" as 9 { 39 | } 40 | 9 : Deferrals: 41 | 9 : * EvPositionControlPressed<(ControlGroup)0> 42 | 9 : * EvThrustControlPressed<(ControlGroup)0> 43 | 9 : * EvVelocityControlPressed<(ControlGroup)0> 44 | 9-->5 : EvJoystickCommandReceived<(ControlGroup)0>\n 45 | 9-->3 : EvVelocityCommandTimeout<(ControlGroup)0>\n 46 | -- 47 | [*] --> 10 48 | state "StBreaking<(ControlGroup)1, true>" as 11 { 49 | } 50 | 11 : Deferrals: 51 | 11 : * EvPositionControlPressed<(ControlGroup)1> 52 | 11 : * EvThrustControlPressed<(ControlGroup)1> 53 | 11 : * EvVelocityControlPressed<(ControlGroup)1> 54 | 11-->10 : EvNegligibleVelocity<(ControlGroup)1>\nEvPositionCommandReceived<(ControlGroup)1>\n 55 | state "StJoystickControl<(ControlGroup)1, true>" as 12 { 56 | [H*] --> 13 57 | [*] --> 13 58 | state "StJoystickPositionControl<(ControlGroup)1, true>" as 14 { 59 | } 60 | 14-->13 : EvThrustControlPressed<(ControlGroup)1>\n 61 | 14-->15 : EvVelocityControlPressed<(ControlGroup)1>\n 62 | state "StJoystickThrustControl<(ControlGroup)1, true>" as 13 { 63 | } 64 | 13-->14 : EvPositionControlPressed<(ControlGroup)1>\n 65 | 13-->15 : EvVelocityControlPressed<(ControlGroup)1>\n 66 | state "StJoystickVelocityControl<(ControlGroup)1, true>" as 15 { 67 | } 68 | 15-->14 : EvPositionControlPressed<(ControlGroup)1>\n 69 | 15-->13 : EvThrustControlPressed<(ControlGroup)1>\n 70 | } 71 | 12-->11 : EvJoystickCommandTimeout<(ControlGroup)1>\n 72 | state "StPositionTracking<(ControlGroup)1, true>" as 10 { 73 | } 74 | 10 : Deferrals: 75 | 10 : * EvPositionControlPressed<(ControlGroup)1> 76 | 10 : * EvThrustControlPressed<(ControlGroup)1> 77 | 10 : * EvVelocityControlPressed<(ControlGroup)1> 78 | 10-->12 : EvJoystickCommandReceived<(ControlGroup)1>\n 79 | 10-->16 : EvVelocityCommandReceived<(ControlGroup)1>\n 80 | state "StVelocityTracking<(ControlGroup)1, true>" as 16 { 81 | } 82 | 16 : Deferrals: 83 | 16 : * EvPositionControlPressed<(ControlGroup)1> 84 | 16 : * EvThrustControlPressed<(ControlGroup)1> 85 | 16 : * EvVelocityControlPressed<(ControlGroup)1> 86 | 16-->12 : EvJoystickCommandReceived<(ControlGroup)1>\n 87 | 16-->10 : EvVelocityCommandTimeout<(ControlGroup)1>\n 88 | -- 89 | [*] --> 17 90 | state "StJoystickControl<(ControlGroup)2, false>" as 18 { 91 | [H*] --> 19 92 | [*] --> 19 93 | state "StJoystickPositionControl<(ControlGroup)2, false>" as 20 { 94 | } 95 | 20-->19 : EvThrustControlPressed<(ControlGroup)2>\n 96 | state "StJoystickThrustControl<(ControlGroup)2, false>" as 19 { 97 | } 98 | 19-->20 : EvPositionControlPressed<(ControlGroup)2>\n 99 | } 100 | 18-->17 : EvJoystickCommandTimeout<(ControlGroup)2>\n 101 | state "StPositionTracking<(ControlGroup)2, false>" as 17 { 102 | } 103 | 17 : Deferrals: 104 | 17 : * EvPositionControlPressed<(ControlGroup)2> 105 | 17 : * EvThrustControlPressed<(ControlGroup)2> 106 | 17-->18 : EvJoystickCommandReceived<(ControlGroup)2>\n 107 | } 108 | @enduml 109 | -------------------------------------------------------------------------------- /src/PlantUmlGenerator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "PlantUmlGenerator.h" 10 | 11 | #include 12 | 13 | static ScName removeNamespaces(const ScName &name) 14 | { 15 | ScName res; 16 | res.reserve(name.size()); 17 | 18 | auto isNameChar = [](char c) { return isalnum(c) || c == '_'; }; 19 | 20 | const char *start = name.data(); 21 | for (const char *p = start; *p; ++p) { 22 | if (ispunct(*p) && *p != '_') { 23 | if (*p != ':') { 24 | res.append(start, p + 1); 25 | } 26 | start = p + 1; 27 | } 28 | } 29 | res.append(start); 30 | 31 | return res; 32 | } 33 | 34 | static const char *puHistoryState(ScHistoryMode historyMode) noexcept 35 | { 36 | switch (historyMode) { 37 | case ScHistoryMode::Deep: 38 | case ScHistoryMode::Full: 39 | return "[H*]"; 40 | break; 41 | case ScHistoryMode::Shallow: 42 | return "[H]"; 43 | break; 44 | default: 45 | break; 46 | } 47 | return ""; 48 | } 49 | 50 | void PlantUmlGenerator::generate(std::ostream &output, const ScName &stmName) 51 | { 52 | output << "@startuml\n"; 53 | addWelcomeNote(output, stmName); 54 | generate(output, stmName, 0); 55 | output << "@enduml\n"; 56 | output.flush(); 57 | } 58 | 59 | void PlantUmlGenerator::addWelcomeNote(std::ostream &output, const ScName &stmName) 60 | { 61 | const auto time = std::time(nullptr); 62 | output << "note \"" << stmName << "\\nGenerated on " 63 | << std::put_time(std::localtime(&time), "%d.%m.%Y") << "\" as NoteGenerated #A2F2A2\n"; 64 | } 65 | 66 | void PlantUmlGenerator::generate(std::ostream &output, const ScName &name, int indentLevel) 67 | { 68 | const auto &state = m_model.states().at(name); 69 | const auto &nameAlias = alias(name); 70 | 71 | std::string indent(indentLevel * 2, ' '); 72 | 73 | // Exclude the outerbox with a state machine name: 74 | if (indentLevel > 0) { 75 | beginState(output, indent, name, nameAlias); 76 | } 77 | 78 | // History pseudo-state: 79 | if (auto historyMode = state.historyState.mode; historyMode != ScHistoryMode::None) { 80 | historyPseudoState(output, indent, alias(state.historyState.initial), historyMode); 81 | } 82 | 83 | // Substates: 84 | const auto totalRegions = state.regions.size(); 85 | for (ScRegionNum regionNum = 0; regionNum < totalRegions; regionNum++) { 86 | if (regionNum > 0) { 87 | output << indent << "--\n"; 88 | } 89 | 90 | const auto &substates = state.regions[regionNum]; 91 | output << indent << "[*] --> " << alias(substates.initial) << "\n"; 92 | for (const auto &substateName : substates.states) { 93 | generate(output, substateName, indentLevel + 1); 94 | } 95 | } 96 | 97 | if (indentLevel > 0) { 98 | endState(output, indent); 99 | } 100 | 101 | // Deferrals: 102 | if (!state.deferrals.empty()) { 103 | output << indent << nameAlias << " : Deferrals:\n"; 104 | for (const auto &eventName : state.deferrals) { 105 | deferralLink(output, indent, nameAlias, eventName); 106 | } 107 | } 108 | 109 | // Transitions: 110 | for (const auto &[target, eventNames] : state.transitions) { 111 | bool isTransitionHighlighted = false; 112 | for (const auto &eventName : eventNames) { 113 | if (!eventName.empty() && isHighlighted(eventName)) { 114 | isTransitionHighlighted = true; 115 | } 116 | } 117 | 118 | auto targetAlias = alias(target.name); 119 | 120 | // History states in PlantUML can be references as StateName[H] or StateName[H*]: 121 | if (target.historyMode != ScHistoryMode::None) { 122 | targetAlias.append(puHistoryState(target.historyMode)); 123 | } 124 | 125 | transitionLink(output, indent, isTransitionHighlighted, nameAlias, targetAlias, eventNames); 126 | } 127 | } 128 | 129 | const ScName &PlantUmlGenerator::alias(const ScName &name) 130 | { 131 | if (m_alias.find(name) == m_alias.cend()) { 132 | m_alias.insert({name, std::to_string(m_nextAlias++)}); 133 | } 134 | return m_alias[name]; 135 | } 136 | 137 | void PlantUmlGenerator::beginState(std::ostream &output, std::string &indent, const ScName &name, 138 | const ScName &nameAlias) 139 | { 140 | const ScName nameShort = removeNamespaces(name); 141 | 142 | if (isHighlighted(name)) { 143 | output << indent << "state \"" << nameShort << "\" as " << nameAlias << " #IndianRed {\n"; 144 | } else { 145 | output << indent << "state \"" << nameShort << "\" as " << nameAlias << " {\n"; 146 | } 147 | 148 | indent.append(" "); 149 | } 150 | 151 | void PlantUmlGenerator::endState(std::ostream &output, std::string &indent) 152 | { 153 | indent.erase(indent.size() - 2); 154 | output << indent << "}\n"; 155 | } 156 | 157 | void PlantUmlGenerator::historyPseudoState(std::ostream &output, const std::string &indent, 158 | const ScName &targetAlias, ScHistoryMode historyMode) 159 | { 160 | output << indent << puHistoryState(historyMode) << " --> " << targetAlias << "\n"; 161 | } 162 | 163 | void PlantUmlGenerator::deferralLink(std::ostream &output, std::string &indent, 164 | const ScName &stateAlias, const ScName &eventName) 165 | { 166 | const ScName eventNameShort = removeNamespaces(eventName); 167 | 168 | if (isHighlighted(eventName)) { 169 | output << indent << stateAlias << " : * " << eventNameShort << "\n"; 170 | } else { 171 | output << indent << stateAlias << " : * " << eventNameShort << "\n"; 172 | } 173 | } 174 | 175 | void PlantUmlGenerator::transitionLink(std::ostream &output, std::string &indent, 176 | bool isTransitionHighlighted, const ScName &stateAliasOrigin, 177 | const ScName &stateAliasTarget, const ScNameSet &eventNames) 178 | { 179 | 180 | if (isTransitionHighlighted) { 181 | output << indent << stateAliasOrigin << "-[#red,bold]-->" << stateAliasTarget << " : "; 182 | } else { 183 | output << indent << stateAliasOrigin << "-->" << stateAliasTarget << " : "; 184 | } 185 | 186 | for (auto eventName : eventNames) { 187 | if (eventName.empty()) { 188 | eventName = ""; 189 | } 190 | 191 | if (isHighlighted(eventName)) { 192 | output << "" << removeNamespaces(eventName) << "\\n"; 193 | } else { 194 | output << removeNamespaces(eventName) << "\\n"; 195 | } 196 | } 197 | 198 | output << "\n"; 199 | } 200 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "ObjDumpParser.h" 10 | #include "ScModel.h" 11 | #include "ScParser.h" 12 | 13 | #include "PlantUmlGenerator.h" 14 | #include "StmListGenerator.h" 15 | #include "TextGenerator.h" 16 | #include "YamlGenerator.h" 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace po = boost::program_options; 25 | namespace bp = boost::process; 26 | 27 | struct Settings 28 | { 29 | std::vector inputFiles; 30 | std::string stmName; 31 | std::string generatorName; 32 | std::vector highlightNameList; 33 | bool isUseObjdump = false; 34 | bool isExtractObjdump = false; 35 | bool isStripObjdump = false; 36 | bool isListStms = false; 37 | }; 38 | 39 | static bool parseOptions(Settings &settings, int argc, char *argv[]) 40 | { 41 | po::options_description oVisible("Generic options"); 42 | // clang-format off 43 | oVisible.add_options() 44 | ("help,h", "Show this help message") 45 | ("objdump,O", "Input files is an objdump instead of binary") 46 | ("extract,X", "Only extract an objdump out of an input binary") 47 | ("strip,S", "Strip an objdump to contain only relevant data") 48 | ("list,l", "List all state-machine names") 49 | ("highlight,t", po::value>(&settings.highlightNameList), 50 | "Highlight given names in the generated state-machine output (multiple use possible)") 51 | ("stm,s", po::value(&settings.stmName), 52 | "Generate an output for a specific state-machine") 53 | ("generator,g", po::value(&settings.generatorName), 54 | "Specify a generator (plantuml, stmlist, text, yaml)"); 55 | // clang-format on 56 | 57 | po::options_description oHidden; 58 | oHidden.add_options()("input", 59 | po::value>(&settings.inputFiles)->multitoken(), 60 | "Input files"); 61 | 62 | po::positional_options_description poDesc; 63 | poDesc.add("input", -1); 64 | 65 | po::options_description oDesc; 66 | oDesc.add(oHidden).add(oVisible); 67 | 68 | po::variables_map vmap; 69 | po::store(po::command_line_parser(argc, argv).options(oDesc).positional(poDesc).run(), vmap); 70 | po::notify(vmap); 71 | 72 | auto isSpecified = [&vmap](const char *optName) { return vmap.count(optName) > 0; }; 73 | 74 | if (isSpecified("help")) { 75 | std::cout << "bosce: BOost State Chart Extractor (from objdump)\n\n" 76 | "Usage:\n bosce [options]\n\n" 77 | << oVisible << std::endl; 78 | return false; 79 | } 80 | 81 | if (!isSpecified("input")) { 82 | throw po::error("input file is not specified"); 83 | } 84 | 85 | settings.isUseObjdump = isSpecified("objdump"); 86 | settings.isExtractObjdump = isSpecified("extract"); 87 | settings.isStripObjdump = isSpecified("strip"); 88 | settings.isListStms = isSpecified("list"); 89 | 90 | if (settings.isExtractObjdump) { 91 | if (settings.isUseObjdump || settings.isListStms || !settings.stmName.empty() 92 | || !settings.generatorName.empty()) { 93 | throw po::error("option '-X' can be combined only with option '-S'"); 94 | } 95 | } else if (settings.isStripObjdump) { 96 | if (!(settings.isUseObjdump || settings.isExtractObjdump)) { 97 | throw po::error("option '-S' must be combined with either '-X' or '-O'"); 98 | } 99 | if (settings.isListStms || !settings.stmName.empty() || !settings.generatorName.empty()) { 100 | throw po::error("option '-S' can be combined only with option '-X'"); 101 | } 102 | } else if (settings.isListStms) { 103 | if (!settings.stmName.empty() || !settings.generatorName.empty()) { 104 | throw po::error("option '-l' cannot be combined with options '-s' and '-g'"); 105 | } 106 | settings.generatorName = "stmlist"; 107 | } else if (settings.generatorName.empty()) { 108 | // by default use plantuml as the most sophisticated generator: 109 | settings.generatorName = "plantuml"; 110 | } 111 | 112 | return true; 113 | } 114 | 115 | static void parseInput(const std::string &inputFile, const Settings &settings, 116 | ObjDumpParser &objDumpParser) 117 | { 118 | if (settings.isUseObjdump) { 119 | /* Use a pre-generated objdump file */ 120 | std::ifstream file(inputFile); 121 | if (!file) { 122 | throw std::runtime_error("Cannot open an input file"); 123 | } 124 | objDumpParser.parse(file, settings.isStripObjdump); 125 | 126 | } else { 127 | /* Generate an objfile on the fly. */ 128 | const auto isFullExtractMode = settings.isExtractObjdump && !settings.isStripObjdump; 129 | bp::ipstream stream; 130 | 131 | auto objdump = [&](auto &output) { 132 | return bp::child(bp::search_path("objdump"), "-j", ".text", "-C", "-d", inputFile, 133 | bp::std_out > output, bp::std_err > stderr); 134 | }; 135 | 136 | auto child = isFullExtractMode ? objdump(stdout) : objdump(stream); 137 | if (!isFullExtractMode) { 138 | objDumpParser.parse(stream, settings.isStripObjdump); 139 | } 140 | 141 | child.wait(); 142 | if (child.exit_code() != 0) { 143 | throw std::runtime_error("objdump returned " + std::to_string(child.exit_code()) 144 | + "; invalid input binary name?"); 145 | } 146 | } 147 | } 148 | 149 | int main(int argc, char *argv[]) 150 | try { 151 | // Command line arguments: 152 | Settings settings; 153 | if (!parseOptions(settings, argc, argv)) { 154 | return EXIT_SUCCESS; 155 | } 156 | 157 | // Model: 158 | ScModel model; 159 | 160 | // Highlight name set: 161 | ScNameSet highlightSet; 162 | for (const auto &highlightName : settings.highlightNameList) { 163 | highlightSet.insert(highlightName); 164 | } 165 | 166 | // Generator: 167 | std::unique_ptr generator; 168 | if (settings.isExtractObjdump || settings.isStripObjdump) { 169 | /* do nothing */ 170 | } else if (settings.generatorName == "plantuml") { 171 | generator = std::make_unique(model, highlightSet); 172 | } else if (settings.generatorName == "stmlist") { 173 | generator = std::make_unique(model); 174 | } else if (settings.generatorName == "text") { 175 | generator = std::make_unique(model, highlightSet); 176 | } else if (settings.generatorName == "yaml") { 177 | generator = std::make_unique(model, highlightSet); 178 | } else { 179 | std::cerr << "Invalid generator: " << settings.generatorName << "\n"; 180 | std::cerr << "Available generators: plantuml (default), stmlist, text and yaml\n" 181 | << std::endl; 182 | return -1; 183 | } 184 | 185 | // Parsing: 186 | ScParser scParser(model); 187 | ObjDumpParser objDumpParser(scParser); 188 | 189 | for (const auto &inputFile : settings.inputFiles) { 190 | parseInput(inputFile, settings, objDumpParser); 191 | } 192 | 193 | // Do not generate the output if an objdump strip is requested: 194 | if (settings.isExtractObjdump || settings.isStripObjdump) { 195 | return 0; 196 | } 197 | 198 | // Generation: 199 | generator->generate(std::cout, settings.stmName); 200 | 201 | return 0; 202 | } catch (const std::exception &ex) { 203 | std::cerr << "Error: " << ex.what() << std::endl; 204 | return EXIT_FAILURE; 205 | } 206 | -------------------------------------------------------------------------------- /src/ScParser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Boost StateChart Extractor 3 | * 4 | * Distributed under the Boost Software License, Version 1.0. 5 | * (See accompanying file LICENSE or copy at 6 | * http://www.boost.org/LICENSE_1_0.txt) 7 | */ 8 | 9 | #include "ScParser.h" 10 | #include "ParserHelpers.h" 11 | #include "ScModel.h" 12 | 13 | ScParser::ScParser(ScModel &model) 14 | : m_model(model) 15 | , m_hasCurrentState(false) 16 | { 17 | } 18 | 19 | template 20 | inline bool expectStartsWith(char *&data, const char (&prefix)[N]) 21 | { 22 | return expectString(data, prefix); 23 | } 24 | 25 | template 26 | inline bool expectEndsWith(const char *start, char *end, const char (&suffix)[N]) 27 | { 28 | const std::size_t dataLen = end - start; 29 | const std::size_t suffixLen = N - 1; 30 | if (dataLen >= suffixLen) { 31 | if (eqString(end - suffixLen, suffix)) { 32 | end[-suffixLen] = '\0'; 33 | return true; 34 | } 35 | } 36 | return false; 37 | } 38 | 39 | inline char *matchAngleArgument(char *&data, bool skipToEnd = false) 40 | { 41 | char *start = data; 42 | int nrAngles = 1; 43 | 44 | for (;;) { 45 | switch (*data) { 46 | case ',': 47 | if (!skipToEnd && (nrAngles == 1)) { 48 | *(data++) = '\0'; 49 | if (*data == ' ') { 50 | ++data; 51 | } 52 | return start; 53 | } 54 | break; 55 | case '>': 56 | if (!--nrAngles) { 57 | if (data[-1] == ' ') { 58 | // handle a double closing angle, e.g. "foo >" 59 | data[-1] = '\0'; 60 | ++data; 61 | } else { 62 | *(data++) = '\0'; 63 | } 64 | return start; 65 | } 66 | break; 67 | case '<': 68 | ++nrAngles; 69 | break; 70 | case '\0': 71 | return nullptr; 72 | default: 73 | break; 74 | } 75 | ++data; 76 | } 77 | } 78 | 79 | static ScTarget parseStateHistory(char *target) noexcept 80 | { 81 | if (expectStartsWith(target, "boost::statechart::")) { 82 | if (expectStartsWith(target, "deep_history<")) { 83 | return {matchAngleArgument(target), ScHistoryMode::Deep}; 84 | } 85 | if (expectStartsWith(target, "shallow_history<")) { 86 | return {matchAngleArgument(target), ScHistoryMode::Shallow}; 87 | } 88 | if (expectStartsWith(target, "full_history<")) { 89 | return {matchAngleArgument(target), ScHistoryMode::Full}; 90 | } 91 | } 92 | return {target, ScHistoryMode::None}; 93 | } 94 | 95 | static ScTarget matchTransitionTarget(char *&data) noexcept 96 | { 97 | char *target = matchAngleArgument(data); 98 | return parseStateHistory(target); 99 | } 100 | 101 | bool ScParser::parseFunctionDecl(char *&data) 102 | { 103 | m_hasCurrentState = false; 104 | 105 | // boost::statechart::detail::reaction_result 106 | // boost::statechart::simple_state< *state-spec* >:: 107 | // local_react, 109 | // boost::statechart::deferral< * event-name * >, 110 | // mpl_::na, ..., mpl_::na> >( 111 | // boost::statechart::event_base const&, void const*)> 112 | 113 | if (expectStartsWith(data, "boost::statechart::")) { 114 | expectStartsWith(data, "detail::reaction_result boost::statechart::"); 115 | // boost::statechart::state_machine::transit<*state-name*>() 138 | 139 | if (!expectStartsWith(data, "boost::statechart::detail::safe_reaction_result ")) { 140 | expectStartsWith(data, "boost::statechart::detail::reaction_result "); 141 | } 142 | if (expectStartsWith(data, "boost::statechart::simple_state<")) { 143 | matchAngleArgument(data, true); 144 | if (expectStartsWith(data, "::transit<")) { 145 | auto [target, historyMode] = matchTransitionTarget(data); 146 | m_model.addTransition(target, m_currentEvent, historyMode); 147 | } else if (expectStartsWith(data, "::discard_event()")) { 148 | m_model.addTransition(m_currentState, m_currentEvent); 149 | } else if (expectStartsWith(data, "::defer_event()")) { 150 | m_model.addDeferral(m_currentEvent); 151 | } 152 | return true; 153 | } 154 | return false; 155 | } 156 | 157 | void ScParser::parseStateMachine(char *&data) 158 | { 159 | ScName name = matchAngleArgument(data); 160 | ScName initialState = matchAngleArgument(data); 161 | 162 | m_model.addStateMachine(name, initialState); 163 | } 164 | 165 | static ScTargetList parseInitialSubstateList(char *mplList) 166 | { 167 | ScTargetList list; 168 | 169 | if (expectStartsWith(mplList, "boost::mpl::list")) { 170 | expectAddress(mplList); 171 | if (!expectString(mplList, "<")) { 172 | return list; 173 | } 174 | 175 | while (true) { 176 | char *substate = matchAngleArgument(mplList); 177 | if (!substate) { // end of list 178 | break; 179 | } 180 | if (expectStartsWith(substate, "mpl_::na")) { // default values started 181 | break; 182 | } 183 | 184 | list.push_back(parseStateHistory(substate)); 185 | } 186 | } else { 187 | list.emplace_back(mplList); 188 | } 189 | 190 | return list; 191 | } 192 | 193 | static ScHistoryMode parseHistoryMode(char *historyMode) noexcept 194 | { 195 | if (expectStartsWith(historyMode, "(boost::statechart::history_mode)")) { 196 | char *end; 197 | auto value = std::strtoul(historyMode, &end, 10); 198 | const long totalModes = 3; 199 | if (*end != 0 || value > totalModes) { 200 | return ScHistoryMode::None; 201 | } 202 | return static_cast(value); 203 | } 204 | 205 | return ScHistoryMode::None; 206 | } 207 | 208 | void ScParser::parseReactionList(char *mplList) 209 | { 210 | /* 211 | * ::local_react, 212 | * boost::statechart::deferral< * event-name * >, 213 | * mpl_::na, ..., mpl_::na> >(boost::statechart::event_base 214 | * const&, void const*)> 215 | */ 216 | 217 | if (expectStartsWith(mplList, "boost::mpl::list")) { 218 | expectAddress(mplList); 219 | if (!expectString(mplList, "<")) { 220 | return; 221 | } 222 | 223 | while (true) { 224 | char *reaction = matchAngleArgument(mplList); 225 | if (!reaction) { 226 | break; 227 | } 228 | if (expectStartsWith(reaction, "mpl_::na")) { // default values started 229 | break; 230 | } 231 | 232 | if (expectStartsWith(reaction, "boost::statechart::transition<")) { 233 | std::string event = matchAngleArgument(reaction); 234 | auto [target, historyMode] = matchTransitionTarget(reaction); 235 | m_model.addTransition(target, event, historyMode); 236 | } else if (expectStartsWith(reaction, "boost::statechart::deferral<")) { 237 | std::string event = matchAngleArgument(reaction); 238 | m_model.addDeferral(event); 239 | } 240 | } 241 | } 242 | } 243 | 244 | void ScParser::parseSimpleState(char *&data) 245 | { 246 | /* 247 | * Basic: 248 | * 249 | * simple_state, 251 | * (boost::statechart::history_mode)0> 252 | * 253 | * With orthogonal region in a parent state: 254 | * 255 | * simple_state< 256 | * state-name, 257 | * boost::statechart::simple_state< 258 | * parent-state, 259 | * grandparent-state, 260 | * boost::mpl::list< 261 | * initial-parent-substate, 262 | * mpl_::na, 263 | * ...>, 264 | * (boost::statechart::history_mode)0>::orthogonal<(unsigned char)0>, 265 | * boost::mpl::list< 266 | * initial-substate, 267 | * mpl_::na, 268 | * ...>, 269 | * (boost::statechart::history_mode)0> 270 | * 271 | * initial-substate can be wrapped with a history marker, e.g. 272 | * boost::statechart::deep_history 273 | */ 274 | 275 | ScName name = matchAngleArgument(data); 276 | 277 | bool isOrthogonalState = expectStartsWith(data, "boost::statechart::simple_state<"); 278 | ScName parent = matchAngleArgument(data); 279 | int orthRegion = 0; 280 | 281 | if (isOrthogonalState) { 282 | matchAngleArgument(data, true); // skip to ::orthogonal 283 | if (!expectStartsWith(data, "::orthogonal<(unsigned char)")) { 284 | return; 285 | } 286 | orthRegion = std::atoi(data); 287 | matchAngleArgument(data, true); // skip to ',' 288 | matchAngleArgument(data); // skip to a list of initial substates 289 | } 290 | 291 | auto initialSubstates = parseInitialSubstateList(matchAngleArgument(data)); 292 | auto historyMode = parseHistoryMode(matchAngleArgument(data)); 293 | 294 | m_model.addState(name, parent, orthRegion, initialSubstates, historyMode); 295 | 296 | if (expectStartsWith(data, "::local_react<")) { 297 | parseReactionList(matchAngleArgument(data)); 298 | } 299 | } 300 | 301 | inline char *matchName(char *&data, char terminus) 302 | { 303 | char *start = data; 304 | 305 | for (int nrAngles = 0; *data; ++data) { 306 | if (isalnum(*data) || (*data == ':') || (*data == '_')) { 307 | continue; 308 | } 309 | if (*data == '<') { 310 | ++nrAngles; 311 | continue; 312 | } 313 | if ((*data == '>') && (nrAngles > 0)) { 314 | --nrAngles; 315 | continue; 316 | } 317 | if (nrAngles == 0) { 318 | break; 319 | } 320 | } 321 | 322 | if (*data != terminus) { 323 | return nullptr; 324 | } 325 | if (*data == '\0') { 326 | return start; 327 | } 328 | *(data++) = '\0'; 329 | return start; 330 | } 331 | 332 | bool ScParser::parseReactMethod(char *&data) 333 | { 334 | /* 335 | * *state*::react(*event* const&) 336 | */ 337 | 338 | char *function = matchName(data, '('); 339 | if (!function) { 340 | return false; 341 | } 342 | if (!expectEndsWith(function, data - 1, "::react")) { 343 | return false; 344 | } 345 | 346 | m_currentState = function; 347 | 348 | char *eventName = matchName(data, ' '); 349 | if (eventName == nullptr) { 350 | /* Sometimes events are not passed as const&. 351 | * *state*::react(*event*) */ 352 | eventName = matchName(data, ')'); 353 | if (eventName == nullptr) { 354 | return false; 355 | } 356 | } 357 | m_currentEvent = eventName; 358 | if (m_currentEvent.empty()) { 359 | return false; 360 | } 361 | 362 | m_hasCurrentState = true; 363 | m_model.setActiveState(m_currentState); 364 | return true; 365 | } 366 | --------------------------------------------------------------------------------