diff --git a/Fw/Buffer/Buffer.hpp b/Fw/Buffer/Buffer.hpp index f24e7971a5..82c92357d5 100644 --- a/Fw/Buffer/Buffer.hpp +++ b/Fw/Buffer/Buffer.hpp @@ -16,7 +16,6 @@ #include #if FW_SERIALIZABLE_TO_STRING #include - #include // snprintf #ifdef BUILD_UT #include #include diff --git a/Fw/Comp/ActiveComponentBase.cpp b/Fw/Comp/ActiveComponentBase.cpp index d5bbd8c7a4..e0cb49f519 100644 --- a/Fw/Comp/ActiveComponentBase.cpp +++ b/Fw/Comp/ActiveComponentBase.cpp @@ -2,7 +2,6 @@ #include #include #include -#include namespace Fw { @@ -38,14 +37,9 @@ namespace Fw { QueuedComponentBase::init(instance); } -#if FW_OBJECT_TO_STRING == 1 && FW_OBJECT_NAMES == 1 - void ActiveComponentBase::toString(char* buffer, NATIVE_INT_TYPE size) { - FW_ASSERT(size > 0); - FW_ASSERT(buffer != nullptr); - PlatformIntType status = snprintf(buffer, static_cast(size), "ActComp: %s", this->m_objName.toChar()); - if (status < 0) { - buffer[0] = 0; - } +#if FW_OBJECT_TO_STRING == 1 + const char* ActiveComponentBase::getToStringFormatString() { + return "ActComp: %s"; } #endif @@ -55,9 +49,7 @@ namespace Fw { #if FW_OBJECT_NAMES == 1 taskName = this->getObjName(); #else - char taskNameChar[FW_TASK_NAME_BUFFER_SIZE]; - (void)snprintf(taskNameChar,sizeof(taskNameChar),"ActComp_%" PRI_FwSizeType,Os::Task::getNumTasks()); - taskName = taskNameChar; + (void) taskName.format("ActComp_%" PRI_FwSizeType, Os::Task::getNumTasks()); #endif // Cooperative threads tasks externalize the task loop, and as such use the state machine as their task function // Standard multithreading tasks use the task loop to respectively call the state machine diff --git a/Fw/Comp/ActiveComponentBase.hpp b/Fw/Comp/ActiveComponentBase.hpp index d9ee8d3ccf..feed74ec8a 100644 --- a/Fw/Comp/ActiveComponentBase.hpp +++ b/Fw/Comp/ActiveComponentBase.hpp @@ -50,7 +50,7 @@ class ActiveComponentBase : public QueuedComponentBase { Os::Task m_task; //!< task object for active component #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); //!< create string description of component + virtual const char* getToStringFormatString(); //!< Format string for toString function #endif PRIVATE: Lifecycle m_stage; //!< Lifecycle stage of the component diff --git a/Fw/Comp/PassiveComponentBase.cpp b/Fw/Comp/PassiveComponentBase.cpp index 1e3b504c67..1a57bcd046 100644 --- a/Fw/Comp/PassiveComponentBase.cpp +++ b/Fw/Comp/PassiveComponentBase.cpp @@ -2,19 +2,30 @@ #include #include -#include +#include namespace Fw { PassiveComponentBase::PassiveComponentBase(const char* name) : Fw::ObjBase(name), m_idBase(0), m_instance(0) { } -#if FW_OBJECT_TO_STRING == 1 && FW_OBJECT_NAMES == 1 +#if FW_OBJECT_TO_STRING == 1 + const char* PassiveComponentBase::getToStringFormatString() { + return "Comp: %s"; + } + void PassiveComponentBase::toString(char* buffer, NATIVE_INT_TYPE size) { FW_ASSERT(size > 0); FW_ASSERT(buffer != nullptr); - PlatformIntType status = snprintf(buffer, static_cast(size), "Comp: %s", this->m_objName.toChar()); - if (status < 0) { + Fw::FormatStatus status = Fw::ExternalString(buffer, static_cast(size)).format( + this->getToStringFormatString(), +#if FW_OBJECT_NAMES == 1 + this->m_objName.toChar() +#else + "UNKNOWN" +#endif + ); + if (status != Fw::FormatStatus::SUCCESS) { buffer[0] = 0; } } diff --git a/Fw/Comp/PassiveComponentBase.hpp b/Fw/Comp/PassiveComponentBase.hpp index ddf1be3ca3..ff56a799cd 100644 --- a/Fw/Comp/PassiveComponentBase.hpp +++ b/Fw/Comp/PassiveComponentBase.hpp @@ -22,8 +22,11 @@ namespace Fw { virtual ~PassiveComponentBase(); //!< Destructor void init(NATIVE_INT_TYPE instance); //!< Initialization function NATIVE_INT_TYPE getInstance() const; + + #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); //!< returns string description of component + virtual const char* getToStringFormatString(); //!< Return the format for a generic component toString + void toString(char* str, NATIVE_INT_TYPE size) override; //!< returns string description of component #endif PRIVATE: U32 m_idBase; //!< ID base for opcodes etc. diff --git a/Fw/Comp/QueuedComponentBase.cpp b/Fw/Comp/QueuedComponentBase.cpp index b649093ff2..6fa01d695d 100644 --- a/Fw/Comp/QueuedComponentBase.cpp +++ b/Fw/Comp/QueuedComponentBase.cpp @@ -19,14 +19,9 @@ namespace Fw { PassiveComponentBase::init(instance); } -#if FW_OBJECT_TO_STRING == 1 && FW_OBJECT_NAMES == 1 - void QueuedComponentBase::toString(char* buffer, NATIVE_INT_TYPE size) { - FW_ASSERT(size > 0); - FW_ASSERT(buffer != nullptr); - PlatformIntType status = snprintf(buffer, static_cast(size), "QueueComp: %s", this->m_objName.toChar()); - if (status < 0) { - buffer[0] = 0; - } +#if FW_OBJECT_TO_STRING == 1 + const char* QueuedComponentBase::getToStringFormatString() { + return "QueueComp: %s"; } #endif @@ -36,9 +31,7 @@ namespace Fw { #if FW_OBJECT_NAMES == 1 queueName = this->m_objName; #else - char queueNameChar[FW_QUEUE_NAME_BUFFER_SIZE]; - (void)snprintf(queueNameChar,sizeof(queueNameChar),"CompQ_%" PRI_FwSizeType,Os::Queue::getNumQueues()); - queueName = queueNameChar; + queueName.format("CompQ_%" PRI_FwSizeType,Os::Queue::getNumQueues()); #endif return this->m_queue.create(queueName, depth, msgSize); } diff --git a/Fw/Comp/QueuedComponentBase.hpp b/Fw/Comp/QueuedComponentBase.hpp index 79833e1284..d78f69d393 100644 --- a/Fw/Comp/QueuedComponentBase.hpp +++ b/Fw/Comp/QueuedComponentBase.hpp @@ -37,7 +37,7 @@ namespace Fw { Os::Queue::Status createQueue(FwSizeType depth, FwSizeType msgSize); virtual MsgDispatchStatus doDispatch()=0; //!< method to dispatch a single message in the queue. #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); //!< dump string representation of component + virtual const char* getToStringFormatString(); //!< Format string for toString function #endif NATIVE_INT_TYPE getNumMsgsDropped(); //!< return number of messages dropped void incNumMsgDropped(); //!< increment the number of messages dropped diff --git a/Fw/Obj/ObjBase.cpp b/Fw/Obj/ObjBase.cpp index 161e047129..2d86ebac2a 100644 --- a/Fw/Obj/ObjBase.cpp +++ b/Fw/Obj/ObjBase.cpp @@ -1,8 +1,7 @@ #include #include -#include -#include #include +#include namespace Fw { @@ -48,8 +47,8 @@ namespace Fw { void ObjBase::toString(char* str, NATIVE_INT_TYPE size) { FW_ASSERT(size > 0); FW_ASSERT(str != nullptr); - PlatformIntType status = snprintf(str, static_cast(size), "Obj: %s", this->m_objName.toChar()); - if (status < 0) { + Fw::FormatStatus formatStatus = Fw::ExternalString(str, static_cast(size)).format("Obj: %s", this->m_objName.toChar()); + if (formatStatus != Fw::FormatStatus::SUCCESS) { str[0] = 0; } } diff --git a/Fw/Port/InputPortBase.cpp b/Fw/Port/InputPortBase.cpp index f60af31df6..47ae6b4ee3 100644 --- a/Fw/Port/InputPortBase.cpp +++ b/Fw/Port/InputPortBase.cpp @@ -26,18 +26,8 @@ namespace Fw { } #if FW_OBJECT_TO_STRING == 1 - void InputPortBase::toString(char* buffer, NATIVE_INT_TYPE size) { -#if FW_OBJECT_NAMES == 1 - FW_ASSERT(size > 0); - FW_ASSERT(buffer != nullptr); - PlatformIntType status = snprintf(buffer, static_cast(size), "InputPort: %s->%s", this->m_objName.toChar(), - this->isConnected() ? this->m_connObj->getObjName() : "None"); - if (status < 0) { - buffer[0] = 0; - } -#else - (void)snprintf(buffer,size,"%s","Unnamed Input port"); -#endif + const char* InputPortBase::getToStringFormatString() { + return "Input Port: %s %s->(%s)"; } #endif diff --git a/Fw/Port/InputPortBase.hpp b/Fw/Port/InputPortBase.hpp index 5499c7c610..f6fe3f71c2 100644 --- a/Fw/Port/InputPortBase.hpp +++ b/Fw/Port/InputPortBase.hpp @@ -21,12 +21,12 @@ namespace Fw { InputPortBase(); // Constructor virtual ~InputPortBase(); // Destructor - virtual void init(); + void init() override; PassiveComponentBase* m_comp; // !< pointer to containing component NATIVE_INT_TYPE m_portNum; // !< port number in containing object #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); + const char* getToStringFormatString() override; //!< Get format string for toString call #endif private: diff --git a/Fw/Port/InputSerializePort.cpp b/Fw/Port/InputSerializePort.cpp index 2bb7c1a61a..e518b97706 100644 --- a/Fw/Port/InputSerializePort.cpp +++ b/Fw/Port/InputSerializePort.cpp @@ -37,16 +37,8 @@ namespace Fw { } #if FW_OBJECT_TO_STRING == 1 - void InputSerializePort::toString(char* buffer, NATIVE_INT_TYPE size) { -#if FW_OBJECT_NAMES == 1 - FW_ASSERT(size > 0); - if (snprintf(buffer, static_cast(size), "Input Serial Port: %s %s->(%s)", this->m_objName.toChar(), this->isConnected() ? "C" : "NC", - this->isConnected() ? this->m_connObj->getObjName() : "None") < 0) { - buffer[0] = 0; - } -#else - (void)snprintf(buffer,size,"%s","InputSerializePort"); -#endif + const char* InputSerializePort::getToStringFormatString() { + return "Input Serial Port: %s %s->(%s)"; } #endif diff --git a/Fw/Port/InputSerializePort.hpp b/Fw/Port/InputSerializePort.hpp index de5096a532..887dbd77cd 100644 --- a/Fw/Port/InputSerializePort.hpp +++ b/Fw/Port/InputSerializePort.hpp @@ -14,9 +14,9 @@ namespace Fw { InputSerializePort(); virtual ~InputSerializePort(); - void init(); + void init() override; - SerializeStatus invokeSerial(SerializeBufferBase &buffer); // !< invoke the port with a serialized version of the call + SerializeStatus invokeSerial(SerializeBufferBase &buffer) override; // !< invoke the port with a serialized version of the call typedef void (*CompFuncPtr)(Fw::PassiveComponentBase* callComp, NATIVE_INT_TYPE portNum, SerializeBufferBase &arg); //!< port callback definition void addCallComp(Fw::PassiveComponentBase* callComp, CompFuncPtr funcPtr); //!< call to register a component @@ -24,7 +24,7 @@ namespace Fw { protected: #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); + const char* getToStringFormatString() override; //!< Get format string for toString call #endif private: diff --git a/Fw/Port/OutputPortBase.cpp b/Fw/Port/OutputPortBase.cpp index 009a51c6b4..89d2789b00 100644 --- a/Fw/Port/OutputPortBase.cpp +++ b/Fw/Port/OutputPortBase.cpp @@ -35,17 +35,8 @@ namespace Fw { #endif #if FW_OBJECT_TO_STRING == 1 - void OutputPortBase::toString(char* buffer, NATIVE_INT_TYPE size) { -#if FW_OBJECT_NAMES == 1 - FW_ASSERT(size > 0); - if (snprintf(buffer, static_cast(size), "OutputPort: %s %s->(%s)", this->m_objName.toChar(), this->isConnected() ? "C" : "NC", - this->isConnected() ? this->m_connObj->getObjName() : "None") < 0) { - buffer[0] = 0; - } -#else - (void)snprintf(buffer,size,"%s","OutputPort"); -#endif - + const char* OutputPortBase::getToStringFormatString() { + return "Output Port: %s %s->(%s)"; } #endif diff --git a/Fw/Port/OutputPortBase.hpp b/Fw/Port/OutputPortBase.hpp index c64e11fc14..108f5dcfa4 100644 --- a/Fw/Port/OutputPortBase.hpp +++ b/Fw/Port/OutputPortBase.hpp @@ -19,10 +19,10 @@ namespace Fw { OutputPortBase(); // constructor virtual ~OutputPortBase(); // destructor - virtual void init(); + void init() override; #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); + const char* getToStringFormatString() override; //!< Get format string for toString call #endif #if FW_PORT_SERIALIZATION == 1 diff --git a/Fw/Port/OutputSerializePort.cpp b/Fw/Port/OutputSerializePort.cpp index a6c61b54df..ab2d6801b7 100644 --- a/Fw/Port/OutputSerializePort.cpp +++ b/Fw/Port/OutputSerializePort.cpp @@ -19,16 +19,8 @@ namespace Fw { } #if FW_OBJECT_TO_STRING == 1 - void OutputSerializePort::toString(char* buffer, NATIVE_INT_TYPE size) { -#if FW_OBJECT_NAMES == 1 - FW_ASSERT(size > 0); - if (snprintf(buffer, static_cast(size), "Output Serial Port: %s %s->(%s)", this->m_objName.toChar(), this->isConnected() ? "C" : "NC", - this->isConnected() ? this->m_connObj->getObjName() : "None") < 0) { - buffer[0] = 0; - } -#else - (void)snprintf(buffer,size,"%s","OutputSerializePort"); -#endif + const char* OutputSerializePort::getToStringFormatString() { + return "Output Serial Port: %s %s->(%s)"; } #endif diff --git a/Fw/Port/OutputSerializePort.hpp b/Fw/Port/OutputSerializePort.hpp index 5734cb7d65..f5b705980a 100644 --- a/Fw/Port/OutputSerializePort.hpp +++ b/Fw/Port/OutputSerializePort.hpp @@ -13,13 +13,11 @@ namespace Fw { public: OutputSerializePort(); virtual ~OutputSerializePort(); - virtual void init(); + void init() override; protected: - - #if FW_OBJECT_TO_STRING == 1 - virtual void toString(char* str, NATIVE_INT_TYPE size); + const char* getToStringFormatString() override; //!< Get format string for toString call #endif private: diff --git a/Fw/Port/PortBase.cpp b/Fw/Port/PortBase.cpp index 75e97704e0..10621269b9 100644 --- a/Fw/Port/PortBase.cpp +++ b/Fw/Port/PortBase.cpp @@ -3,6 +3,7 @@ #include #include #include "Fw/Types/Assert.hpp" +#include "Fw/Types/ExternalString.hpp" #if FW_PORT_TRACING void setConnTrace(bool trace) { @@ -75,17 +76,43 @@ namespace Fw { #endif // FW_PORT_TRACING -#if FW_OBJECT_NAMES == 1 #if FW_OBJECT_TO_STRING == 1 + const char* PortBase::getToStringFormatString() { + return "Port: %s %s->(%s)"; + } + void PortBase::toString(char* buffer, NATIVE_INT_TYPE size) { FW_ASSERT(size > 0); - if (snprintf(buffer, static_cast(size), "Port: %s %s->(%s)", this->m_objName.toChar(), this->m_connObj ? "C" : "NC", - this->m_connObj ? this->m_connObj->getObjName() : "None") < 0) { + // Get the port-custom format string + const char* formatString = this->getToStringFormatString(); + // Determine this port object name (or use "UNKNOWN") + const char* object_name = +#if FW_OBJECT_NAMES == 1 + this->m_objName.toChar(); +#else + "UNKNOWN"; +#endif + // Get the C/NC for connected or not + const char* this_is_connected = this->isConnected() ? "C" : "NC"; + + // Get the name of the connection object, "UNKNOWN" or "NONE" + const char* connected_to = this->isConnected() ? +#if FW_OBJECT_NAMES == 1 + this->m_connObj->getObjName() +#else + "UNKNOWN" +#endif + : "None"; + // Format the external string or use "" on error + if (Fw::ExternalString(buffer, static_cast(size)).format( + formatString, + object_name, + this_is_connected, + connected_to) != Fw::FormatStatus::SUCCESS) { buffer[0] = 0; } } #endif // FW_OBJECT_TO_STRING -#endif // FW_OBJECT_NAMES } diff --git a/Fw/Port/PortBase.hpp b/Fw/Port/PortBase.hpp index 0e81ddd56d..c9b8c09458 100644 --- a/Fw/Port/PortBase.hpp +++ b/Fw/Port/PortBase.hpp @@ -33,7 +33,9 @@ namespace Fw { Fw::ObjBase* m_connObj; // !< object port is connected to #if FW_OBJECT_TO_STRING - virtual void toString(char* str, NATIVE_INT_TYPE size); + virtual const char* getToStringFormatString(); //!< Get format string for toString call + + void toString(char* str, NATIVE_INT_TYPE size) override; //!< Unified port toString method #endif diff --git a/Fw/Types/Assert.cpp b/Fw/Types/Assert.cpp index b3565f6428..a05280f274 100644 --- a/Fw/Types/Assert.cpp +++ b/Fw/Types/Assert.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -12,7 +14,7 @@ namespace Fw { void defaultPrintAssert(const CHAR* msg) { - (void)fprintf(stderr, "%s\n", msg); + (void) fwrite(msg, sizeof(CHAR), static_cast(Fw::StringUtils::string_length(msg, FW_ASSERT_TEXT_SIZE)), stderr); } void defaultReportAssert(FILE_NAME_ARG file, @@ -26,36 +28,37 @@ void defaultReportAssert(FILE_NAME_ARG file, FwAssertArgType arg6, CHAR* destBuffer, NATIVE_INT_TYPE buffSize) { + Fw::ExternalString external(destBuffer, static_cast(buffSize)); switch (numArgs) { case 0: - (void)snprintf(destBuffer, static_cast(buffSize), fileIdFs, file, lineNo); + (void)external.format(fileIdFs, file, lineNo); break; case 1: - (void)snprintf(destBuffer, static_cast(buffSize), fileIdFs " %" PRI_FwAssertArgType, file, lineNo, arg1); + (void)external.format(fileIdFs " %" PRI_FwAssertArgType, file, lineNo, arg1); break; case 2: - (void)snprintf(destBuffer, static_cast(buffSize), fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, + (void)external.format(fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, lineNo, arg1, arg2); break; case 3: - (void)snprintf(destBuffer, static_cast(buffSize), + (void)external.format( fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, lineNo, arg1, arg2, arg3); break; case 4: - (void)snprintf(destBuffer, static_cast(buffSize), + (void)external.format( fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, lineNo, arg1, arg2, arg3, arg4); break; case 5: - (void)snprintf(destBuffer, static_cast(buffSize), + (void)external.format( fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, lineNo, arg1, arg2, arg3, arg4, arg5); break; case 6: - (void)snprintf(destBuffer, static_cast(buffSize), + (void)external.format( fileIdFs " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType " %" PRI_FwAssertArgType, file, lineNo, arg1, arg2, arg3, arg4, arg5, arg6); @@ -63,9 +66,6 @@ void defaultReportAssert(FILE_NAME_ARG file, default: // in an assert already, what can we do? break; } - - // null terminate - destBuffer[buffSize - 1] = 0; } void AssertHook::printAssert(const CHAR* msg) { diff --git a/Fw/Types/CMakeLists.txt b/Fw/Types/CMakeLists.txt index 6c3c54334f..27dce5ae95 100644 --- a/Fw/Types/CMakeLists.txt +++ b/Fw/Types/CMakeLists.txt @@ -23,6 +23,7 @@ set(MOD_DEPS Fw/Cfg ) register_fprime_module() +require_fprime_implementation("Fw_StringFormat") ### UTs ### set(UT_SOURCE_FILES "${CMAKE_CURRENT_LIST_DIR}/test/ut/ExternalSerializeBufferTest.cpp" @@ -35,3 +36,7 @@ register_fprime_ut() # Non-test directory add_fprime_subdirectory("${CMAKE_CURRENT_LIST_DIR}/GTest") + +# Create an empty interface library and use it to register an implementation +add_library(snprintf-format INTERFACE) +register_fprime_implementation(Fw_StringFormat snprintf-format "${CMAKE_CURRENT_LIST_DIR}/snprintf_format.cpp") diff --git a/Fw/Types/PolyType.cpp b/Fw/Types/PolyType.cpp index 30c04196bd..66d4e24fcf 100644 --- a/Fw/Types/PolyType.cpp +++ b/Fw/Types/PolyType.cpp @@ -1,7 +1,6 @@ #include #include -#include -#define __STDC_FORMAT_MACROS +#include namespace Fw { @@ -605,68 +604,66 @@ void PolyType::toString(StringBase& dest) const { } void PolyType::toString(StringBase& dest, bool append) const { - char valString[80]; + char format[21]; // U64 max fits into 20 decimal digits + 1 null terminator + Fw::ExternalString external(format, sizeof format); switch (this->m_dataType) { case TYPE_U8: - (void)snprintf(valString, sizeof(valString), "%" PRIu8 " ", this->m_val.u8Val); + (void)external.format("%" PRIu8 " ", this->m_val.u8Val); break; case TYPE_I8: - (void)snprintf(valString, sizeof(valString), "%" PRId8 " ", this->m_val.i8Val); + (void)external.format("%" PRId8 " ", this->m_val.i8Val); break; #if FW_HAS_16_BIT case TYPE_U16: - (void)snprintf(valString, sizeof(valString), "%" PRIu16 " ", this->m_val.u16Val); + (void)external.format("%" PRIu16 " ", this->m_val.u16Val); break; case TYPE_I16: - (void)snprintf(valString, sizeof(valString), "%" PRId16 " ", this->m_val.i16Val); + (void)external.format("%" PRId16 " ", this->m_val.i16Val); break; #endif #if FW_HAS_32_BIT case TYPE_U32: - (void)snprintf(valString, sizeof(valString), "%" PRIu32 " ", this->m_val.u32Val); + (void)external.format("%" PRIu32 " ", this->m_val.u32Val); break; case TYPE_I32: - (void)snprintf(valString, sizeof(valString), "%" PRId32 " ", this->m_val.i32Val); + (void)external.format("%" PRId32 " ", this->m_val.i32Val); break; #endif #if FW_HAS_64_BIT case TYPE_U64: - (void)snprintf(valString, sizeof(valString), "%" PRIu64 " ", this->m_val.u64Val); + (void)external.format("%" PRIu64 " ", this->m_val.u64Val); break; case TYPE_I64: - (void)snprintf(valString, sizeof(valString), "%" PRId64 " ", this->m_val.i64Val); + (void)external.format("%" PRId64 " ", this->m_val.i64Val); break; #endif #if FW_HAS_F64 case TYPE_F64: - (void)snprintf(valString, sizeof(valString), "%g ", this->m_val.f64Val); + (void)external.format("%g ", this->m_val.f64Val); break; case TYPE_F32: - (void)snprintf(valString, sizeof(valString), "%g ", static_cast(this->m_val.f32Val)); + (void)external.format("%g ", static_cast(this->m_val.f32Val)); break; #else case TYPE_F32: - (void)snprintf(valString, sizeof(valString), "%g ", this->m_val.f32Val); + (void)external.format("%g ", this->m_val.f32Val); break; #endif case TYPE_BOOL: - (void)snprintf(valString, sizeof(valString), "%s ", this->m_val.boolVal ? "T" : "F"); + (void)external.format("%s ", this->m_val.boolVal ? "T" : "F"); break; case TYPE_PTR: - (void)snprintf(valString, sizeof(valString), "%p ", this->m_val.ptrVal); + (void)external.format("%p ", this->m_val.ptrVal); break; default: - (void)snprintf(valString, sizeof(valString), "%s ", "NT"); + (void)external.format("%s ", "NT"); break; } - // NULL terminate - valString[sizeof(valString) - 1] = 0; - if (append) { - dest += valString; + dest += external; } else { - dest = valString; + dest = external; } } diff --git a/Fw/Types/StringBase.cpp b/Fw/Types/StringBase.cpp index b86effa3e9..81d222743b 100644 --- a/Fw/Types/StringBase.cpp +++ b/Fw/Types/StringBase.cpp @@ -14,7 +14,6 @@ #include #include #include -#include #include namespace Fw { @@ -65,33 +64,10 @@ FormatStatus StringBase::vformat(const CHAR* formatString, va_list args) { CHAR* us = const_cast(this->toChar()); SizeType cap = this->getCapacity(); FW_ASSERT(us != nullptr); - - // Check format string - if (formatString == nullptr) { - return FormatStatus::INVALID_FORMAT_STRING; - } - FwSizeType total_needed_size = 0; -#if FW_USE_PRINTF_FAMILY_FUNCTIONS_IN_STRING_FORMATTING - // Check that the API size type fits in fprime size type - static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), - "Range of PlatformIntType does not fit within range of FwSizeType"); - PlatformIntType total_needed_size_api = vsnprintf(us, cap, formatString, args); - // Check for error return, or a type overflow - if (total_needed_size_api < 0) { - return FormatStatus::OTHER_ERROR; - } - total_needed_size = static_cast(total_needed_size_api); -#else - total_needed_size = StringUtils::string_length(format_string, cap); - *this = formatString; -#endif - // Force null terminate - us[cap - 1] = 0; - // Check for overflow - if (total_needed_size >= cap) { - return FormatStatus::OVERFLOWED; - } - return FormatStatus::SUCCESS; + // Needed until SizeType an FwSizeType are the same + static_assert(std::numeric_limits::max() >= std::numeric_limits::max(), + "String size type must fit into FwSizeType"); + return Fw::stringFormat(us, static_cast(cap), formatString, args); } bool StringBase::operator!=(const StringBase& other) const { diff --git a/Fw/Types/StringBase.hpp b/Fw/Types/StringBase.hpp index 82df7def28..6290386afe 100644 --- a/Fw/Types/StringBase.hpp +++ b/Fw/Types/StringBase.hpp @@ -12,7 +12,7 @@ #ifndef FW_STRING_BASE_HPP #define FW_STRING_BASE_HPP - +#include #include #include #include @@ -22,14 +22,6 @@ namespace Fw { -//! \brief status of format -enum class FormatStatus { - SUCCESS, //!< Format worked - OVERFLOWED, //!< Format overflowed - INVALID_FORMAT_STRING, //!< Format provided invalid format string - OTHER_ERROR //!< An error was returned from an underlying call -}; - class StringBase : public Serializable { public: using SizeType = NATIVE_UINT_TYPE; diff --git a/Fw/Types/format.hpp b/Fw/Types/format.hpp new file mode 100644 index 0000000000..407227087e --- /dev/null +++ b/Fw/Types/format.hpp @@ -0,0 +1,46 @@ +// ====================================================================== +// \title format.hpp +// \author mstarch +// \brief hpp file for c-string format function +// +// \copyright +// Copyright (C) 2025 California Institute of Technology. +// ALL RIGHTS RESERVED. United States Government Sponsorship +// acknowledged. +// ====================================================================== +#ifndef FW_TYPES_FORMAT_HPP_ +#define FW_TYPES_FORMAT_HPP_ +#include +#include +namespace Fw { + +//! \brief status of string format calls +enum class FormatStatus { + SUCCESS, //!< Format worked + OVERFLOWED, //!< Format overflowed + INVALID_FORMAT_STRING, //!< Format provided invalid format string + SIZE_OVERFLOW, //!< FwSizeType overflowed the range of size_t + OTHER_ERROR //!< An error was returned from an underlying call +}; + +//! \brief format a c-string +//! +//! Format a string using printf family formatting semantics. Destination will be filled with the formatted string up to +//! maximumSize - 1. This function will always terminate the string with a \0. +//! +//! This function can return several error codes: +//! OVERFLOWED: the complete string did not fit in the buffer with an appended null-terminator +//! INVALID_FORMAT_STRING: the format string was null +//! OTHER_ERROR: another error occurred in an underlying function call +//! Otherwise SUCCESS is returned. destination may be modified even in the case of an error. +//! +//! \param destination: destination to fill with the formatted string. +//! \param maximumSize: size of the buffer represented by destination +//! \param formatString: format string to fill +//! \param args: variable arguments. +//! \return: SUCCESS on successful formatting, OVERFLOWED on overflow, and something else on any error +FormatStatus stringFormat(char* destination, const FwSizeType maximumSize, const char* formatString, va_list args); +} +#endif // FW_TYPES_FORMAT_HPP_ + + diff --git a/Fw/Types/snprintf_format.cpp b/Fw/Types/snprintf_format.cpp new file mode 100644 index 0000000000..8681bce39c --- /dev/null +++ b/Fw/Types/snprintf_format.cpp @@ -0,0 +1,34 @@ +// ====================================================================== +// \title format.cpp +// \author mstarch +// \brief cpp file for c-string format function as a implementation using snprintf +// +// \copyright +// Copyright (C) 2025 California Institute of Technology. +// ALL RIGHTS RESERVED. United States Government Sponsorship +// acknowledged. +// ====================================================================== +#include +#include +#include + +Fw::FormatStatus Fw::stringFormat(char* destination, const FwSizeType maximumSize, const char* formatString, va_list args) { + Fw::FormatStatus formatStatus = Fw::FormatStatus::SUCCESS; + // Check format string + if (formatString == nullptr) { + formatStatus = Fw::FormatStatus::INVALID_FORMAT_STRING; + } + // Must allow the compiler to choose the correct type for comparison + else if (maximumSize > std::numeric_limits::max()) { + formatStatus = Fw::FormatStatus::SIZE_OVERFLOW; + } else { + PlatformIntType needed_size = vsnprintf(destination, static_cast(maximumSize), formatString, args); + destination[maximumSize - 1] = 0; // Force null-termination + if (needed_size < 0) { + formatStatus = Fw::FormatStatus::OTHER_ERROR; + } else if (static_cast(needed_size) >= maximumSize) { + formatStatus = Fw::FormatStatus::OVERFLOWED; + } + } + return formatStatus; +} \ No newline at end of file diff --git a/Os/Posix/CMakeLists.txt b/Os/Posix/CMakeLists.txt index a385d92b4c..9d6e594310 100644 --- a/Os/Posix/CMakeLists.txt +++ b/Os/Posix/CMakeLists.txt @@ -24,6 +24,7 @@ register_fprime_module(Os_Posix_Shared) # Set up Posix implementations register_os_implementation("File;FileSystem;Directory" Posix Os_Posix_Shared) register_os_implementation("Console" Posix) + register_os_implementation("Task" Posix Os_Posix_Shared Fw_Time) register_os_implementation("Mutex;ConditionVariable" Posix Os_Posix_Shared) register_os_implementation("RawTime" Posix Os_Posix_Shared) diff --git a/Os/Stub/test/ut/StubFileTests.cpp b/Os/Stub/test/ut/StubFileTests.cpp index 5836f3a74e..fee608c145 100644 --- a/Os/Stub/test/ut/StubFileTests.cpp +++ b/Os/Stub/test/ut/StubFileTests.cpp @@ -3,6 +3,7 @@ // \brief tests using stub implementation for Os::File interface testing // ====================================================================== #include +#include "Os/Os.hpp" #include "Os/File.hpp" #include "Os/Stub/test/File.hpp" #include "Os/test/ut/file/CommonTests.hpp" @@ -191,6 +192,7 @@ TEST_F(Interface, Write) { } int main(int argc, char **argv) { + Os::init(); ::testing::InitGoogleTest(&argc, argv); STest::Random::seed(); return RUN_ALL_TESTS(); diff --git a/Os/Stub/test/ut/StubQueueTests.cpp b/Os/Stub/test/ut/StubQueueTests.cpp index 556c2294b7..63aa7b7dc2 100644 --- a/Os/Stub/test/ut/StubQueueTests.cpp +++ b/Os/Stub/test/ut/StubQueueTests.cpp @@ -7,6 +7,7 @@ #include "Os/Queue.hpp" #include "Os/Stub/test/Queue.hpp" #include "STest/Random/Random.hpp" +#include "Os/Os.hpp" void resetInjections() { @@ -235,6 +236,7 @@ TEST(Interface, QueueHandle) { } int main(int argc, char** argv) { + Os::init(); ::testing::InitGoogleTest(&argc, argv); STest::Random::seed(); return RUN_ALL_TESTS(); diff --git a/Os/Stub/test/ut/StubTaskTests.cpp b/Os/Stub/test/ut/StubTaskTests.cpp index 75affe6b4b..ecde3002a7 100644 --- a/Os/Stub/test/ut/StubTaskTests.cpp +++ b/Os/Stub/test/ut/StubTaskTests.cpp @@ -7,6 +7,7 @@ #include "Os/Stub/test/Task.hpp" #include "Os/test/ut/task/CommonTests.hpp" #include "Os/test/ut/task/RulesHeaders.hpp" +#include "Os/Os.hpp" using namespace Os::Stub::Task::Test; @@ -137,6 +138,7 @@ TEST_F(Interface, RegistryRemove) { } int main(int argc, char **argv) { + Os::init(); ::testing::InitGoogleTest(&argc, argv); STest::Random::seed(); return RUN_ALL_TESTS(); diff --git a/cmake/API.cmake b/cmake/API.cmake index 64a4f626cc..b090c1fbca 100644 --- a/cmake/API.cmake +++ b/cmake/API.cmake @@ -668,7 +668,8 @@ endfunction() # # Designates that the given implementor is the selected implementor for the needed implementation. Platforms must call # this function once for each defined IMPLEMENTATION. An executable/deployment/unit-test may call this function to set -# a specific implementor for any needed implementation +# a specific implementor for any needed implementation. FRAMEWORK_DEFAULT may be supplied to indicate a default choice +# set by the framework, which can be overridden by the platform and module selections. # # **IMPLEMENTATION:** implementation module name that is implemented by IMPLEMENTOR # **IMPLEMENTOR:** implementor of IMPLEMENTATION diff --git a/cmake/FPrime.cmake b/cmake/FPrime.cmake index 8e09fcd27a..dc467f550c 100644 --- a/cmake/FPrime.cmake +++ b/cmake/FPrime.cmake @@ -170,6 +170,7 @@ endmacro(fprime_initialize_build_system) # registered. #### function(fprime_setup_included_code) + choose_fprime_implementation(Fw_StringFormat snprintf-format FRAMEWORK_DEFAULT) # Default choice is snprintf # Must be done before code is registered but after custom target registration setup_global_targets() # For BUILD_TESTING builds then set up libraries that support testing diff --git a/cmake/implementation.cmake b/cmake/implementation.cmake index bac00d0a77..4e0e93699e 100644 --- a/cmake/implementation.cmake +++ b/cmake/implementation.cmake @@ -80,11 +80,17 @@ endfunction() # MODULE: module to setup implementation choices for ##### function(setup_executable_implementation IMPLEMENTATION MODULE) - # Get the chosen implementor and fallback to the platform choice + # Get the chosen implementor for the module get_property(IMPLEMENTOR GLOBAL PROPERTY "${MODULE}_${IMPLEMENTATION}") + # Fallback to the platform in the case that the module does not specify if (NOT IMPLEMENTOR) get_property(IMPLEMENTOR GLOBAL PROPERTY "${FPRIME_PLATFORM}_${IMPLEMENTATION}") endif() + # Fallback to the FRAMEWORK_DEFAULT in the case that the platform does not specify + if (NOT IMPLEMENTOR) + get_property(IMPLEMENTOR GLOBAL PROPERTY "FRAMEWORK_DEFAULT_${IMPLEMENTATION}") + endif() + # Handle a failure to choose anything if (NOT IMPLEMENTOR) get_property(LOCAL_IMPLEMENTATIONS GLOBAL PROPERTY "${IMPLEMENTATION}_IMPLEMENTORS")