Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/human-output-regression.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# SPDX-License-Identifier: Apache-2.0
name: human-output-regression

on:
push:
branches: [main, master]
pull_request:

permissions:
contents: read

jobs:
pytest-human-output:
name: pytest human output (${{ matrix.runner }})
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
runner: [ubuntu-24.04, macos-14]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
cache: pip
cache-dependency-path: .github/workflows/human-output-regression.yml

- name: Install pytest
run: python3 -m pip install --upgrade pip pytest

- name: Install LLVM 20 and build tools on Ubuntu
if: runner.os == 'Linux'
run: |
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 20
sudo apt-get update
sudo apt-get install -y clang-20 llvm-20-dev libclang-20-dev cmake ninja-build

- name: Install LLVM 20 and build tools on macOS
if: runner.os == 'macOS'
run: brew install llvm@20 cmake ninja

- name: Configure toolchain environment
shell: bash
run: |
if [[ "${RUNNER_OS}" == "Linux" ]]; then
llvm_prefix="/usr/lib/llvm-20"
clang_executable="/usr/bin/clang-20"
else
llvm_prefix="$(brew --prefix llvm@20)"
clang_executable="${llvm_prefix}/bin/clang"
fi

echo "${llvm_prefix}/bin" >> "${GITHUB_PATH}"
echo "LLVM_DIR=${llvm_prefix}/lib/cmake/llvm" >> "${GITHUB_ENV}"
echo "Clang_DIR=${llvm_prefix}/lib/cmake/clang" >> "${GITHUB_ENV}"
echo "CLANG_EXECUTABLE=${clang_executable}" >> "${GITHUB_ENV}"
echo "CLANG_RESOURCE_DIR=$("${clang_executable}" -print-resource-dir)" >> "${GITHUB_ENV}"

- name: Configure
run: |
cmake -S . -B build -G Ninja \
-DLLVM_DIR="${LLVM_DIR}" \
-DClang_DIR="${Clang_DIR}" \
-DCLANG_EXECUTABLE="${CLANG_EXECUTABLE}" \
-DCLANG_RESOURCE_DIR="${CLANG_RESOURCE_DIR}"

- name: Build analyzer binary
run: cmake --build build --target coretrace_concurrency_analyzer --parallel

- name: Run human-output golden tests
env:
CORETRACE_ANALYZER_BIN: ./build/coretrace_concurrency_analyzer
run: python3 -m pytest tests/integration/cli/test_human_output_golden.py -q
53 changes: 53 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,18 @@ option(BUILD_CLI "Build coretrace_concurrency_analyzer CLI tool" ON)
option(BUILD_SHARED_LIB "Build shared library variant" OFF)

