Index: llvm/docs/CommandGuide/llvm-cov.rst =================================================================== --- llvm/docs/CommandGuide/llvm-cov.rst +++ llvm/docs/CommandGuide/llvm-cov.rst @@ -181,6 +181,9 @@ binaries *BIN*,... using the profile data *PROFILE*. It can optionally be filtered to only show the coverage for the files listed in *SOURCES*. +*BIN* may be an executable, object file, dynamic library, or archive (thin or +otherwise). + To use :program:`llvm-cov show`, you need a program that is compiled with instrumentation to emit profile and coverage data. To build such a program with ``clang`` use the ``-fprofile-instr-generate`` and ``-fcoverage-mapping`` @@ -331,6 +334,9 @@ the binaries *BIN*,... using the profile data *PROFILE*. It can optionally be filtered to only show the coverage for the files listed in *SOURCES*. +*BIN* may be an executable, object file, dynamic library, or archive (thin or +otherwise). + If no source files are provided, a summary line is printed for each file in the coverage data. If any files are provided, summaries can be shown for each function in the listed files if the ``-show-functions`` option is enabled. Index: llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h =================================================================== --- llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h +++ llvm/include/llvm/ProfileData/Coverage/CoverageMappingReader.h @@ -203,9 +203,15 @@ BinaryCoverageReader(const BinaryCoverageReader &) = delete; BinaryCoverageReader &operator=(const BinaryCoverageReader &) = delete; + static Expected>> + create(MemoryBufferRef ObjectBuffer, StringRef Arch, + SmallVectorImpl> &ObjectFileBuffers); + static Expected> - create(std::unique_ptr &ObjectBuffer, - StringRef Arch); + createCoverageReaderFromBuffer(StringRef Coverage, + InstrProfSymtab &&ProfileNames, + uint8_t BytesInAddress, + support::endianness Endian); Error readNextRecord(CoverageMappingRecord &Record) override; }; Index: llvm/lib/ProfileData/Coverage/CoverageMapping.cpp =================================================================== --- llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -285,11 +285,14 @@ if (std::error_code EC = CovMappingBufOrErr.getError()) return errorCodeToError(EC); StringRef Arch = Arches.empty() ? StringRef() : Arches[File.index()]; - auto CoverageReaderOrErr = - BinaryCoverageReader::create(CovMappingBufOrErr.get(), Arch); - if (Error E = CoverageReaderOrErr.takeError()) + MemoryBufferRef CovMappingBufRef = + CovMappingBufOrErr.get()->getMemBufferRef(); + auto CoverageReadersOrErr = + BinaryCoverageReader::create(CovMappingBufRef, Arch, Buffers); + if (Error E = CoverageReadersOrErr.takeError()) return std::move(E); - Readers.push_back(std::move(CoverageReaderOrErr.get())); + for (auto &Reader : CoverageReadersOrErr.get()) + Readers.push_back(std::move(Reader)); Buffers.push_back(std::move(CovMappingBufOrErr.get())); } return load(Readers, *ProfileReader); Index: llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp =================================================================== --- llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp +++ llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp @@ -586,12 +586,43 @@ static const char *TestingFormatMagic = "llvmcovmtestdata"; -static Error loadTestingFormat(StringRef Data, InstrProfSymtab &ProfileNames, - StringRef &CoverageMapping, - uint8_t &BytesInAddress, - support::endianness &Endian) { - BytesInAddress = 8; - Endian = support::endianness::little; +Expected> +BinaryCoverageReader::createCoverageReaderFromBuffer( + StringRef Coverage, InstrProfSymtab &&ProfileNames, uint8_t BytesInAddress, + support::endianness Endian) { + std::unique_ptr Reader(new BinaryCoverageReader()); + Reader->ProfileNames = std::move(ProfileNames); + if (BytesInAddress == 4 && Endian == support::endianness::little) { + if (Error E = + readCoverageMappingData( + Reader->ProfileNames, Coverage, Reader->MappingRecords, + Reader->Filenames)) + return std::move(E); + } else if (BytesInAddress == 4 && Endian == support::endianness::big) { + if (Error E = readCoverageMappingData( + Reader->ProfileNames, Coverage, Reader->MappingRecords, + Reader->Filenames)) + return std::move(E); + } else if (BytesInAddress == 8 && Endian == support::endianness::little) { + if (Error E = + readCoverageMappingData( + Reader->ProfileNames, Coverage, Reader->MappingRecords, + Reader->Filenames)) + return std::move(E); + } else if (BytesInAddress == 8 && Endian == support::endianness::big) { + if (Error E = readCoverageMappingData( + Reader->ProfileNames, Coverage, Reader->MappingRecords, + Reader->Filenames)) + return std::move(E); + } else + return make_error(coveragemap_error::malformed); + return Reader; +} + +static Expected> +loadTestingFormat(StringRef Data) { + uint8_t BytesInAddress = 8; + support::endianness Endian = support::endianness::little; Data = Data.substr(StringRef(TestingFormatMagic).size()); if (Data.empty()) @@ -610,9 +641,10 @@ Data = Data.substr(N); if (Data.size() < ProfileNamesSize) return make_error(coveragemap_error::malformed); + InstrProfSymtab ProfileNames; if (Error E = ProfileNames.create(Data.substr(0, ProfileNamesSize), Address)) - return E; - CoverageMapping = Data.substr(ProfileNamesSize); + return std::move(E); + StringRef CoverageMapping = Data.substr(ProfileNamesSize); // Skip the padding bytes because coverage map data has an alignment of 8. if (CoverageMapping.empty()) return make_error(coveragemap_error::truncated); @@ -620,7 +652,8 @@ if (CoverageMapping.size() < Pad) return make_error(coveragemap_error::malformed); CoverageMapping = CoverageMapping.substr(Pad); - return Error::success(); + return BinaryCoverageReader::createCoverageReaderFromBuffer( + CoverageMapping, std::move(ProfileNames), BytesInAddress, Endian); } static Expected lookupSection(ObjectFile &OF, StringRef Name) { @@ -643,15 +676,8 @@ return make_error(coveragemap_error::no_data_found); } -static Error loadBinaryFormat(MemoryBufferRef ObjectBuffer, - InstrProfSymtab &ProfileNames, - StringRef &CoverageMapping, - uint8_t &BytesInAddress, - support::endianness &Endian, StringRef Arch) { - auto BinOrErr = createBinary(ObjectBuffer); - if (!BinOrErr) - return BinOrErr.takeError(); - auto Bin = std::move(BinOrErr.get()); +static Expected> +loadBinaryFormat(std::unique_ptr Bin, StringRef Arch) { std::unique_ptr OF; if (auto *Universal = dyn_cast(Bin.get())) { // If we have a universal binary, try to look up the object for the @@ -671,9 +697,10 @@ return make_error(coveragemap_error::malformed); // The coverage uses native pointer sizes for the object it's written in. - BytesInAddress = OF->getBytesInAddress(); - Endian = OF->isLittleEndian() ? support::endianness::little - : support::endianness::big; + uint8_t BytesInAddress = OF->getBytesInAddress(); + support::endianness Endian = OF->isLittleEndian() + ? support::endianness::little + : support::endianness::big; // Look for the sections that we are interested in. auto ObjFormat = OF->getTripleObjectFormat(); @@ -681,66 +708,101 @@ lookupSection(*OF, getInstrProfSectionName(IPSK_name, ObjFormat, /*AddSegmentInfo=*/false)); if (auto E = NamesSection.takeError()) - return E; + return std::move(E); auto CoverageSection = lookupSection(*OF, getInstrProfSectionName(IPSK_covmap, ObjFormat, /*AddSegmentInfo=*/false)); if (auto E = CoverageSection.takeError()) - return E; + return std::move(E); // Get the contents of the given sections. - if (Expected E = CoverageSection->getContents()) - CoverageMapping = *E; - else - return E.takeError(); + auto CoverageMappingOrErr = CoverageSection->getContents(); + if (!CoverageMappingOrErr) + return CoverageMappingOrErr.takeError(); + InstrProfSymtab ProfileNames; if (Error E = ProfileNames.create(*NamesSection)) - return E; + return std::move(E); - return Error::success(); + return BinaryCoverageReader::createCoverageReaderFromBuffer( + CoverageMappingOrErr.get(), std::move(ProfileNames), BytesInAddress, + Endian); } -Expected> -BinaryCoverageReader::create(std::unique_ptr &ObjectBuffer, - StringRef Arch) { - std::unique_ptr Reader(new BinaryCoverageReader()); +Expected>> +BinaryCoverageReader::create( + MemoryBufferRef ObjectBuffer, StringRef Arch, + SmallVectorImpl> &ObjectFileBuffers) { + std::vector> Readers; - StringRef Coverage; - uint8_t BytesInAddress; - support::endianness Endian; - Error E = Error::success(); - consumeError(std::move(E)); - if (ObjectBuffer->getBuffer().startswith(TestingFormatMagic)) + if (ObjectBuffer.getBuffer().startswith(TestingFormatMagic)) { // This is a special format used for testing. - E = loadTestingFormat(ObjectBuffer->getBuffer(), Reader->ProfileNames, - Coverage, BytesInAddress, Endian); - else - E = loadBinaryFormat(ObjectBuffer->getMemBufferRef(), Reader->ProfileNames, - Coverage, BytesInAddress, Endian, Arch); - if (E) - return std::move(E); + auto ReaderOrErr = loadTestingFormat(ObjectBuffer.getBuffer()); + if (!ReaderOrErr) + return ReaderOrErr.takeError(); + Readers.push_back(std::move(ReaderOrErr.get())); + return Readers; + } - if (BytesInAddress == 4 && Endian == support::endianness::little) - E = readCoverageMappingData( - Reader->ProfileNames, Coverage, Reader->MappingRecords, - Reader->Filenames); - else if (BytesInAddress == 4 && Endian == support::endianness::big) - E = readCoverageMappingData( - Reader->ProfileNames, Coverage, Reader->MappingRecords, - Reader->Filenames); - else if (BytesInAddress == 8 && Endian == support::endianness::little) - E = readCoverageMappingData( - Reader->ProfileNames, Coverage, Reader->MappingRecords, - Reader->Filenames); - else if (BytesInAddress == 8 && Endian == support::endianness::big) - E = readCoverageMappingData( - Reader->ProfileNames, Coverage, Reader->MappingRecords, - Reader->Filenames); - else - return make_error(coveragemap_error::malformed); - if (E) - return std::move(E); - return std::move(Reader); + auto BinOrErr = createBinary(ObjectBuffer); + if (!BinOrErr) + return BinOrErr.takeError(); + std::unique_ptr Bin = std::move(BinOrErr.get()); + + // MachO universal binaries which contain archives need to be treated as + // archives, not as regular binaries. + if (auto *Universal = dyn_cast(Bin.get())) { + for (auto &ObjForArch : Universal->objects()) { + // Skip slices within the universal binary which target the wrong arch. + std::string ObjArch = ObjForArch.getArchFlagName(); + if (Arch != ObjArch) + continue; + + auto ArchiveOrErr = ObjForArch.getAsArchive(); + if (!ArchiveOrErr) { + // If this is not an archive, try treating it as a regular object. + consumeError(ArchiveOrErr.takeError()); + break; + } + + return BinaryCoverageReader::create( + ArchiveOrErr.get()->getMemoryBufferRef(), Arch, ObjectFileBuffers); + } + } + + // Load coverage out of archive members. + if (auto *Ar = dyn_cast(Bin.get())) { + Error Err = Error::success(); + for (auto &Child : Ar->children(Err)) { + Expected ChildBufOrErr = Child.getMemoryBufferRef(); + if (!ChildBufOrErr) + return ChildBufOrErr.takeError(); + + auto ChildReadersOrErr = BinaryCoverageReader::create( + ChildBufOrErr.get(), Arch, ObjectFileBuffers); + if (!ChildReadersOrErr) + return ChildReadersOrErr.takeError(); + for (auto &Reader : ChildReadersOrErr.get()) + Readers.push_back(std::move(Reader)); + } + if (Err) + return std::move(Err); + + // Thin archives reference object files outside of the archive file, i.e. + // files which reside in memory not owned by the caller. Transfer ownership + // to the caller. + if (Ar->isThin()) + for (auto &Buffer : Ar->takeThinBuffers()) + ObjectFileBuffers.push_back(std::move(Buffer)); + + return Readers; + } + + auto ReaderOrErr = loadBinaryFormat(std::move(Bin), Arch); + if (!ReaderOrErr) + return ReaderOrErr.takeError(); + Readers.push_back(std::move(ReaderOrErr.get())); + return Readers; } Error BinaryCoverageReader::readNextRecord(CoverageMappingRecord &Record) { Index: llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/obj1.c =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/obj1.c @@ -0,0 +1 @@ +void f1() {} Index: llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/obj2.c =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/obj2.c @@ -0,0 +1 @@ +void f2() {} Index: llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives.proftext =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives.proftext @@ -0,0 +1,8 @@ +f1 +0x0 +1 +100 +f2 +0x0 +1 +100 Index: llvm/test/tools/llvm-cov/universal_bin_wrapping_archives.test =================================================================== --- /dev/null +++ llvm/test/tools/llvm-cov/universal_bin_wrapping_archives.test @@ -0,0 +1,44 @@ +The coverage reader should be able to handle archives, and archives embedded within +MachO universal binaries. + +--- +Steps to re-generate these files on macOS: + +clang -fprofile-instr-generate -fcoverage-mapping -c obj1.c -o obj1_32.o -arch i386 +clang -fprofile-instr-generate -fcoverage-mapping -c obj2.c -o obj2_32.o -arch i386 +clang -fprofile-instr-generate -fcoverage-mapping -c obj1.c -o obj1_64.o -arch x86_64 +clang -fprofile-instr-generate -fcoverage-mapping -c obj2.c -o obj2_64.o -arch x86_64 +ar -q archive_32 obj1_32.o obj2_32.o +ar -q archive_64 obj1_64.o obj2_64.o +lipo -output universal_bin_wrapping_archives -create archive_32 archive_64 +--- + +RUN: llvm-profdata merge %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives.proftext -o %t.profdata + +RUN: llvm-cov show %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \ +RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch i386 \ +RUN: | FileCheck %s --check-prefix=SHOW_ARCHIVE + +RUN: llvm-cov show %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \ +RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch x86_64 \ +RUN: | FileCheck %s --check-prefix=SHOW_ARCHIVE + +SHOW_ARCHIVE: {{.*}}obj1.c: +SHOW_ARCHIVE-NEXT: 1| 100|void f1() {} +SHOW_ARCHIVE: {{.*}}obj2.c: +SHOW_ARCHIVE-NEXT: 1| 100|void f2() {} + +RUN: llvm-cov report %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \ +RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch i386 \ +RUN: | FileCheck %s --check-prefix=REPORT_ARCHIVE + +RUN: llvm-cov report %S/Inputs/universal_bin_wrapping_archives/universal_bin_wrapping_archives \ +RUN: -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs/universal_bin_wrapping_archives %s -arch x86_64 \ +RUN: | FileCheck %s --check-prefix=REPORT_ARCHIVE + +RUN: llvm-ar rcT %t.thin32.a %S/Inputs/universal_bin_wrapping_archives/obj1_32.o %S/Inputs/universal_bin_wrapping_archives/obj2_32.o +RUN: llvm-cov report %t.thin32.a -instr-profile %t.profdata | FileCheck %s --check-prefix=REPORT_ARCHIVE + +REPORT_ARCHIVE: obj1.c 1 0 100.00% 1 0 100.00% 1 0 100.00% +REPORT_ARCHIVE: obj2.c 1 0 100.00% 1 0 100.00% 1 0 100.00% +REPORT_ARCHIVE: TOTAL 2 0 100.00% 2 0 100.00% 2 0 100.00%