Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
57054ca
refactor(analyzer): track lowered and user locations in access facts
SizzleUnrlsd Apr 9, 2026
b03eaa1
feat(analyzer): expose IR helpers for tracked roots and source locations
SizzleUnrlsd Apr 9, 2026
e9f7ea8
feat(analyzer): resolve tracked roots and user callsites from LLVM IR
SizzleUnrlsd Apr 9, 2026
b772c04
refactor(analyzer): collect tracked roots with lowered and user locat…
SizzleUnrlsd Apr 9, 2026
63bf69f
feat(analyzer): propagate thread entry bindings across direct calls
SizzleUnrlsd Apr 9, 2026
67c2db7
feat(analyzer): propagate shared accesses through direct call contexts
SizzleUnrlsd Apr 9, 2026
45e3f5f
feat(analyzer): emit user-facing race locations with lowered related …
SizzleUnrlsd Apr 9, 2026
09852c1
feat(reporting): render related diagnostic locations in human output
SizzleUnrlsd Apr 9, 2026
5b1b7f3
docs(fixtures): describe shared-object propagation fixtures
SizzleUnrlsd Apr 9, 2026
bc9bc0a
test(fixtures): add shared object by reference race fixture
SizzleUnrlsd Apr 9, 2026
413c8c7
test(fixtures): add thread-local class negative fixture
SizzleUnrlsd Apr 9, 2026
aca2129
test(analyzer): cover propagated shared objects and remapped race loc…
SizzleUnrlsd Apr 9, 2026
78f6fa2
test(cli): cover propagated shared objects and user-facing race locat…
SizzleUnrlsd Apr 9, 2026
9404966
test(fixtures): update human golden output for basic data race
SizzleUnrlsd Apr 9, 2026
4ca9d71
test(fixtures): update human golden output for missing join limitation
SizzleUnrlsd Apr 9, 2026
b56ce11
chore(fixtures): normalize atomic vs non-atomic fixture formatting
SizzleUnrlsd Apr 9, 2026
065f551
chore(fixtures): normalize cpp class data race fixture formatting
SizzleUnrlsd Apr 9, 2026
1304049
chore(style): format code with clang-format
SizzleUnrlsd Apr 9, 2026
c63808a
chore(style): format code with clang-format
SizzleUnrlsd Apr 9, 2026
a891b5f
chore(style): format code with clang-format
SizzleUnrlsd Apr 9, 2026
81b47d8
style(tests): simplify race location assertions for stable clang-form…
SizzleUnrlsd Apr 9, 2026
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
51 changes: 33 additions & 18 deletions src/internal/analysis/data_race_checker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,11 @@ namespace ctrace::concurrency::internal::analysis
std::tie(rhs.file, rhs.line, rhs.column, rhs.function);
}

bool hasDistinctLoweredLocation(const AccessFact& access)
{
return !sameSourceLocation(access.userLocation, access.loweredLocation);
}