set(CONCURRENCY_ANALYZER_SOURCES
src/coretrace_concurrency_analysis.cpp
src/coretrace_concurrency_error.cpp
src/internal/analysis/concurrency_symbol_classifier.cpp
src/internal/analysis/data_race_checker.cpp
src/internal/diagnostics/compiler_diagnostic_parser.cpp
src/internal/diagnostics/diagnostic_catalog.cpp
src/internal/analysis/ir_utils.cpp
src/internal/analysis/lock_scope_tracker.cpp
src/internal/reporting/report_renderer.cpp
src/internal/analysis/shared_access_collector.cpp
src/internal/analysis/thread_spawn_detector.cpp
src/internal/analysis/tu_facts_builder.cpp
src/internal/compile_command_builder.cpp
src/internal/compilation_backend.cpp
src/internal/ir_loader.cpp
Expand All @@ -54,6 +65,8 @@ target_include_directories(coretrace_concurrency_analyzer_lib
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_include_directories(coretrace_concurrency_analyzer_lib SYSTEM
Expand All @@ -76,6 +89,7 @@ if(LLVM_LINK_LLVM_DYLIB AND TARGET LLVM)
target_link_libraries(coretrace_concurrency_analyzer_lib PUBLIC LLVM)
else()
llvm_map_components_to_libnames(coretrace_concurrency_llvm_libs
analysis
core
irreader
support
Expand All @@ -94,6 +108,11 @@ if(BUILD_CLI)
main.cpp
)

target_include_directories(coretrace_concurrency_analyzer
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_link_libraries(coretrace_concurrency_analyzer
PRIVATE
coretrace_concurrency_analyzer_lib
Expand Down Expand Up @@ -145,6 +164,22 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)

add_test(NAME coretrace_concurrency_error_tests COMMAND coretrace_concurrency_error_tests)

add_executable(coretrace_concurrency_analysis_tests
tests/unit/test_concurrency_analysis.cpp
)

target_compile_definitions(coretrace_concurrency_analysis_tests
PRIVATE
${CORETRACE_TEST_COMMON_DEFINITIONS}
)

target_link_libraries(coretrace_concurrency_analysis_tests
PRIVATE
coretrace_concurrency_analyzer_lib
)

add_test(NAME coretrace_concurrency_analysis_tests COMMAND coretrace_concurrency_analysis_tests)

add_executable(coretrace_concurrency_cli_cpp_tests
tests/integration/cli/test_cli_cpp.cpp
)
Expand Down Expand Up @@ -175,6 +210,24 @@ if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
else()
message(STATUS "Skipping cli integration tests: ctestfw module not available")
endif()

execute_process(
COMMAND "${Python3_EXECUTABLE}" -c "import pytest"
RESULT_VARIABLE CORETRACE_PYTEST_IMPORT_RESULT
OUTPUT_QUIET
ERROR_QUIET
)

if(CORETRACE_PYTEST_IMPORT_RESULT EQUAL 0)
add_test(
NAME coretrace_concurrency_human_output_golden_tests
COMMAND "${Python3_EXECUTABLE}" -m pytest
"${CMAKE_CURRENT_SOURCE_DIR}/tests/integration/cli/test_human_output_golden.py"
-q
)
else()
message(STATUS "Skipping human output golden tests: pytest module not available")
endif()
else()
message(STATUS "Skipping cli integration tests: Python3 interpreter not found")
endif()
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,13 @@ Run the C++ test suite (unit + CLI integration) with CTest:
ctest --test-dir build --output-on-failure
```

Optional Python integration tests (requires `ctestfw`):
Optional Python integration tests:

```bash
python3 tests/integration/cli/test_analyzer.py
python3 -m pytest tests/integration/cli/test_human_output_golden.py
CORETRACE_ANALYZER_BIN=./build/coretrace_concurrency_analyzer \
python3 -m pytest tests/integration/cli/test_human_output_golden.py
```

## Code style (clang-format)
Expand Down
196 changes: 196 additions & 0 deletions include/coretrace_concurrency_analysis.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
// SPDX-License-Identifier: Apache-2.0
#pragma once

#include <cstdint>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <variant>
#include <vector>

namespace llvm
{
class Module;
} // namespace llvm

namespace ctrace::concurrency
{
enum class AccessKind
{
Read,
Write,
};

enum class Severity
{
Info,
Warning,
Error,
};

enum class RuleId
{
CompilerDiagnostic,
DataRaceGlobal,
};

enum class ConfidenceLevel
{
Low,
Medium,
High,
};

enum class OutputFormat
{
Human,
Json,
Sarif,
};

struct SourceLocation
{
std::string file;
unsigned line = 0;
unsigned column = 0;
unsigned endLine = 0;
unsigned endColumn = 0;
std::string function;
};

struct TaxonomyRef
{
std::string scheme;
std::string id;
std::string title;
};

struct RelatedLocation
{
std::string label;
SourceLocation location;
};

struct DiagnosticNote
{
std::string text;
};

using DiagnosticPropertyValue =
std::variant<bool, std::int64_t, std::string, std::vector<std::string>>;

struct Diagnostic
{
std::string id;
Severity severity = Severity::Info;
RuleId ruleId = RuleId::DataRaceGlobal;
std::optional<ConfidenceLevel> confidence;
std::vector<TaxonomyRef> taxonomies;
SourceLocation location;
std::vector<RelatedLocation> relatedLocations;
std::string message;
std::vector<DiagnosticNote> notes;
std::map<std::string, DiagnosticPropertyValue> properties;
};

struct DiagnosticSummary
{
std::size_t info = 0;
std::size_t warning = 0;
std::size_t error = 0;
};

struct FunctionSummary
{
std::string file;
std::string name;
bool threadReachable = false;
std::vector<std::string> threadEntries;
std::size_t sharedAccessCount = 0;
std::size_t protectedAccessCount = 0;
std::size_t writeAccessCount = 0;
bool hasDiagnostics = false;
};

struct DiagnosticReport
{
std::vector<FunctionSummary> functions;
std::vector<Diagnostic> diagnostics;
DiagnosticSummary diagnosticsSummary;
};

using AnalysisReport = DiagnosticReport;

class SingleTUConcurrencyAnalyzer
{
public:
[[nodiscard]] DiagnosticReport analyze(const llvm::Module& module) const;
};

constexpr std::string_view toString(AccessKind kind)
{
switch (kind)
{
case AccessKind::Read:
return "read";
case AccessKind::Write:
return "write";
}
return "unknown";
}

constexpr std::string_view toString(Severity severity)
{
switch (severity)
{
case Severity::Info:
return "info";
case Severity::Warning:
return "warning";
case Severity::Error:
return "error";
}
return "unknown";
}

constexpr std::string_view toString(RuleId ruleId)
{
switch (ruleId)
{
case RuleId::CompilerDiagnostic:
return "CompilerDiagnostic";
case RuleId::DataRaceGlobal:
return "DataRaceGlobal";
}
return "UnknownRule";
}

constexpr std::string_view toString(ConfidenceLevel confidence)
{
switch (confidence)
{
case ConfidenceLevel::Low:
return "low";
case ConfidenceLevel::Medium:
return "medium";
case ConfidenceLevel::High:
return "high";
}
return "unknown";
}

constexpr std::string_view toString(OutputFormat format)
{
switch (format)
{
case OutputFormat::Human:
return "human";
case OutputFormat::Json:
return "json";
case OutputFormat::Sarif:
return "sarif";
}
return "unknown";
}
} // namespace ctrace::concurrency
Loading
Loading