[Coverage] Support overriding compilation directory

When making compilation relocatable, for example in distributed
compilation scenarios, we want to set compilation dir to a relative
value like `.` but this presents a problem when generating reports
because if the file path is relative as well, for example `..`, you
may end up writing files outside of the output directory.

This change introduces a flag that allows overriding the compilation
directory that's stored inside the profile with a different value that
is absolute.

Differential Revision: https://reviews.llvm.org/D100232
This commit is contained in:
Petr Hosek 2021-04-09 11:53:59 -07:00
parent a0162a81b1
commit 8280ece0c9
10 changed files with 107 additions and 42 deletions

View File

@ -603,7 +603,7 @@ public:
/// Ignores non-instrumented object files unless all are not instrumented.
static Expected<std::unique_ptr<CoverageMapping>>
load(ArrayRef<StringRef> ObjectFilenames, StringRef ProfileFilename,
ArrayRef<StringRef> Arches = None);
ArrayRef<StringRef> Arches = None, StringRef CompilationDir = "");
/// The number of functions that couldn't have their profiles mapped.
///

View File

@ -202,13 +202,15 @@ public:
static Expected<std::vector<std::unique_ptr<BinaryCoverageReader>>>
create(MemoryBufferRef ObjectBuffer, StringRef Arch,
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers);
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers,
StringRef CompilationDir = "");
static Expected<std::unique_ptr<BinaryCoverageReader>>
createCoverageReaderFromBuffer(StringRef Coverage, std::string &&FuncRecords,
InstrProfSymtab &&ProfileNames,
uint8_t BytesInAddress,
support::endianness Endian);
support::endianness Endian,
StringRef CompilationDir = "");
Error readNextRecord(CoverageMappingRecord &Record) override;
};
@ -216,14 +218,17 @@ public:
/// Reader for the raw coverage filenames.
class RawCoverageFilenamesReader : public RawCoverageReader {
std::vector<std::string> &Filenames;
StringRef CompilationDir;
// Read an uncompressed sequence of filenames.
Error readUncompressed(CovMapVersion Version, uint64_t NumFilenames);
public:
RawCoverageFilenamesReader(StringRef Data,
std::vector<std::string> &Filenames)
: RawCoverageReader(Data), Filenames(Filenames) {}
std::vector<std::string> &Filenames,
StringRef CompilationDir = "")
: RawCoverageReader(Data), Filenames(Filenames),
CompilationDir(CompilationDir) {}
RawCoverageFilenamesReader(const RawCoverageFilenamesReader &) = delete;
RawCoverageFilenamesReader &
operator=(const RawCoverageFilenamesReader &) = delete;

View File

