Skip to content

clang source-based code coverage *sometimes* incorrectly includes comments into coverage reports #135049

Open
@krinkinmu

Description

@krinkinmu

Hi There,

I believe I found a bug in the clang source based coverage. It manifests for me on the following clang version:

clang++ --version
Ubuntu clang version 18.1.8 (++20240731024944+3b5b5c1ec4a3-1~exp1~20240731145000.144)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

I posted the problem on LLVM discussions (see https://discourse.llvm.org/t/llvm-source-based-coverage-sometimes-generates-counter-for-comments/85730), however since then I found a sensibly small reproducer for the issue, so I'm reporting it as a bug now.

Here is C++ snippet I use to reproduce the issue:

#include <iostream>
#include <memory>

template <typename RequestType, typename ResponseType> class Stream {
public:
  virtual ~Stream() = default;
  virtual void onEvent() = 0;
};

template <typename RequestType, typename ResponseType>
class StreamImpl : public Stream<RequestType, ResponseType> {
public:
  void onEvent() override {
    // A comment that should not be counted by coverage.
    std::cout << "onEvent\n";
  }
};

using Request = int;
using Response = int;

using Processor = Stream<Request, Response>;
using ProcessorPtr = std::unique_ptr<Processor>;

inline ProcessorPtr createSomething() {
  return std::make_unique<StreamImpl<Request, Response>>();
}

class TestLibrary {
public:
  TestLibrary();
  ~TestLibrary();
  void method();
};

TestLibrary::TestLibrary() = default;
TestLibrary::~TestLibrary() = default;

void TestLibrary::method() {
  std::cout << "TestLibrary::method()\n";
}

int main() {
  TestLibrary library;
  library.method();
  return 0;
}

For completeness here are the commands used to generate coverage:

clang++ -fprofile-instr-generate -fcoverage-mapping test.cc -o test
LLVM_PROFILE_FILE="prof.raw" ./test
llvm-profdata merge -sparse prof.raw -o prof.data
llvm-cov export --format=lcov ./test -instr-profile=prof.data

The re-producer is somewhat convoluted, but what I want to concentrate on here is coverage of StreamImpl::onEvent method and specifically the line with comment in that method (line 14).

There is a range of results that, I think, are sensible here:

  1. StreamImpl::onEvent is not included in the coverage at all - it's not used, it's a method of a template that does not need to be instantiated in principle (createSomething function isn't used and there is no other places where this template is used).
  2. StreamImpl::onEvent could be included in the coverage report with 0 coverage, but the comment on line 14 should not be included in the coverage report (e.g., we should not report 0 coverage for comments, instead we should not report coverage for comments at all).

However, what I actually get is the following:

FN:36,_ZN11TestLibraryC2Ev
FN:37,_ZN11TestLibraryD2Ev
FN:39,_ZN11TestLibrary6methodEv
FN:43,main
FN:25,_Z15createSomethingv
FN:13,_ZN10StreamImplIiiE7onEventEv
FNDA:1,_ZN11TestLibraryC2Ev
FNDA:1,_ZN11TestLibraryD2Ev
FNDA:1,_ZN11TestLibrary6methodEv
FNDA:1,main
FNDA:0,_Z15createSomethingv
FNDA:0,_ZN10StreamImplIiiE7onEventEv
FNF:6
FNH:4
DA:13,0
DA:14,0
DA:15,0
DA:16,0
DA:25,0
DA:26,0
DA:27,0
DA:36,1
DA:37,1
DA:39,1
DA:40,1
DA:41,1
DA:43,1
DA:44,1
DA:45,1
DA:46,1
DA:47,1
BRF:0
BRH:0
LF:17
LH:10

As you can see there is a 0 reported for line 14 which is not what I expect to see.

If I change the code to remove the inline modifier from createSomething function, I get a reasonable lcov:

FN:25,_Z15createSomethingv
FN:36,_ZN11TestLibraryC2Ev
FN:37,_ZN11TestLibraryD2Ev
FN:39,_ZN11TestLibrary6methodEv
FN:43,main
FN:6,_ZN6StreamIiiED2Ev
FN:13,_ZN10StreamImplIiiE7onEventEv
FNDA:0,_Z15createSomethingv
FNDA:1,_ZN11TestLibraryC2Ev
FNDA:1,_ZN11TestLibraryD2Ev
FNDA:1,_ZN11TestLibrary6methodEv
FNDA:1,main
FNDA:0,_ZN6StreamIiiED2Ev
FNDA:0,_ZN10StreamImplIiiE7onEventEv
FNF:7
FNH:4
DA:6,0
DA:13,0
DA:15,0
DA:16,0
DA:25,0
DA:26,0
DA:27,0
DA:36,1
DA:37,1
DA:39,1
DA:40,1
DA:41,1
DA:43,1
DA:44,1
DA:45,1
DA:46,1
DA:47,1
BRF:0
BRH:0
LF:17
LH:10

NOTE: The reason why inline is there is that originally that function was defined in a header file.

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