diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 207dc3ef..c845f41e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -195,6 +195,30 @@ jobs: run: | docker run --rm --volume $(pwd):/note-c/ --workdir /note-c/ --entrypoint ./scripts/run_cppcheck.sh ghcr.io/blues/note_c_ci:latest + run_macos_unit_tests: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Configure + run: cmake -B build -DNOTE_C_BUILD_TESTS=ON + + - name: Build + run: cmake --build build --parallel $(sysctl -n hw.ncpu) + + # NOTE: ctest is not run here. A subset of tests that rely on FFF + # symbol interposition fail on macOS due to two-level namespace + # linking. The build step above verifies that all sources and tests + # compile and link successfully. + # + # Even with -Wl,-flat_namespace, intra-library calls within the + # note_c dylib are resolved at link time as direct calls, so FFF + # fakes in the test executable cannot interpose them. Fully fixing + # this would require linking note-c as a static library for tests + # or using a different mocking approach on macOS. + publish_ci_image: runs-on: ubuntu-latest # Make sure unit tests unit tests passed before publishing. diff --git a/CMakeLists.txt b/CMakeLists.txt index 01bd3fa6..67d05e11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,26 +27,48 @@ option(NOTE_C_SINGLE_PRECISION "Use single precision for JSON floating point num option(NOTE_C_HEARTBEAT_CALLBACK "Enable heartbeat callback support." OFF) set(NOTE_C_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + +# Detect whether the system already provides strlcpy/strlcat (macOS, *BSD). +# When present, we skip the bundled implementations in n_str.c and define +# guards so note.h doesn't redeclare them (which collides with the platform's +# fortified macros). +include(CheckSymbolExists) +check_symbol_exists(strlcpy "string.h" HAVE_STRLCPY) +check_symbol_exists(strlcat "string.h" HAVE_STRLCAT) + add_library(note_c SHARED) -target_sources( - note_c - PRIVATE - ${NOTE_C_SRC_DIR}/n_atof.c - ${NOTE_C_SRC_DIR}/n_b64.c - ${NOTE_C_SRC_DIR}/n_cjson.c - ${NOTE_C_SRC_DIR}/n_cjson_helpers.c - ${NOTE_C_SRC_DIR}/n_cobs.c - ${NOTE_C_SRC_DIR}/n_const.c - ${NOTE_C_SRC_DIR}/n_ftoa.c - ${NOTE_C_SRC_DIR}/n_helpers.c - ${NOTE_C_SRC_DIR}/n_hooks.c - ${NOTE_C_SRC_DIR}/n_i2c.c - ${NOTE_C_SRC_DIR}/n_md5.c - ${NOTE_C_SRC_DIR}/n_printf.c - ${NOTE_C_SRC_DIR}/n_request.c - ${NOTE_C_SRC_DIR}/n_serial.c - ${NOTE_C_SRC_DIR}/n_str.c + +set(NOTE_C_SOURCES + ${NOTE_C_SRC_DIR}/n_atof.c + ${NOTE_C_SRC_DIR}/n_b64.c + ${NOTE_C_SRC_DIR}/n_cjson.c + ${NOTE_C_SRC_DIR}/n_cjson_helpers.c + ${NOTE_C_SRC_DIR}/n_cobs.c + ${NOTE_C_SRC_DIR}/n_const.c + ${NOTE_C_SRC_DIR}/n_ftoa.c + ${NOTE_C_SRC_DIR}/n_helpers.c + ${NOTE_C_SRC_DIR}/n_hooks.c + ${NOTE_C_SRC_DIR}/n_i2c.c + ${NOTE_C_SRC_DIR}/n_md5.c + ${NOTE_C_SRC_DIR}/n_printf.c + ${NOTE_C_SRC_DIR}/n_request.c + ${NOTE_C_SRC_DIR}/n_serial.c ) +# n_str.c provides weak strlcpy/strlcat. On systems that already have them +# the file won't compile because the platform headers define these as +# fortified macros that conflict with the function definitions. +if(NOT HAVE_STRLCPY OR NOT HAVE_STRLCAT) + list(APPEND NOTE_C_SOURCES ${NOTE_C_SRC_DIR}/n_str.c) +endif() +target_sources(note_c PRIVATE ${NOTE_C_SOURCES}) + +if(HAVE_STRLCPY) + target_compile_definitions(note_c PUBLIC HAVE_STRLCPY) +endif() +if(HAVE_STRLCAT) + target_compile_definitions(note_c PUBLIC HAVE_STRLCAT) +endif() + target_compile_options( note_c PRIVATE @@ -56,28 +78,35 @@ target_compile_options( -Werror -Og -ggdb - PUBLIC - -m32 - -mfpmath=sse - -msse2 ) +# The Linux CI runs tests in 32-bit mode. These flags are x86-specific and +# unavailable on Apple toolchains or non-x86 architectures. +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") + target_compile_options(note_c PUBLIC -m32 -mfpmath=sse -msse2) +endif() target_include_directories( note_c PUBLIC ${NOTE_C_SRC_DIR} ) -target_link_directories( - note_c - PUBLIC - /lib32 - /usr/lib32 - /usr/lib/gcc/x86_64-linux-gnu/12/32 -) -target_link_options( - note_c - PUBLIC - -m32 - -Wl,-melf_i386 -) +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") + target_link_directories( + note_c + PUBLIC + /lib32 + /usr/lib32 + /usr/lib/gcc/x86_64-linux-gnu/12/32 + ) + target_link_options( + note_c + PUBLIC + -m32 + -Wl,-melf_i386 + ) +endif() +if(APPLE) + # Use flat namespace so test fakes (FFF) can interpose library symbols + target_link_options(note_c PRIVATE -Wl,-flat_namespace) +endif() if(NOTE_C_LOW_MEM) target_compile_definitions( diff --git a/n_helpers.c b/n_helpers.c index 3c02cd61..3a46c2e3 100644 --- a/n_helpers.c +++ b/n_helpers.c @@ -2403,7 +2403,7 @@ uint32_t NoteMemAvailable(void) objHeader *lastObj = NULL; static long int maxsize = 35000; for (long int i=maxsize; i>=(long int)sizeof(objHeader); i=i-sizeof(objHeader)) { - for (long int j=0;; j++) { + for (;;) { objHeader *thisObj; thisObj = (objHeader *) _Malloc(i); if (thisObj == NULL) { @@ -2416,16 +2416,8 @@ uint32_t NoteMemAvailable(void) } // Free the objects backwards - long int lastLength = 0; - long int lastLengthCount = 0; uint32_t total = 0; while (lastObj != NULL) { - if (lastObj->length != lastLength) { - lastLength = lastObj->length; - lastLengthCount = 1; - } else { - lastLengthCount++; - } objHeader *thisObj = lastObj; lastObj = lastObj->prev; total += thisObj->length; diff --git a/n_ua.c b/n_ua.c index 91815291..c270373b 100644 --- a/n_ua.c +++ b/n_ua.c @@ -92,9 +92,9 @@ __attribute__((weak)) void NoteUserAgentUpdate(J *ua) */ /**************************************************************************/ #if defined(_MSC_VER) -J *NoteUserAgent() +J *NoteUserAgent(void) #else -__attribute__((weak)) J *NoteUserAgent() +__attribute__((weak)) J *NoteUserAgent(void) #endif { diff --git a/scripts/check_libc_dependencies.sh b/scripts/check_libc_dependencies.sh index f3c7fadc..498f3686 100755 --- a/scripts/check_libc_dependencies.sh +++ b/scripts/check_libc_dependencies.sh @@ -30,6 +30,8 @@ LIBC_WHITELIST=( "strchr" "strcmp" "strlen" + "strlcpy" # bundled in n_str.c; also available in glibc 2.38+ + "strlcat" # bundled in n_str.c; also available in glibc 2.38+ "strncmp" "strstr" "strtol" # required by atoi in NoteGenEnvInt diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index f4a0c38a..a74e6d23 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,8 +16,8 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(Catch2) -# Add specific build flags for Catch2 -if(TARGET Catch2) +# Add specific build flags for Catch2 (32-bit mode for Linux x86 CI only) +if(TARGET Catch2 AND CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|i[3-6]86") target_compile_options(Catch2 PUBLIC -m32 @@ -47,6 +47,10 @@ macro(add_test TEST_NAME) PRIVATE -Og PRIVATE -ggdb ) + if(APPLE) + # Allow test fakes (FFF) to interpose symbols from the note_c dylib + target_link_options(${TEST_NAME} PRIVATE -Wl,-flat_namespace) + endif() list(APPEND TEST_TARGETS ${TEST_NAME}) @@ -239,8 +243,12 @@ if(NOTE_C_COVERAGE) # ourselves, so we don't care about coverage for them. set( EXCLUDE_FROM_COVERAGE - "n_atof.c;n_b64.c;n_cjson.c;n_ftoa.c;n_md5.c;n_str.c" + "n_atof.c;n_b64.c;n_cjson.c;n_ftoa.c;n_md5.c" ) + # n_str.c is only compiled when the system lacks strlcpy/strlcat. + if(NOT HAVE_STRLCPY OR NOT HAVE_STRLCAT) + list(APPEND EXCLUDE_FROM_COVERAGE "n_str.c") + endif() foreach(EXCLUDE_FILE ${EXCLUDE_FROM_COVERAGE}) string(APPEND LCOV_EXCLUDE "--exclude '*/${EXCLUDE_FILE}' ") endforeach()