diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 142d3adc..afc59899 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -19,14 +19,14 @@ jobs: fail-fast: false matrix: include: - # ── Windows x64 native ───────────────────────────────────────────── + # ── Windows x64 native ────────────────────────────────────────────── - os: windows-2022 artifact-name: windows-x64-binaries firebird-version: '5.0.3' - os: windows-2022 firebird-branch: master - # ── Windows x86 native (WoW64) ───────────────────────────────────── + # ── Windows x86 native (WoW64) ────────────────────────────────────── - os: windows-2022 artifact-name: windows-x86-binaries arch: Win32 @@ -35,26 +35,34 @@ jobs: arch: Win32 firebird-branch: master - # ── Windows ARM64 native ─────────────────────────────────────────── + # ── Windows ARM64 native ───────────────────────────────────────────── # Official Firebird releases have no win-arm64 binaries; snapshots do. - os: windows-11-arm artifact-name: windows-arm64-binaries firebird-branch: master - # ── Linux x64 native ─────────────────────────────────────────────── + # ── Linux x64 native ───────────────────────────────────────────────── - os: ubuntu-22.04 artifact-name: linux-x64-binaries firebird-version: '5.0.3' - os: ubuntu-22.04 firebird-branch: master - # ── Linux ARM64 native ───────────────────────────────────────────── + # ── Linux ARM64 native ────────────────────────────────────────────── - os: ubuntu-22.04-arm artifact-name: linux-arm64-binaries firebird-version: '5.0.3' - os: ubuntu-22.04-arm firebird-branch: master + # ── Linux x64 sanitizers (Debug, Firebird 5.0.3) ───────────────────── + - os: ubuntu-22.04 + sanitizer: Asan + firebird-version: '5.0.3' + - os: ubuntu-22.04 + sanitizer: Valgrind + firebird-version: '5.0.3' + runs-on: ${{ matrix.os }} steps: @@ -70,6 +78,10 @@ jobs: if: runner.os == 'Linux' run: sudo apt-get update && sudo apt-get install -y unixodbc unixodbc-dev + - name: Install Valgrind + if: matrix.sanitizer == 'Valgrind' + run: sudo apt-get install -y valgrind + - name: Build, install and test shell: pwsh env: @@ -79,7 +91,10 @@ jobs: run: | $archArgs = @{} if ('${{ matrix.arch }}') { $archArgs['Architecture'] = '${{ matrix.arch }}' } - Invoke-Build test -Configuration Release @archArgs -File ./firebird-odbc-driver.build.ps1 + $sanitizerArgs = @{} + if ('${{ matrix.sanitizer }}') { $sanitizerArgs['Sanitizer'] = '${{ matrix.sanitizer }}' } + $config = if ('${{ matrix.sanitizer }}') { 'Debug' } else { 'Release' } + Invoke-Build test -Configuration $config @archArgs @sanitizerArgs -File ./firebird-odbc-driver.build.ps1 - name: Upload artifacts (Windows) if: runner.os == 'Windows' && matrix.artifact-name diff --git a/CMakeLists.txt b/CMakeLists.txt index 577e318d..8798d9e5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,41 @@ set(CMAKE_C_STANDARD_REQUIRED ON) option(BUILD_SHARED_LIBS "Build shared libraries" ON) option(BUILD_TESTING "Build tests" ON) +# --------------------------------------------------------------------------- +# Sanitizer options +# --------------------------------------------------------------------------- +option(BUILD_WITH_ASAN "Enable AddressSanitizer (-fsanitize=address)" OFF) +option(BUILD_WITH_VALGRIND "Enable Valgrind memcheck via CTest" OFF) + +if(BUILD_WITH_ASAN AND BUILD_WITH_VALGRIND) + message(FATAL_ERROR "BUILD_WITH_ASAN and BUILD_WITH_VALGRIND are mutually exclusive. " + "ASAN instruments the binary at compile time; Valgrind instruments at runtime. " + "They must not be combined.") +endif() + +if(MSVC AND (BUILD_WITH_ASAN OR BUILD_WITH_VALGRIND)) + message(WARNING "Sanitizers are not yet supported on MSVC. " + "BUILD_WITH_ASAN / BUILD_WITH_VALGRIND will be ignored.") +elseif(NOT MSVC) + if(BUILD_WITH_ASAN) + message(STATUS "AddressSanitizer: ENABLED") + add_compile_options(-fsanitize=address -fno-omit-frame-pointer) + add_link_options(-fsanitize=address) + endif() + + if(BUILD_WITH_VALGRIND) + find_program(VALGRIND_COMMAND valgrind) + if(NOT VALGRIND_COMMAND) + message(FATAL_ERROR "Valgrind not found but BUILD_WITH_VALGRIND is ON. " + "Install it with: sudo apt-get install valgrind") + endif() + message(STATUS "Valgrind memcheck: ENABLED (${VALGRIND_COMMAND})") + set(MEMORYCHECK_COMMAND ${VALGRIND_COMMAND}) + set(MEMORYCHECK_COMMAND_OPTIONS + "--leak-check=full --error-exitcode=1 --suppressions=${CMAKE_SOURCE_DIR}/valgrind.supp") + endif() +endif() + # --------------------------------------------------------------------------- # CPU architecture detection (from PR #248) # --------------------------------------------------------------------------- @@ -95,12 +130,19 @@ else() # Compiler optimization flags (from PR #248 / old makefile.linux) add_compile_options( - "$<$:-O0;-g3;-D_DEBUG;-DDEBUG;-DLOGGING;-fexceptions>" + "$<$:-O0;-g3;-D_DEBUG;-fexceptions>" "$<$:-O3;-DNDEBUG;-ftree-loop-vectorize>" "$<$:-O2;-g;-DNDEBUG>" "$<$:-Os;-DNDEBUG>" ) + # Debug macros: DEBUG and LOGGING are for Debug builds only; + # sanitizer builds strip them for cleaner output (less noise in reports). + if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND NOT BUILD_WITH_ASAN AND NOT BUILD_WITH_VALGRIND) + add_definitions(-DDEBUG) + add_definitions(-DLOGGING) + endif() + # SSE4.1 for x86/x86_64 (from PR #248) if(FBODBC_ARCH STREQUAL "x86" OR FBODBC_ARCH STREQUAL "i686" OR FBODBC_ARCH STREQUAL "x86_64" OR FBODBC_ARCH STREQUAL "AMD64") @@ -246,12 +288,6 @@ else() ) endif() -# Debug-specific definitions (matching .vcxproj: DEBUG;LOGGING for Debug configs) -target_compile_definitions(OdbcFb PRIVATE - $<$:DEBUG> - $<$:LOGGING> -) - # --------------------------------------------------------------------------- # Testing # --------------------------------------------------------------------------- diff --git a/CMakePresets.json b/CMakePresets.json index fa3fc630..9ef7c3e1 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -30,6 +30,22 @@ "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } + }, + { + "name": "asan", + "displayName": "Debug + AddressSanitizer", + "inherits": "debug", + "cacheVariables": { + "BUILD_WITH_ASAN": "ON" + } + }, + { + "name": "valgrind", + "displayName": "Debug + Valgrind", + "inherits": "debug", + "cacheVariables": { + "BUILD_WITH_VALGRIND": "ON" + } } ], "buildPresets": [ @@ -42,6 +58,16 @@ "name": "debug", "configurePreset": "default", "configuration": "Debug" + }, + { + "name": "asan", + "configurePreset": "asan", + "configuration": "Debug" + }, + { + "name": "valgrind", + "configurePreset": "valgrind", + "configuration": "Debug" } ], "testPresets": [ @@ -51,6 +77,26 @@ "output": { "outputOnFailure": true } + }, + { + "name": "asan", + "configurePreset": "asan", + "output": { + "outputOnFailure": true + }, + "environment": { + "ASAN_OPTIONS": "detect_leaks=1:halt_on_error=1:print_stats=1" + } + }, + { + "name": "valgrind", + "configurePreset": "valgrind", + "output": { + "outputOnFailure": true + }, + "execution": { + "timeout": 600 + } } ] } diff --git a/MainUnicode.cpp b/MainUnicode.cpp index 1d3c8486..7c5b674e 100644 --- a/MainUnicode.cpp +++ b/MainUnicode.cpp @@ -85,7 +85,7 @@ class ConvertingString if ( length == SQL_NTS ) lengthString = 0; else if ( retCountOfBytes ) - lengthString = length / sizeof(wchar_t); + lengthString = length / sizeof(SQLWCHAR); else lengthString = length; } diff --git a/cmake/GetVersionFromGit.cmake b/cmake/GetVersionFromGit.cmake index b2adaa26..9c8e3421 100644 --- a/cmake/GetVersionFromGit.cmake +++ b/cmake/GetVersionFromGit.cmake @@ -22,11 +22,15 @@ if(NOT GIT_FOUND) return() endif() -# Try to find the latest semver tag matching v.. -# We use --match to only consider tags in vX.Y.Z format (not rc, temp, etc.) +# Find the latest semver tag matching v... +# Prefer a stable vX.Y.Z tag (--exclude "*-*" filters out prereleases like +# v3.5.0-rc1). If no stable tag exists yet, fall back to any matching tag; +# the parser regex below handles the optional - suffix. +# NOTE: --always is intentionally NOT used on the first call so that a +# "no stable tag" case fails with non-zero exit and triggers the fallback. execute_process( COMMAND ${GIT_EXECUTABLE} describe --tags --match "v[0-9]*.[0-9]*.[0-9]*" - --exclude "*-*" --long --always + --exclude "*-*" --long WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE @@ -35,10 +39,9 @@ execute_process( ) if(NOT GIT_DESCRIBE_RESULT EQUAL 0) - # No matching tag found at all, try without --exclude execute_process( COMMAND ${GIT_EXECUTABLE} describe --tags --match "v[0-9]*.[0-9]*.[0-9]*" - --long --always + --long WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_OUTPUT OUTPUT_STRIP_TRAILING_WHITESPACE diff --git a/firebird-odbc-driver.build.ps1 b/firebird-odbc-driver.build.ps1 index f205c6c0..2a43704a 100644 --- a/firebird-odbc-driver.build.ps1 +++ b/firebird-odbc-driver.build.ps1 @@ -19,7 +19,10 @@ param( [string]$Configuration = 'Debug', [ValidateSet('', 'Win32')] - [string]$Architecture = '' + [string]$Architecture = '', + + [ValidateSet('None', 'Asan', 'Valgrind')] + [string]$Sanitizer = 'None' ) # Detect OS @@ -66,6 +69,16 @@ task build { if ($IsWindowsOS -and $Architecture) { $cmakeArgs += @('-A', $Architecture) } + if ($Sanitizer -ne 'None') { + if ($IsWindowsOS) { + print Yellow "WARNING: Sanitizer=$Sanitizer is not supported on Windows. Building without sanitizer." + } else { + switch ($Sanitizer) { + 'Asan' { $cmakeArgs += '-DBUILD_WITH_ASAN=ON' } + 'Valgrind' { $cmakeArgs += '-DBUILD_WITH_VALGRIND=ON' } + } + } + } exec { cmake @cmakeArgs } if ($IsWindowsOS) { @@ -160,6 +173,13 @@ task test build, build-test-databases, install, { print Yellow 'WARNING: FIREBIRD_ODBC_CONNECTION environment variable is not set. Using built-in connection strings.' } + # Set sanitizer runtime options + if ($Sanitizer -eq 'Asan' -and -not $IsWindowsOS) { + $env:ASAN_OPTIONS = 'detect_leaks=1:halt_on_error=1:print_stats=1' + $env:LSAN_OPTIONS = "suppressions=$(Join-Path $BuildRoot 'lsan.supp')" + print Cyan "ASAN_OPTIONS=$env:ASAN_OPTIONS" + } + # Test suites that exercise charset/encoding-sensitive code paths. $charsetSensitiveSuites = @( 'WCharTest' diff --git a/lsan.supp b/lsan.supp new file mode 100644 index 00000000..7355db56 --- /dev/null +++ b/lsan.supp @@ -0,0 +1,12 @@ +# LeakSanitizer suppressions for Firebird ODBC Driver test suite +# +# This file suppresses known leak reports from third-party libraries +# (Firebird client, unixODBC, system allocators, etc.). +# Populate as false positives are discovered during ASAN/LSAN runs. +# +# Usage: +# export LSAN_OPTIONS="suppressions=lsan.supp" + +leak:libfbclient +leak:libodbc +leak:libodbcinst diff --git a/valgrind.supp b/valgrind.supp new file mode 100644 index 00000000..5b6252fd --- /dev/null +++ b/valgrind.supp @@ -0,0 +1,18 @@ +# Valgrind suppressions for Firebird ODBC Driver test suite +# +# This file suppresses known false positives from third-party libraries +# (Firebird client, unixODBC, system allocators, etc.). +# Populate as false positives are discovered during Valgrind runs. +# +# Usage: +# valgrind --leak-check=full --suppressions=valgrind.supp ./firebird_odbc_tests +# +# Or via CTest (when ENABLE_VALGRIND=ON): +# ctest -T memcheck + +{ + fbclient_global_init + Memcheck:Leak + ... + obj:*/libfbclient.so* +}