@ -319,7 +319,8 @@ static Error handleMaybeNoDataFoundError(Error E) {
Expected<std::unique_ptr<CoverageMapping>>
CoverageMapping::load(ArrayRef<StringRef> ObjectFilenames,
StringRef ProfileFilename, ArrayRef<StringRef> Arches) {
StringRef ProfileFilename, ArrayRef<StringRef> Arches,
StringRef CompilationDir) {
auto ProfileReaderOrErr = IndexedInstrProfReader::create(ProfileFilename);
if (Error E = ProfileReaderOrErr.takeError())
return std::move(E);
@ -336,8 +337,8 @@ CoverageMapping::load(ArrayRef<StringRef> ObjectFilenames,
MemoryBufferRef CovMappingBufRef =
CovMappingBufOrErr.get()->getMemBufferRef();
SmallVector<std::unique_ptr<MemoryBuffer>, 4> Buffers;
auto CoverageReadersOrErr =
BinaryCoverageReader::create(CovMappingBufRef, Arch, Buffers);
auto CoverageReadersOrErr = BinaryCoverageReader::create(
CovMappingBufRef, Arch, Buffers, CompilationDir);
if (Error E = CoverageReadersOrErr.takeError()) {
E = handleMaybeNoDataFoundError(std::move(E));
if (E)

View File

@ -138,7 +138,8 @@ Error RawCoverageFilenamesReader::read(CovMapVersion Version) {
}
StringRef UncompressedFilenames(StorageBuf.data(), StorageBuf.size());
RawCoverageFilenamesReader Delegate(UncompressedFilenames, Filenames);
RawCoverageFilenamesReader Delegate(UncompressedFilenames, Filenames,
CompilationDir);
return Delegate.readUncompressed(Version, NumFilenames);
}
@ -168,7 +169,11 @@ Error RawCoverageFilenamesReader::readUncompressed(CovMapVersion Version,
if (sys::path::is_absolute(Filename)) {
Filenames.push_back(Filename.str());
} else {
SmallString<256> P(CWD);
SmallString<256> P;
if (!CompilationDir.empty())
P.assign(CompilationDir);
else
P.assign(CWD);
llvm::sys::path::append(P, Filename);
Filenames.push_back(static_cast<std::string>(P));
}
@ -520,7 +525,7 @@ struct CovMapFuncRecordReader {
template <class IntPtrT, support::endianness Endian>
static Expected<std::unique_ptr<CovMapFuncRecordReader>>
get(CovMapVersion Version, InstrProfSymtab &P,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R, StringRef D,
std::vector<std::string> &F);
};
@ -535,6 +540,7 @@ class VersionedCovMapFuncRecordReader : public CovMapFuncRecordReader {
// in \c Records.
DenseMap<NameRefType, size_t> FunctionRecords;
InstrProfSymtab &ProfileNames;
StringRef CompilationDir;
std::vector<std::string> &Filenames;
std::vector<BinaryCoverageReader::ProfileMappingRecord> &Records;
@ -594,9 +600,9 @@ class VersionedCovMapFuncRecordReader : public CovMapFuncRecordReader {
public:
VersionedCovMapFuncRecordReader(
InstrProfSymtab &P,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R, StringRef D,
std::vector<std::string> &F)
: ProfileNames(P), Filenames(F), Records(R) {}
: ProfileNames(P), CompilationDir(D), Filenames(F), Records(R) {}
~VersionedCovMapFuncRecordReader() override = default;
@ -629,7 +635,8 @@ public:
return make_error<CoverageMapError>(coveragemap_error::malformed);
size_t FilenamesBegin = Filenames.size();
StringRef FilenameRegion(CovBuf, FilenamesSize);
RawCoverageFilenamesReader Reader(FilenameRegion, Filenames);
RawCoverageFilenamesReader Reader(FilenameRegion, Filenames,
CompilationDir);
if (auto Err = Reader.read(Version))
return std::move(Err);
CovBuf += FilenamesSize;
@ -735,14 +742,14 @@ public:
template <class IntPtrT, support::endianness Endian>
Expected<std::unique_ptr<CovMapFuncRecordReader>> CovMapFuncRecordReader::get(
CovMapVersion Version, InstrProfSymtab &P,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &R, StringRef D,
std::vector<std::string> &F) {
using namespace coverage;
switch (Version) {
case CovMapVersion::Version1:
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version1, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version1, IntPtrT, Endian>>(P, R, D, F);
case CovMapVersion::Version2:
case CovMapVersion::Version3:
case CovMapVersion::Version4:
@ -753,19 +760,19 @@ Expected<std::unique_ptr<CovMapFuncRecordReader>> CovMapFuncRecordReader::get(
return std::move(E);
if (Version == CovMapVersion::Version2)
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version2, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version2, IntPtrT, Endian>>(P, R, D, F);
else if (Version == CovMapVersion::Version3)
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version3, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version3, IntPtrT, Endian>>(P, R, D, F);
else if (Version == CovMapVersion::Version4)
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version4, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version4, IntPtrT, Endian>>(P, R, D, F);
else if (Version == CovMapVersion::Version5)
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version5, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version5, IntPtrT, Endian>>(P, R, D, F);
else if (Version == CovMapVersion::Version6)
return std::make_unique<VersionedCovMapFuncRecordReader<
CovMapVersion::Version6, IntPtrT, Endian>>(P, R, F);
CovMapVersion::Version6, IntPtrT, Endian>>(P, R, D, F);
}
llvm_unreachable("Unsupported version");
}
@ -774,7 +781,7 @@ template <typename T, support::endianness Endian>
static Error readCoverageMappingData(
InstrProfSymtab &ProfileNames, StringRef CovMap, StringRef FuncRecords,
std::vector<BinaryCoverageReader::ProfileMappingRecord> &Records,
std::vector<std::string> &Filenames) {
StringRef CompilationDir, std::vector<std::string> &Filenames) {
using namespace coverage;
// Read the records in the coverage data section.
@ -785,7 +792,7 @@ static Error readCoverageMappingData(
return make_error<CoverageMapError>(coveragemap_error::unsupported_version);
Expected<std::unique_ptr<CovMapFuncRecordReader>> ReaderExpected =
CovMapFuncRecordReader::get<T, Endian>(Version, ProfileNames, Records,
Filenames);
CompilationDir, Filenames);
if (Error E = ReaderExpected.takeError())
return E;
auto Reader = std::move(ReaderExpected.get());
@ -817,8 +824,9 @@ static const char *TestingFormatMagic = "llvmcovmtestdata";
Expected<std::unique_ptr<BinaryCoverageReader>>
BinaryCoverageReader::createCoverageReaderFromBuffer(
StringRef Coverage, std::string &&FuncRecords, InstrProfSymtab &&ProfileNames,
uint8_t BytesInAddress, support::endianness Endian) {
StringRef Coverage, std::string &&FuncRecords,
InstrProfSymtab &&ProfileNames, uint8_t BytesInAddress,
support::endianness Endian, StringRef CompilationDir) {
std::unique_ptr<BinaryCoverageReader> Reader(
new BinaryCoverageReader(std::move(FuncRecords)));
Reader->ProfileNames = std::move(ProfileNames);
@ -827,23 +835,23 @@ BinaryCoverageReader::createCoverageReaderFromBuffer(
if (Error E =
readCoverageMappingData<uint32_t, support::endianness::little>(
Reader->ProfileNames, Coverage, FuncRecordsRef,
Reader->MappingRecords, Reader->Filenames))
Reader->MappingRecords, CompilationDir, Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 4 && Endian == support::endianness::big) {
if (Error E = readCoverageMappingData<uint32_t, support::endianness::big>(
Reader->ProfileNames, Coverage, FuncRecordsRef,
Reader->MappingRecords, Reader->Filenames))
Reader->MappingRecords, CompilationDir, Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 8 && Endian == support::endianness::little) {
if (Error E =
readCoverageMappingData<uint64_t, support::endianness::little>(
Reader->ProfileNames, Coverage, FuncRecordsRef,
Reader->MappingRecords, Reader->Filenames))
Reader->MappingRecords, CompilationDir, Reader->Filenames))
return std::move(E);
} else if (BytesInAddress == 8 && Endian == support::endianness::big) {
if (Error E = readCoverageMappingData<uint64_t, support::endianness::big>(
Reader->ProfileNames, Coverage, FuncRecordsRef,
Reader->MappingRecords, Reader->Filenames))
Reader->MappingRecords, CompilationDir, Reader->Filenames))
return std::move(E);
} else
return make_error<CoverageMapError>(coveragemap_error::malformed);
@ -851,7 +859,7 @@ BinaryCoverageReader::createCoverageReaderFromBuffer(
}
static Expected<std::unique_ptr<BinaryCoverageReader>>
loadTestingFormat(StringRef Data) {
loadTestingFormat(StringRef Data, StringRef CompilationDir) {
uint8_t BytesInAddress = 8;
support::endianness Endian = support::endianness::little;
@ -911,7 +919,7 @@ loadTestingFormat(StringRef Data) {
}
return BinaryCoverageReader::createCoverageReaderFromBuffer(
CoverageMapping, CoverageRecords.str(), std::move(ProfileNames),
BytesInAddress, Endian);
BytesInAddress, Endian, CompilationDir);
}
/// Find all sections that match \p Name. There may be more than one if comdats
@ -941,7 +949,8 @@ static Expected<std::vector<SectionRef>> lookupSections(ObjectFile &OF,
}
static Expected<std::unique_ptr<BinaryCoverageReader>>
loadBinaryFormat(std::unique_ptr<Binary> Bin, StringRef Arch) {
loadBinaryFormat(std::unique_ptr<Binary> Bin, StringRef Arch,
StringRef CompilationDir = "") {
std::unique_ptr<ObjectFile> OF;
if (auto *Universal = dyn_cast<MachOUniversalBinary>(Bin.get())) {
// If we have a universal binary, try to look up the object for the
@ -1013,7 +1022,7 @@ loadBinaryFormat(std::unique_ptr<Binary> Bin, StringRef Arch) {
return BinaryCoverageReader::createCoverageReaderFromBuffer(
CoverageMapping, std::move(FuncRecords), std::move(ProfileNames),
BytesInAddress, Endian);
BytesInAddress, Endian, CompilationDir);
}
/// Determine whether \p Arch is invalid or empty, given \p Bin.
@ -1032,12 +1041,14 @@ static bool isArchSpecifierInvalidOrMissing(Binary *Bin, StringRef Arch) {
Expected<std::vector<std::unique_ptr<BinaryCoverageReader>>>
BinaryCoverageReader::create(
MemoryBufferRef ObjectBuffer, StringRef Arch,
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers) {
SmallVectorImpl<std::unique_ptr<MemoryBuffer>> &ObjectFileBuffers,
StringRef CompilationDir) {
std::vector<std::unique_ptr<BinaryCoverageReader>> Readers;
if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) {
// This is a special format used for testing.
auto ReaderOrErr = loadTestingFormat(ObjectBuffer.getBuffer());
auto ReaderOrErr =
loadTestingFormat(ObjectBuffer.getBuffer(), CompilationDir);
if (!ReaderOrErr)
return ReaderOrErr.takeError();
Readers.push_back(std::move(ReaderOrErr.get()));
@ -1070,7 +1081,8 @@ BinaryCoverageReader::create(
}
return BinaryCoverageReader::create(
ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers);
ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers,
CompilationDir);
}
}
@ -1083,7 +1095,7 @@ BinaryCoverageReader::create(
return ChildBufOrErr.takeError();
auto ChildReadersOrErr = BinaryCoverageReader::create(
ChildBufOrErr.get(), Arch, ObjectFileBuffers);
ChildBufOrErr.get(), Arch, ObjectFileBuffers, CompilationDir);
if (!ChildReadersOrErr)
return ChildReadersOrErr.takeError();
for (auto &Reader : ChildReadersOrErr.get())
@ -1102,7 +1114,7 @@ BinaryCoverageReader::create(
return std::move(Readers);
}
auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch);
auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch, CompilationDir);
if (!ReaderOrErr)
return ReaderOrErr.takeError();
Readers.push_back(std::move(ReaderOrErr.get()));

View File

@ -0,0 +1,8 @@
main
# Func Hash:
0
# Num Counters:
1
# Counter Values:
1

View File

@ -0,0 +1,3 @@
// RUN: llvm-profdata merge %S/Inputs/compilation_dir.proftext -o %t.profdata
// RUN: llvm-cov show %S/Inputs/compilation_dir.covmapping -instr-profile=%t.profdata -compilation-dir=%S | FileCheck %s
int main() {} // CHECK: [[@LINE]]| 1|int main() {}

View File

@ -434,7 +434,8 @@ std::unique_ptr<CoverageMapping> CodeCoverageTool::load() {
warning("profile data may be out of date - object is newer",
ObjectFilename);
auto CoverageOrErr =
CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches);
CoverageMapping::load(ObjectFilenames, PGOFilename, CoverageArches,
ViewOpts.CompilationDirectory);
if (Error E = CoverageOrErr.takeError()) {
error("Failed to load coverage: " + toString(std::move(E)),
join(ObjectFilenames.begin(), ObjectFilenames.end(), ", "));
@ -739,6 +740,10 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
cl::alias NumThreadsA("j", cl::desc("Alias for --num-threads"),
cl::aliasopt(NumThreads));
cl::opt<std::string> CompilationDirectory(
"compilation-dir", cl::init(""),
cl::desc("Directory used as a base for relative coverage mapping paths"));
auto commandLineParser = [&, this](int argc, const char **argv) -> int {
cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
ViewOpts.Debug = DebugDump;
@ -873,6 +878,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
ViewOpts.ShowInstantiationSummary = InstantiationSummary;
ViewOpts.ExportSummaryOnly = SummaryOnly;
ViewOpts.NumThreads = NumThreads;
ViewOpts.CompilationDirectory = CompilationDirectory;
return 0;
};

View File

@ -49,6 +49,7 @@ struct CoverageViewOptions {
std::string ProjectTitle;
std::string CreatedTimeStr;
unsigned NumThreads;
std::string CompilationDirectory;
/// Change the output's stream color if the colors are enabled.
ColoredRawOstream colored_ostream(raw_ostream &OS,

View File

@ -900,7 +900,7 @@ INSTANTIATE_TEST_CASE_P(ParameterizedCovMapTest, CoverageMappingTest,
std::pair<bool, bool>({true, true})),);
TEST(CoverageMappingTest, filename_roundtrip) {
std::vector<std::string> Paths({"", "a", "b", "c", "d", "e"});
std::vector<std::string> Paths({"dir", "a", "b", "c", "d", "e"});
for (bool Compress : {false, true}) {
std::string EncodedFilenames;
@ -915,8 +915,37 @@ TEST(CoverageMappingTest, filename_roundtrip) {
EXPECT_THAT_ERROR(Reader.read(CovMapVersion::CurrentVersion), Succeeded());
ASSERT_EQ(ReadFilenames.size(), Paths.size());
for (unsigned I = 1; I < Paths.size(); ++I)
ASSERT_TRUE(ReadFilenames[I] == Paths[I]);
for (unsigned I = 1; I < Paths.size(); ++I) {
SmallString<256> P(Paths[0]);
llvm::sys::path::append(P, Paths[I]);
ASSERT_TRUE(ReadFilenames[I] == P);
}
}
}
TEST(CoverageMappingTest, filename_compilation_dir) {
std::vector<std::string> Paths({"dir", "a", "b", "c", "d", "e"});
for (bool Compress : {false, true}) {
std::string EncodedFilenames;
{
raw_string_ostream OS(EncodedFilenames);
CoverageFilenamesSectionWriter Writer(Paths);
Writer.write(OS, Compress);
}
StringRef CompilationDir = "out";
std::vector<std::string> ReadFilenames;
RawCoverageFilenamesReader Reader(EncodedFilenames, ReadFilenames,
CompilationDir);
EXPECT_THAT_ERROR(Reader.read(CovMapVersion::CurrentVersion), Succeeded());
ASSERT_EQ(ReadFilenames.size(), Paths.size());
for (unsigned I = 1; I < Paths.size(); ++I) {
SmallString<256> P(CompilationDir);
llvm::sys::path::append(P, Paths[I]);
ASSERT_TRUE(ReadFilenames[I] == P);
}
}
}