bool shareSelfConcurrentEntry(const EntrySet& lhsEntries, const EntrySet& rhsEntries,
const TUFacts& facts)
{
Expand Down Expand Up @@ -193,7 +198,7 @@ namespace ctrace::concurrency::internal::analysis
std::set<std::string> conflictKinds;
conflictKinds.insert(conflictKindLabel(lhs.kind, rhs.kind));

if (sameSourceLocation(lhs.location, rhs.location) &&
if (sameSourceLocation(lhs.loweredLocation, rhs.loweredLocation) &&
shareSelfConcurrentEntry(lhsEntries, rhsEntries, facts) &&
(lhs.kind == AccessKind::Write || rhs.kind == AccessKind::Write))
{
Expand All @@ -207,7 +212,7 @@ namespace ctrace::concurrency::internal::analysis
const std::vector<std::string>& entries)
{
std::ostringstream stream;
stream << toString(access.kind) << " at " << formatLocation(access.location);
stream << toString(access.kind) << " at " << formatLocation(access.userLocation);

if (!entries.empty())
stream << " (thread entries: " << joinValues(entries) << ")";
Expand All @@ -227,9 +232,9 @@ namespace ctrace::concurrency::internal::analysis
const std::vector<std::string> conflictKinds =
collectConflictKinds(lhs, rhs, lhsEntries, rhsEntries, facts);

DiagnosticBuilder(report, RuleId::DataRaceGlobal)
.primaryLocation(lhs.location)
.relatedLocation("Conflicting access", rhs.location)
DiagnosticBuilder builder(report, RuleId::DataRaceGlobal);
builder.primaryLocation(lhs.userLocation)
.relatedLocation("Conflicting access", rhs.userLocation)
.message("unsynchronized concurrent access to global '" + lhs.symbol + "'")
.note("first access: " + describeAccess(lhs, orderedLhsEntries))
.note("conflicting access: " + describeAccess(rhs, orderedRhsEntries))
Expand All @@ -243,8 +248,14 @@ namespace ctrace::concurrency::internal::analysis
.property("firstThreadEntries", orderedLhsEntries)
.property("secondThreadEntries", orderedRhsEntries)
.property("conflictKinds", conflictKinds)
.property("variableAliasing", std::vector<std::string>{})
.emit();
.property("variableAliasing", std::vector<std::string>{});

if (hasDistinctLoweredLocation(lhs))
builder.relatedLocation("Lowered first access", lhs.loweredLocation);
if (hasDistinctLoweredLocation(rhs))
builder.relatedLocation("Lowered conflicting access", rhs.loweredLocation);

builder.emit();
}

void emitSelfConcurrentDiagnostic(DiagnosticReport& report, const AccessFact& access,
Expand All @@ -255,9 +266,9 @@ namespace ctrace::concurrency::internal::analysis
orderedEntries.empty() ? access.functionId : joinValues(orderedEntries);
const std::vector<std::string> conflictKinds = {"write/write"};

DiagnosticBuilder(report, RuleId::DataRaceGlobal)
.primaryLocation(access.location)
.relatedLocation("Concurrent invocation", access.location)
DiagnosticBuilder builder(report, RuleId::DataRaceGlobal);
builder.primaryLocation(access.userLocation)
.relatedLocation("Concurrent invocation", access.userLocation)
.message("unsynchronized concurrent access to global '" + access.symbol + "'")
.note("access: " + describeAccess(access, orderedEntries))
.note("conflicts with another concurrent invocation reachable from thread entry "
Expand All @@ -273,8 +284,12 @@ namespace ctrace::concurrency::internal::analysis
.property("firstThreadEntries", orderedEntries)
.property("secondThreadEntries", orderedEntries)
.property("conflictKinds", conflictKinds)
.property("variableAliasing", std::vector<std::string>{})
.emit();
.property("variableAliasing", std::vector<std::string>{});

if (hasDistinctLoweredLocation(access))
builder.relatedLocation("Lowered access", access.loweredLocation);

builder.emit();
}

DiagnosticSummary computeSummary(const std::vector<Diagnostic>& diagnostics)
Expand Down Expand Up @@ -316,13 +331,13 @@ namespace ctrace::concurrency::internal::analysis
for (const AccessFact& access : facts.accesses)
{
FunctionSummary& summary = ensureSummary(access.functionId);
if (summary.name.empty() && !access.location.function.empty())
summary.name = access.location.function;
else if (!access.location.function.empty())
summary.name = access.location.function;
if (summary.name.empty() && !access.userLocation.function.empty())
summary.name = access.userLocation.function;
else if (!access.userLocation.function.empty())
summary.name = access.userLocation.function;

if (summary.file.empty() && !access.location.file.empty())
summary.file = access.location.file;
if (summary.file.empty() && !access.userLocation.file.empty())
summary.file = access.userLocation.file;

++summary.sharedAccessCount;
if (!access.heldLocks.empty())
Expand Down
35 changes: 34 additions & 1 deletion src/internal/analysis/facts.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>

namespace llvm
Expand Down Expand Up @@ -34,19 +35,51 @@ namespace ctrace::concurrency::internal::analysis
bool insideLoop = false;
};

enum class RootBindingKind
{
Global,
Argument,
};

struct RootBinding
{
RootBindingKind kind = RootBindingKind::Global;
std::string symbol;
unsigned argumentIndex = 0;

[[nodiscard]] static RootBinding global(std::string globalSymbol)
{
return RootBinding{
.kind = RootBindingKind::Global,
.symbol = std::move(globalSymbol),
};
}

[[nodiscard]] static RootBinding argument(unsigned index)
{
return RootBinding{
.kind = RootBindingKind::Argument,
.argumentIndex = index,
};
}
};

struct AccessFact
{
std::string symbol;
std::string functionId;
AccessKind kind = AccessKind::Read;
SourceLocation location;
SourceLocation loweredLocation;
SourceLocation userLocation;
bool allowCallsiteProjection = false;
std::set<std::string> heldLocks;
};

struct PendingAccess
{
const llvm::Function* function = nullptr;
const llvm::Instruction* instruction = nullptr;
RootBinding root;
AccessFact fact;
};

Expand Down
Loading
Loading