diff --git a/.gitignore b/.gitignore index 149298d..9b935aa 100644 --- a/.gitignore +++ b/.gitignore @@ -180,4 +180,4 @@ build/ .html/ .latex/ -out/ \ No newline at end of file +out/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 198c830..9c65583 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,12 @@ find_package(libelf REQUIRED) # Find package dependancies list(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/build/Debug/generators") -#Linking Libraries -add_executable(${PROJECT_NAME} src/main.cpp src/elf_parser.cpp src/gcc_parse.cpp) +#Creating Executable Target +add_executable(${PROJECT_NAME} src/main.cpp + src/elf_parser.cpp + src/gcc_parse.cpp + src/state_machine.cpp + src/concrete_states.cpp) #Linking Libraries target_link_libraries(${PROJECT_NAME} PUBLIC ctre::ctre libelf::libelf) @@ -45,7 +49,10 @@ target_compile_options(${PROJECT_NAME} PRIVATE ${COMPILER_BUILD_FLAGS}) libhal_unit_test(SOURCES tests/main.test.cpp - tests/testing.test.cpp + # tests/testing.test.cpp + + src/state_machine.cpp + src/concrete_states.cpp PACKAGES tl-function-ref diff --git a/conanfile.py b/conanfile.py index 8f58551..31f8fe5 100644 --- a/conanfile.py +++ b/conanfile.py @@ -49,7 +49,7 @@ def build_requirements(self): """ def layout(self): cmake_layout(self) - + """ Method to build the project """ diff --git a/include/concrete_states.hpp b/include/concrete_states.hpp new file mode 100644 index 0000000..a1472cf --- /dev/null +++ b/include/concrete_states.hpp @@ -0,0 +1,247 @@ +/** + * @file concrete_states.hpp + * @brief Concrete state implementations for the state machine. + * + * This file defines all concrete states used in the state machine workflow. + * Each state represents a distinct phase in the processing pipeline, from + * user input through validation to output generation. + * + * The states follow a sequential workflow: + * UserInput → ElfParser → Callgraph → AbiParser → Validator → Output + * + * ErrorState can be entered from any state when an error occurs. + */ + +#pragma once +#include +#include +#include + +#include "state.hpp" +#include "state_context.hpp" + +/** + * @class UserInputState + * @brief Initial state that handles user input collection and validation. + * + * This is the entry point of the state machine. It processes user input, + * validates parameters, and prepares the context for subsequent states. + * + * @note This is the default initial state of the StateMachine. + */ +class UserInputState : public State +{ + public: + /** + * @brief Initialize user input processing. + * @param p_context Shared state context for storing input data. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Process and validate user input. + * @param p_context Context to store validated input parameters. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Transition to ElfParserState or ErrorState. + * @param p_context Context containing validated input. + * @return Next state (typically ElfParserState) or ErrorState on failure. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class ElfParserState + * @brief Parses ELF (Executable and Linkable Format) binary files. + * + * This state reads and analyzes ELF binaries, extracting relevant information + * such as symbols, sections, and metadata needed for further processing. + */ +class ElfParserState : public State +{ + public: + /** + * @brief Initialize ELF parsing resources. + * @param p_context Context containing file paths and configuration. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Parse the ELF binary and extract data. + * @param p_context Context to store parsed ELF information. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Transition to CallgraphState or ErrorState. + * @param p_context Context containing parsed ELF data. + * @return Next state (typically CallgraphState) or ErrorState on parse + * failure. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class CallgraphState + * @brief Generates and analyzes the call graph from parsed binary data. + * + * This state parses the GCC call graph representing function relationships + * and dependencies within the analyzed binary. + */ +class CallgraphState : public State +{ + public: + /** + * @brief Initialize call graph generation. + * @param p_context Context containing parsed binary data. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Build the call graph structure. + * @param p_context Context to store the generated call graph. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Transition to AbiParserState or ErrorState. + * @param p_context Context containing the call graph. + * @return Next state (typically AbiParserState) or ErrorState on failure. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class AbiParserState + * @brief Parses and analyzes Application Binary Interface (ABI) information. + * + * This state extracts and processes ABI-related data, including function + * signatures, calling conventions, and interface specifications. + */ +class AbiParserState : public State +{ + public: + /** + * @brief Initialize ABI parsing. + * @param p_context Context containing call graph and binary data. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Parse ABI specifications and interface data. + * @param p_context Context to store parsed ABI information. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Transition to ValidatorState or ErrorState. + * @param p_context Context containing ABI data. + * @return Next state (typically ValidatorState) or ErrorState on parse + * failure. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class ValidatorState + * @brief Validates the processed data for correctness and consistency. + * + * This state performs validation checks on the parsed and processed data + * to ensure exceptions meets requirements and is internally consistent before + * output. + */ +class ValidatorState : public State +{ + public: + /** + * @brief Initialize validation checks. + * @param p_context Context containing all processed data. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Perform validation on processed data. + * @param p_context Context containing data to validate. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Transition to OutputState or ErrorState. + * @param p_context Context with validation results. + * @return Next state (OutputState if valid) or ErrorState on validation + * failure. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class OutputState + * @brief Generates and writes the final output. + * + * This is the final state in the normal execution flow. It formats and + * outputs the processed results to the specified destination. + */ +class OutputState : public State +{ + public: + /** + * @brief Initialize output generation. + * @param p_context Context containing validated data to output. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Generate and write output. + * @param p_context Context containing all processed data. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Complete execution and terminate state machine. + * @param p_context Context with output results. + * @return std::nullopt to signal state machine completion. + */ + std::optional> exit( + StateContext& p_context) override; +}; + +/** + * @class ErrorState + * @brief Handles error conditions and cleanup. + * + * This state is entered when an error occurs in any other state. It handles + * error reporting, logging, and cleanup operations before terminating the + * state machine. + * + * @note This state always returns std::nullopt to terminate execution. + */ +class ErrorState : public State +{ + public: + /** + * @brief Initialize error handling. + * @param p_context Context containing error information. + */ + void enter(StateContext& p_context) override; + + /** + * @brief Process and report the error. + * @param p_context Context with error details for reporting. + */ + void handle(StateContext& p_context) override; + + /** + * @brief Terminate state machine after error handling. + * @param p_context Context after error processing. + * @return std::nullopt to signal state machine termination. + */ + std::optional> exit( + StateContext& p_context) override; +}; \ No newline at end of file diff --git a/include/state.hpp b/include/state.hpp new file mode 100644 index 0000000..19060f2 --- /dev/null +++ b/include/state.hpp @@ -0,0 +1,78 @@ +#pragma once +#include +#include + +#include "state_context.hpp" + +/** + * @class State + * @brief Abstract base class for all states in the state machine. + * + * The State class defines the interface that all concrete states must + * implement. Each state has three lifecycle phases: enter (initialization), + * handle (main logic), and exit (cleanup and transition). States use the + * StateContext to access shared data and determine transitions. + * + * Concrete states should inherit from this class and implement all three pure + * virtual methods to define their specific behavior. + * + * @note This is an abstract class and cannot be instantiated directly. + * @see StateMachine + * @see StateContext + * @see state_context.hpp + */ +class State +{ + public: + /** + * @brief Called when entering the state. + * + * This method is invoked when the state machine transitions into this + * state. It should perform any initialization or setup required before the + * state's main logic executes. + * + * @param context Reference to the shared state context containing data + * and configuration accessible to all states. + * + * @note This is called before handle() in the state lifecycle. + */ + virtual void enter(StateContext& p_context) = 0; + + /** + * @brief Executes the main logic of the state. + * + * This method contains the primary behavior and operations of the state. + * It is called after enter() and before exit() during state execution. + * + * @param context Reference to the shared state context for accessing + * and modifying shared data. + * + * @note This is where the state performs its core functionality. + */ + virtual void handle(StateContext& p_context) = 0; + + /** + * @brief Called when exiting the state and determines the next state. + * + * This method is invoked after handle() completes. It should perform any + * cleanup operations and return the next state to transition to. If this + * returns std::nullopt, the state machine will terminate. + * + * @param context Reference to the shared state context for accessing + * data needed to determine the next state. + * + * @return std::optional> The next state to + * transition to, or std::nullopt to terminate the state machine. + * + * @note Returning std::nullopt signals the end of state machine execution. + * @note The returned state's ownership is transferred to the state machine. + */ + virtual std::optional> exit(StateContext& p_context) + = 0; + + /** + * @brief Virtual destructor for proper cleanup of derived classes. + * + */ + virtual ~State() = default; +}; \ No newline at end of file diff --git a/include/state_context.hpp b/include/state_context.hpp new file mode 100644 index 0000000..718895e --- /dev/null +++ b/include/state_context.hpp @@ -0,0 +1,23 @@ +#pragma once + +/** + * @brief Content in this is temporary. Please change + * them according to what the state desires. All data relating to + * SAFE will be stored here and shared between the states. + * + */ +class StateContext +{ + public: + void inc_data() + { + data++; + } + int get_data() + { + return data; + } + + private: + int data = 0; +}; \ No newline at end of file diff --git a/include/state_machine.hpp b/include/state_machine.hpp new file mode 100644 index 0000000..8e2728b --- /dev/null +++ b/include/state_machine.hpp @@ -0,0 +1,123 @@ +#pragma once +#include +#include +#include + +#include "concrete_states.hpp" +#include "state.hpp" +#include "state_context.hpp" + +class State; + +/** + * @class StateMachine + * @brief Manages state transitions and execution in a finite state machine. + * + * The StateMachine class implements a complete state machine that automatically + * executes states in sequence. Each state performs its enter, handle, and exit + * operations, with the exit operation determining the next state transition. + * The machine runs until a state returns std::nullopt, indicating completion. + * + * @note The state machine begins with UserInputState as the initial state. + * @note States are owned exclusively by the state machine via unique pointers. + * @see State + * @see StateContext + * @see concrete_states.hpp + */ +class StateMachine +{ + public: + /** + * @brief Constructs a new StateMachine with UserInputState as the initial + * state. + * + * Initializes the state machine with a default context and sets + * UserInputState as the starting state. + */ + StateMachine(); + + /** + * @brief Destroys the StateMachine. + * + * Default destructor that cleans up the current state and context. + */ + ~StateMachine() = default; + + /** + * @brief Gets a reference to the current state. + * + * Retrieves the currently active state without transferring ownership. + * The reference remains valid until the state transitions. + * + * @return std::optional> A reference to + * the current state, or std::nullopt if no state is active. + * + * @note This dereferences the optional and unique_ptr to return a direct + * reference to the State object. + */ + std::optional> get_current_state(); + + /** + * @brief Gets the reference of the current state context. + * + * Retrieves the reference of the context containing shared data and + * configuration used by states during execution. + * + * @return StateContext A reference of the current context. + */ + StateContext& get_context(); + + /** + * @brief Executes the state machine until completion. + * + * Runs a continuous loop that executes each state's full lifecycle: + * 1. Calls enter() on the current state + * 2. Calls handle() to perform the state's main logic + * 3. Calls exit() which returns the next state (or nullopt to terminate) + * 4. Transitions to the next state + * + * The loop continues until a state's exit() method returns std::nullopt, + * indicating the state machine has completed its execution. + * + * @note This is a blocking operation that runs until the state machine + * reaches a terminal state. + * @see State::enter() + * @see State::handle() + * @see State::exit() + */ + void run_state(); + + /** + * @brief Transitions to a new state by moving ownership. + * + * Replaces the current state with the new state. If p_new_state is + * std::nullopt, the state machine will have no active state and + * run_state() will terminate. + * + * @param p_new_state The new state to transition to, or std::nullopt to + * terminate the state machine. + * + * @note Ownership of the new state is transferred to the state machine + * using move semantics. + * @note The previous state is automatically destroyed when replaced. + */ + void transition_state(std::optional> p_new_state); + + private: + /** + * @brief The currently active state. + * + * Holds exclusive ownership of the current state. Set to std::nullopt + * when the state machine has completed execution. + */ + std::optional> m_current_state; + + /** + * @brief The context shared across all states. + * + * Contains data and configuration that persists across state transitions. + * This context is passed to each state's enter(), handle(), and exit() + * methods. + */ + StateContext m_context; +}; diff --git a/src/concrete_states.cpp b/src/concrete_states.cpp new file mode 100644 index 0000000..93cbf63 --- /dev/null +++ b/src/concrete_states.cpp @@ -0,0 +1,125 @@ +#include "concrete_states.hpp" + +/** + * @brief Content for each of function is temporary. Please change + * them according to what the state desires + * + */ + +void UserInputState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void UserInputState::handle(StateContext& p_context) +{ + p_context.inc_data(); +} + +std::optional> UserInputState::exit( + StateContext& p_context) +{ + std::ignore = p_context; + return std::make_unique(); +} + +void ElfParserState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void ElfParserState::handle(StateContext& p_context) +{ + p_context.inc_data(); +} + +std::optional> ElfParserState::exit( + StateContext& p_context) +{ + std::ignore = p_context; + return std::make_unique(); +} + +void CallgraphState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void CallgraphState::handle(StateContext& p_context) +{ + std::ignore = p_context; + p_context.inc_data(); +} + +std::optional> CallgraphState::exit( + StateContext& p_context) +{ + std::ignore = p_context; + return std::make_unique(); +} + +void AbiParserState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void AbiParserState::handle(StateContext& p_context) +{ + p_context.inc_data(); +} + +std::optional> AbiParserState::exit( + StateContext& p_context) +{ + std::ignore = p_context; + return std::make_unique(); +} + +void ValidatorState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void ValidatorState::handle(StateContext& p_context) +{ + p_context.inc_data(); +} + +std::optional> ValidatorState::exit( + StateContext& p_context) +{ + std::ignore = p_context; + return std::make_unique(); +} + +void OutputState::enter(StateContext& p_context) +{ + std::print("Previous Number: {}\n", p_context.get_data()); +} + +void OutputState::handle(StateContext& p_context) +{ + p_context.inc_data(); +} + +std::optional> OutputState::exit(StateContext& p_context) +{ + std::ignore = p_context; + return std::nullopt; +} + +void ErrorState::enter(StateContext& p_context) +{ + std::ignore = p_context; +} + +void ErrorState::handle(StateContext& p_context) +{ + std::ignore = p_context; +} + +std::optional> ErrorState::exit(StateContext& p_context) +{ + std::ignore = p_context; + return std::nullopt; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 803741c..6397515 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,11 @@ * @copyright Copyright (c) 2025 * */ +#include "concrete_states.hpp" +#include "state.hpp" +#include "state_context.hpp" +#include "state_machine.hpp" + #include #include @@ -16,5 +21,9 @@ int main(int argc, char** argv) std::ignore = argc; std::ignore = argv; std::println("Hello C++: {}", __cplusplus); + + StateMachine sm; + sm.run_state(); + return 0; } diff --git a/src/state_machine.cpp b/src/state_machine.cpp new file mode 100644 index 0000000..3b9ce37 --- /dev/null +++ b/src/state_machine.cpp @@ -0,0 +1,35 @@ +#include "state_machine.hpp" +#include "concrete_states.hpp" + +StateMachine::StateMachine() +{ + m_current_state = std::make_unique(); +} + +std::optional> StateMachine::get_current_state() +{ + if (m_current_state) { + return std::ref(**m_current_state); + } + return std::nullopt; +} + +StateContext& StateMachine::get_context() +{ + return m_context; +} + +void StateMachine::run_state() +{ + while (m_current_state != std::nullopt) { + m_current_state.value()->enter(m_context); + m_current_state.value()->handle(m_context); + transition_state(m_current_state.value()->exit(m_context)); + } +} + +void StateMachine::transition_state( + std::optional> new_state) +{ + this->m_current_state = std::move(new_state); +} \ No newline at end of file diff --git a/tests/main.test.cpp b/tests/main.test.cpp index 36712f5..25e6cd9 100644 --- a/tests/main.test.cpp +++ b/tests/main.test.cpp @@ -1,4 +1,10 @@ +#include "state_machine.test.cpp" + +void state_machine_tests(); + int main() { // Position dependent test go below: + state_machine_tests(); + return 0; } \ No newline at end of file diff --git a/tests/state_machine.test.cpp b/tests/state_machine.test.cpp new file mode 100644 index 0000000..87377d3 --- /dev/null +++ b/tests/state_machine.test.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include "concrete_states.hpp" +#include "state.hpp" +#include "state_context.hpp" +#include "state_machine.hpp" + +// Mock State for testing +class MockState : public State +{ + public: + bool enter_called = false; + bool handle_called = false; + bool exit_called = false; + + void enter(StateContext& context) override + { + std::ignore = context; + enter_called = true; + } + + void handle(StateContext& context) override + { + std::ignore = context; + handle_called = true; + } + + std::optional> exit(StateContext& context) override + { + std::ignore = context; + exit_called = true; + return std::nullopt; + } +}; + +void state_machine_tests() +{ + using namespace boost::ut; + + "StateMachine"_test = [] { + "Initalize inital state"_test = [] { + StateMachine sm; + auto actual_state = sm.get_current_state(); + expect(actual_state.has_value()) << "A State is initialized"; + + State& actual_state_ref = actual_state.value().get(); + expect(typeid(actual_state_ref) == typeid(UserInputState)) + << "Initial state should be UserInputState"; + }; + + "Transition State"_test = [] { + StateMachine sm; + std::optional> mock_state + = std::make_unique(); + sm.transition_state(std::move(mock_state)); + + auto actual_state = sm.get_current_state(); + expect(actual_state.has_value()) << "A State is initialized"; + + State& actual_state_ref = actual_state.value().get(); + expect(typeid(actual_state_ref) == typeid(MockState)) + << "State should transition to MockState"; + }; + + "State calling enter, handle, and exit"_test = [] { + StateMachine sm; + auto mock_state = std::make_unique(); + auto* mock_ptr = mock_state.get(); + + sm.transition_state(std::move(mock_state)); + + auto state = sm.get_current_state(); + if (state.has_value()) { + StateContext context; + state.value().get().enter(context); + state.value().get().handle(context); + state.value().get().exit(context); + } + + expect(mock_ptr->enter_called) << "Should run enter()"; + expect(mock_ptr->handle_called) << "Should run handle()"; + expect(mock_ptr->exit_called) << "Should run exit()"; + }; + + "State Transition Order"_test = [] { + StateMachine sm; + StateContext context = sm.get_context(); + std::optional> actual_state; + std::vector state_order + = { typeid(UserInputState), typeid(ElfParserState), + typeid(CallgraphState), typeid(AbiParserState), + typeid(ValidatorState), typeid(OutputState) }; + + for (auto& expect_state : state_order) { + actual_state = sm.get_current_state(); + State& actual_state_ref = actual_state.value().get(); + expect(typeid(actual_state_ref) == expect_state) + << "State should transition to " << expect_state.name(); + actual_state_ref.enter(context); + actual_state_ref.handle(context); + sm.transition_state(actual_state_ref.exit(context)); + } + }; + }; +} \ No newline at end of file