1//===- gcov.cpp - GCOV compatible LLVM coverage tool ----------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// llvm-cov is a command line tools to analyze and report coverage information.
10//
11//===----------------------------------------------------------------------===//
12
13#include "llvm/ProfileData/GCOV.h"
14#include "llvm/ADT/SmallString.h"
15#include "llvm/Option/ArgList.h"
16#include "llvm/Option/OptTable.h"
17#include "llvm/Option/Option.h"
18#include "llvm/Support/CommandLine.h"
19#include "llvm/Support/Errc.h"
20#include "llvm/Support/FileSystem.h"
21#include "llvm/Support/Path.h"
22#include "llvm/Support/StringSaver.h"
23#include "llvm/Support/WithColor.h"
24#include <system_error>
25using namespace llvm;
26
27namespace {
28enum ID {
29 OPT_INVALID = 0, // This is not an option ID.
30#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
31#include "GcovOpts.inc"
32#undef OPTION
33};
34
35using namespace llvm::opt;
36#define OPTTABLE_CODE
37#include "GcovOpts.inc"
38
39class GcovOptTable : public opt::OptTable {
40public:
41 GcovOptTable() : OptTable(optionTables()) {
42 setGroupedShortOptions(true);
43 setDashDashParsing(true);
44 }
45};
46} // namespace
47
48static void reportCoverage(StringRef SourceFile, StringRef ObjectDir,
49 StringRef InputGCNO, StringRef InputGCDA,
50 bool DumpGCOV, const GCOV::Options &Options) {
51 SmallString<128> CoverageFileStem(ObjectDir);
52 if (CoverageFileStem.empty()) {
53 // If no directory was specified with -o, look next to the source file.
54 CoverageFileStem = sys::path::parent_path(path: SourceFile);
55 sys::path::append(path&: CoverageFileStem, a: sys::path::stem(path: SourceFile));
56 } else if (sys::fs::is_directory(Path: ObjectDir))
57 // A directory name was given. Use it and the source file name.
58 sys::path::append(path&: CoverageFileStem, a: sys::path::stem(path: SourceFile));
59 else
60 // A file was given. Ignore the source file and look next to this file.
61 sys::path::replace_extension(path&: CoverageFileStem, extension: "");
62
63 std::string GCNO = InputGCNO.empty() ? std::string(CoverageFileStem) + ".gcno"
64 : InputGCNO.str();
65 std::string GCDA = InputGCDA.empty() ? std::string(CoverageFileStem) + ".gcda"
66 : InputGCDA.str();
67 GCOVFile GF;
68
69 // Open .gcda and .gcda without requiring a NUL terminator. The concurrent
70 // modification may nullify the NUL terminator condition.
71 ErrorOr<std::unique_ptr<MemoryBuffer>> GCNO_Buff =
72 MemoryBuffer::getFileOrSTDIN(Filename: GCNO, /*IsText=*/false,
73 /*RequiresNullTerminator=*/false);
74 if (std::error_code EC = GCNO_Buff.getError()) {
75 errs() << GCNO << ": " << EC.message() << "\n";
76 return;
77 }
78 GCOVBuffer GCNO_GB(GCNO_Buff.get().get());
79 if (!GF.readGCNO(Buffer&: GCNO_GB)) {
80 errs() << "Invalid .gcno File!\n";
81 return;
82 }
83
84 ErrorOr<std::unique_ptr<MemoryBuffer>> GCDA_Buff =
85 MemoryBuffer::getFileOrSTDIN(Filename: GCDA, /*IsText=*/false,
86 /*RequiresNullTerminator=*/false);
87 if (std::error_code EC = GCDA_Buff.getError()) {
88 if (EC != errc::no_such_file_or_directory) {
89 errs() << GCDA << ": " << EC.message() << "\n";
90 return;
91 }
92 // Clear the filename to make it clear we didn't read anything.
93 GCDA = "-";
94 } else {
95 GCOVBuffer gcda_buf(GCDA_Buff.get().get());
96 if (!gcda_buf.readGCDAFormat())
97 errs() << GCDA << ":not a gcov data file\n";
98 else if (!GF.readGCDA(Buffer&: gcda_buf))
99 errs() << "Invalid .gcda File!\n";
100 }
101
102 if (DumpGCOV)
103 GF.print(OS&: errs());
104
105 gcovOneInput(options: Options, filename: SourceFile, gcno: GCNO, gcda: GCDA, file&: GF);
106}
107
108int gcovMain(int argc, const char *argv[]) {
109 StringRef ToolName = sys::path::filename(path: argv[0]);
110 auto Error = [&](const Twine &Msg) {
111 WithColor::error(OS&: errs(), Prefix: ToolName) << Msg << '\n';
112 exit(status: 1);
113 };
114 BumpPtrAllocator A;
115 StringSaver Saver(A);
116 GcovOptTable Tbl;
117 opt::InputArgList Args =
118 Tbl.parseArgs(Argc: argc, Argv: const_cast<char **>(argv), Unknown: OPT_UNKNOWN, Saver, ErrorFn: Error);
119 if (Args.hasArg(Ids: OPT_help)) {
120 Tbl.printHelp(OS&: outs(), Usage: (ToolName + " [options] SOURCEFILE").str().c_str(),
121 Title: "LLVM code coverage tool");
122 return 0;
123 }
124 if (Args.hasArg(Ids: OPT_version)) {
125 cl::PrintVersionMessage();
126 return 0;
127 }
128 std::vector<std::string> SourceFiles = Args.getAllArgValues(Id: OPT_INPUT);
129 if (SourceFiles.empty())
130 Error("no source file specified");
131
132 GCOV::Options Options(
133 Args.hasArg(Ids: OPT_all_blocks), Args.hasArg(Ids: OPT_branch_probabilities),
134 Args.hasArg(Ids: OPT_branch_counts), Args.hasArg(Ids: OPT_function_summaries),
135 Args.hasArg(Ids: OPT_preserve_paths), Args.hasArg(Ids: OPT_unconditional_branches),
136 Args.hasArg(Ids: OPT_intermediate_format), Args.hasArg(Ids: OPT_long_file_names),
137 Args.hasArg(Ids: OPT_demangled_names), Args.hasArg(Ids: OPT_no_output),
138 Args.hasArg(Ids: OPT_relative_only), Args.hasArg(Ids: OPT_print_stdout),
139 Args.hasArg(Ids: OPT_hash_filenames),
140 Args.getLastArgValue(Id: OPT_source_prefix_EQ).str());
141
142 StringRef ObjectDir = Args.getLastArgValue(Id: OPT_object_directory_EQ);
143 StringRef InputGCNO = Args.getLastArgValue(Id: OPT_gcno_EQ);
144 StringRef InputGCDA = Args.getLastArgValue(Id: OPT_gcda_EQ);
145 bool DumpGCOV = Args.hasArg(Ids: OPT_dump_gcov);
146 for (const std::string &SourceFile : SourceFiles)
147 reportCoverage(SourceFile, ObjectDir, InputGCNO, InputGCDA, DumpGCOV,
148 Options);
149 return 0;
150}
151