Skip to content

Data race in Source-based Code Coverage #62558

Closed
@Dushistov

Description

@Dushistov

I run two test functions, each on its own thread. Both of these function test the same functionality.
As result each line of tested code should executed two times.
But time to time the execution counter for lines are different.

My code

#include <string>
#include <string_view>
#include <cassert>
#include <thread>

struct State {
  std::string data;

  static State parse(std::string_view src) { return State{std::string(src)}; }

  bool has(std::string_view needle) const {
    return this->data == needle;
  }
};

void state_has_true() {
  const auto s = State::parse("value");
  assert(s.has("value"));
}

void state_has_false() {
  const auto s = State::parse("value");
  assert(!s.has("missing"));
}

int main() {
   std::thread t1(state_has_true);
   std::thread t2(state_has_false);


  t1.join();
  t2.join(); 
}  

If I run code coverage calculation only once, then all work as expected:

$ clang++ -std=c++17 -Wall -Wextra -pedantic -fprofile-instr-generate -fcoverage-mapping foo.cc -o foo
$ LLVM_PROFILE_FILE="foo.profraw" ./foo
$ llvm-profdata merge -sparse foo.profraw -o foo.profdata
$ llvm-cov show ./foo -instr-profile=foo.profdata | grep -E '^   ( 9|1[1-3])' 
    9|      2|  static State parse(std::string_view src) { return State{std::string(src)}; }
   11|      2|  bool has(std::string_view needle) const {
   12|      2|    return this->data == needle;
   13|      2|  }

But if I run such script test.sh, that repeats shell code above 15000 times,
then I certainly got error:

#!/bin/bash

set -euo pipefail

make
export LLVM_PROFILE_FILE="foo.profraw"
for i in $(seq 1 15000); do
    rm -f $LLVM_PROFILE_FILE foo.profdata
    ./foo
    llvm-profdata merge -sparse foo.profraw -o foo.profdata
    if [ ! -z "$(llvm-cov show ./foo -instr-profile=foo.profdata | grep -E '^   ( 9|1[1-3])' | grep -v  '|      2|')" ]; then
        echo "Found BUG at step $i"
        llvm-cov show ./foo -instr-profile=foo.profdata
        exit 1
    fi
done

Result of running the script above:

sh test.sh 
Found BUG at step 460
    1|       |#include <string>
    2|       |#include <string_view>
    3|       |#include <cassert>
    4|       |#include <thread>
    5|       |
    6|       |struct State {
    7|       |  std::string data;
    8|       |
    9|      2|  static State parse(std::string_view src) { return State{std::string(src)}; }
   10|       |
   11|      1|  bool has(std::string_view needle) const {
   12|      1|    return this->data == needle;
   13|      1|  }
   14|       |};
   15|       |
   16|      1|void state_has_true() {
   17|      1|  const auto s = State::parse("value");
   18|      1|  assert(s.has("value"));
   19|      1|}
   20|       |
   21|      1|void state_has_false() {
   22|      1|  const auto s = State::parse("value");
   23|      1|  assert(!s.has("missing"));
   24|      1|}
   25|       |
   26|      1|int main() {
   27|      1|   std::thread t1(state_has_true);
   28|      1|   std::thread t2(state_has_false);
   29|       |
   30|       |
   31|      1|  t1.join();
   32|      1|  t2.join(); 
   33|      1|}  

As you see counters for lines 11-13 are equal 1 while should be 2.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions