diff --git a/.github/tsan_suppressions.txt b/.github/tsan_suppressions.txt new file mode 100644 index 00000000..eebd4661 --- /dev/null +++ b/.github/tsan_suppressions.txt @@ -0,0 +1,17 @@ +# ThreadSanitizer suppressions for JsRuntimeHost +# +# These suppress data races internal to third-party libraries that we cannot fix. +# TSan on macOS (JavaScriptCore via Xcode) passes clean — these suppressions are +# only needed for the Ubuntu JSC build (libjavascriptcoregtk). + +# JavaScriptCore internal races in libjavascriptcoregtk. +# Races manifest in TSan interceptors (free/malloc/memcpy/close) called from +# JSC's JIT worker threads. function-name suppressions are needed because the +# top frame is a libc interceptor attributed to UnitTests, not libjavascriptcoregtk. +called_from_lib:libjavascriptcoregtk +race:free +race:close +race:memcpy + +# JSC signal handler that doesn't save/restore errno +signal:libjavascriptcoregtk diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 6e76da7c..f8433225 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -15,6 +15,10 @@ on: required: false type: boolean default: false + enable-thread-sanitizer: + required: false + type: boolean + default: false jobs: build: @@ -36,6 +40,7 @@ jobs: cmake -B Build/ubuntu -G Ninja \ -D CMAKE_BUILD_TYPE=RelWithDebInfo \ -D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} \ + -D ENABLE_THREAD_SANITIZER=${{ inputs.enable-thread-sanitizer && 'ON' || 'OFF' }} \ -D CMAKE_C_COMPILER=${{ inputs.cc }} \ -D CMAKE_CXX_COMPILER=${{ inputs.cxx }} @@ -45,3 +50,13 @@ jobs: - name: Run Tests working-directory: Build/ubuntu/Tests/UnitTests run: ./UnitTests + env: + TSAN_OPTIONS: ${{ inputs.enable-thread-sanitizer && format('suppressions={0}/.github/tsan_suppressions.txt', github.workspace) || '' }} + # JSC's concurrent GC on Linux uses SIGUSR1 + sem_wait to suspend mutator + # threads at safepoints. TSan's signal interception delays SIGUSR1 delivery + # indefinitely, deadlocking the Collector Thread's sem_wait. Disabling the + # concurrent collector removes the dedicated Collector Thread, so GC runs + # on the mutator without cross-thread signals. macOS JSC uses Mach + # thread_suspend() and is unaffected. + JSC_useConcurrentGC: ${{ inputs.enable-thread-sanitizer && '0' || '' }} + diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index 8e3c4817..fe5879db 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -14,6 +14,10 @@ on: required: false type: boolean default: false + enable-thread-sanitizer: + required: false + type: boolean + default: false jobs: build: @@ -28,7 +32,8 @@ jobs: - name: Configure CMake run: | cmake -B Build/macOS -G Xcode \ - -D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} + -D ENABLE_SANITIZERS=${{ inputs.enable-sanitizers && 'ON' || 'OFF' }} \ + -D ENABLE_THREAD_SANITIZER=${{ inputs.enable-thread-sanitizer && 'ON' || 'OFF' }} - name: Build run: cmake --build Build/macOS --target UnitTests --config RelWithDebInfo @@ -36,3 +41,6 @@ jobs: - name: Run Tests working-directory: Build/macOS/Tests/UnitTests/RelWithDebInfo run: ./UnitTests + env: + TSAN_OPTIONS: ${{ inputs.enable-thread-sanitizer && format('suppressions={0}/.github/tsan_suppressions.txt', github.workspace) || '' }} + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 390fd614..b183b5d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -83,6 +83,14 @@ jobs: runs-on: macos-latest enable-sanitizers: true + macOS_Xcode164_ThreadSanitizer: + uses: ./.github/workflows/build-macos.yml + with: + xcode-version: '16.4' + runs-on: macos-latest + enable-thread-sanitizer: true + + # ── iOS ─────────────────────────────────────────────────────── iOS_Xcode164: uses: ./.github/workflows/build-ios.yml @@ -114,3 +122,10 @@ jobs: cc: clang cxx: clang++ enable-sanitizers: true + + Ubuntu_ThreadSanitizer_clang: + uses: ./.github/workflows/build-linux.yml + with: + cc: clang + cxx: clang++ + enable-thread-sanitizer: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 1b1fff76..782efa22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ FetchContent_Declare(AndroidExtensions EXCLUDE_FROM_ALL) FetchContent_Declare(arcana.cpp GIT_REPOSITORY https://github.com/microsoft/arcana.cpp.git - GIT_TAG b9bf9d85fce37d5fc9dbfc4a4dc5e1531bee215a + GIT_TAG 0b9f9b6d761909fbca9d3ab1f2d8ff1c3d25ed3d EXCLUDE_FROM_ALL) FetchContent_Declare(asio GIT_REPOSITORY https://github.com/chriskohlhoff/asio.git @@ -37,7 +37,7 @@ FetchContent_Declare(llhttp EXCLUDE_FROM_ALL) FetchContent_Declare(UrlLib GIT_REPOSITORY https://github.com/BabylonJS/UrlLib.git - GIT_TAG 880c2575e57ca0b59068ecc4860f185b9970e0ce + GIT_TAG d251ad44015e1ee6cd071514cb863d8ffc220f16 EXCLUDE_FROM_ALL) # -------------------------------------------------- @@ -85,6 +85,11 @@ option(JSRUNTIMEHOST_POLYFILL_TEXTDECODER "Include JsRuntimeHost Polyfill TextDe # Sanitizers option(ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF) +option(ENABLE_THREAD_SANITIZER "Enable ThreadSanitizer" OFF) + +if(ENABLE_SANITIZERS AND ENABLE_THREAD_SANITIZER) + message(FATAL_ERROR "ENABLE_SANITIZERS and ENABLE_THREAD_SANITIZER cannot be used together.") +endif() if(ENABLE_SANITIZERS) set(ENABLE_RTTI ON CACHE BOOL "" FORCE) @@ -111,6 +116,15 @@ if(ENABLE_SANITIZERS) endif() endif() +if(ENABLE_THREAD_SANITIZER) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + add_compile_options(-fsanitize=thread -fno-omit-frame-pointer) + add_link_options(-fsanitize=thread) + else() + message(WARNING "ThreadSanitizer not supported on this compiler.") + endif() +endif() + # -------------------------------------------------- if(JSRUNTIMEHOST_TESTS)