blob: 1a51ecce13c67aef38a61194f7bb52a4645f7978 [file] [log] [blame]
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
// -*- mode: C++ -*-
//
// Copyright 2020-2022 Google LLC
//
// Licensed under the Apache License v2.0 with LLVM Exceptions (the
// "License"); you may not use this file except in compliance with the
// License. You may obtain a copy of the License at
//
// https://llvm.org/LICENSE.txt
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: Maria Teguiani
// Author: Giuliano Procida
// Author: Siddharth Nayyar
#include <getopt.h>
#include <cstddef>
#include <cstring>
#include <deque>
#include <fstream>
#include <iostream>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
#include "abigail_reader.h"
#include "btf_reader.h"
#include "elf_reader.h"
#include "equality.h"
#include "error.h"
#include "graph.h"
#include "metrics.h"
#include "proto_reader.h"
#include "reporting.h"
namespace {
stg::Metrics metrics;
const int kAbiChange = 4;
const size_t kMaxCrcOnlyChanges = 3;
const stg::CompareOptions kAllCompareOptionsEnabled{true, true};
enum class InputFormat { ABI, BTF, ELF, STG };
using Inputs = std::vector<std::pair<InputFormat, const char*>>;
using Outputs =
std::vector<std::pair<stg::reporting::OutputFormat, const char*>>;
std::vector<stg::Id> Read(const Inputs& inputs, stg::Graph& graph,
bool process_dwarf, stg::Metrics& metrics) {
std::vector<stg::Id> roots;
for (const auto& [format, filename] : inputs) {
switch (format) {
case InputFormat::ABI: {
stg::Time read(metrics, "read ABI");
roots.push_back(stg::abixml::Read(graph, filename, metrics));
break;
}
case InputFormat::BTF: {
stg::Time read(metrics, "read BTF");
roots.push_back(stg::btf::ReadFile(graph, filename));
break;
}
case InputFormat::ELF: {
stg::Time read(metrics, "read ELF");
roots.push_back(stg::elf::Read(graph, filename, process_dwarf,
/* verbose = */ false));
break;
}
case InputFormat::STG: {
stg::Time read(metrics, "read STG");
roots.push_back(stg::proto::Read(graph, filename));
break;
}
}
}
return roots;
}
bool RunExact(const Inputs& inputs, bool process_dwarf, stg::Metrics& metrics) {
stg::Graph graph;
const auto roots = Read(inputs, graph, process_dwarf, metrics);
struct PairCache {
std::optional<bool> Query(const stg::Pair& comparison) const {
return equalities.find(comparison) != equalities.end()
? std::make_optional(true)
: std::nullopt;
}
void AllSame(const std::vector<stg::Pair>& comparisons) {
for (const auto& comparison : comparisons) {
equalities.insert(comparison);
}
}
void AllDifferent(const std::vector<stg::Pair>&) {}
std::unordered_set<stg::Pair, stg::HashPair> equalities;
};
stg::Time compute(metrics, "equality check");
PairCache equalities;
return stg::Equals<PairCache>(graph, equalities)(roots[0], roots[1]);
}
bool Run(const Inputs& inputs, const Outputs& outputs,
const stg::CompareOptions& compare_options, bool process_dwarf,
stg::Metrics& metrics) {
// Read inputs.
stg::Graph graph;
const auto roots = Read(inputs, graph, process_dwarf, metrics);
// Compute differences.
stg::Compare compare{graph, compare_options, metrics};
std::pair<bool, std::optional<stg::Comparison>> result;
{
stg::Time compute(metrics, "compute diffs");
result = compare(roots[0], roots[1]);
}
stg::Check(compare.scc.Empty()) << "internal error: SCC state broken";
const auto& [equals, comparison] = result;
// Write reports.
stg::NameCache names;
for (const auto& [format, filename] : outputs) {
std::ofstream output(filename);
if (comparison) {
stg::Time report(metrics, "report diffs");
stg::reporting::Options options{format, kMaxCrcOnlyChanges};
stg::reporting::Reporting reporting{graph, compare.outcomes, options,
names};
Report(reporting, *comparison, output);
output << std::flush;
}
if (!output)
stg::Die() << "error writing to " << '\'' << filename << '\'';
}
return equals;
}
} // namespace
bool ParseCompareOptions(const char* opts_arg, stg::CompareOptions& opts) {
std::stringstream opt_stream(opts_arg);
std::string opt;
while (std::getline(opt_stream, opt, ',')) {
if (opt == "ignore_symbol_type_presence_changes")
opts.ignore_symbol_type_presence_changes = true;
else if (opt == "ignore_type_declaration_status_changes")
opts.ignore_type_declaration_status_changes = true;
else if (opt == "all")
opts = kAllCompareOptionsEnabled;
else
return false;
}
return true;
}
enum LongOptions {
kProcessDwarf = 256,
};
int main(int argc, char* argv[]) {
// Process arguments.
bool opt_metrics = false;
bool opt_exact = false;
bool opt_process_dwarf = false;
stg::CompareOptions compare_options;
InputFormat opt_input_format = InputFormat::ABI;
stg::reporting::OutputFormat opt_output_format =
stg::reporting::OutputFormat::PLAIN;
Inputs inputs;
Outputs outputs;
static option opts[] = {
{"metrics", no_argument, nullptr, 'm' },
{"abi", no_argument, nullptr, 'a' },
{"btf", no_argument, nullptr, 'b' },
{"elf", no_argument, nullptr, 'e' },
{"stg", no_argument, nullptr, 's' },
{"exact", no_argument, nullptr, 'x' },
{"compare-options", required_argument, nullptr, 'c' },
{"format", required_argument, nullptr, 'f' },
{"output", required_argument, nullptr, 'o' },
{"process-dwarf", no_argument, nullptr, kProcessDwarf},
{nullptr, 0, nullptr, 0 },
};
auto usage = [&]() {
std::cerr << "usage: " << argv[0] << '\n'
<< " [-m|--metrics]\n"
<< " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file1\n"
<< " [-a|--abi|-b|--btf|-e|--elf|-s|--stg] file2\n"
<< " [{-x|--exact}]\n"
<< " [--process-dwarf]\n"
<< " [{-c|--compare-options} "
"{ignore_symbol_type_presence_changes|"
"ignore_type_declaration_status_changes|all}]\n"
<< " [{-f|--format} {plain|flat|small|short|viz}]\n"
<< " [{-o|--output} {filename|-}] ...\n"
<< " implicit defaults: --abi --format plain\n"
<< " format and output can appear multiple times\n"
<< " multiple comma-separated compare-options can be passed\n"
<< " --exact (node equality) cannot be combined with --output\n"
<< "\n";
return 1;
};
while (true) {
int ix;
int c = getopt_long(argc, argv, "-mabesxc:f:o:", opts, &ix);
if (c == -1)
break;
const char* argument = optarg;
switch (c) {
case 'm':
opt_metrics = true;
break;
case 'a':
opt_input_format = InputFormat::ABI;
break;
case 'b':
opt_input_format = InputFormat::BTF;
break;
case 'e':
opt_input_format = InputFormat::ELF;
break;
case 's':
opt_input_format = InputFormat::STG;
break;
case 'x':
opt_exact = true;
break;
case 1:
inputs.push_back({opt_input_format, argument});
break;
case 'c':
if (!ParseCompareOptions(argument, compare_options))
return usage();
break;
case 'f':
if (strcmp(argument, "plain") == 0)
opt_output_format = stg::reporting::OutputFormat::PLAIN;
else if (strcmp(argument, "flat") == 0)
opt_output_format = stg::reporting::OutputFormat::FLAT;
else if (strcmp(argument, "small") == 0)
opt_output_format = stg::reporting::OutputFormat::SMALL;
else if (strcmp(argument, "short") == 0)
opt_output_format = stg::reporting::OutputFormat::SHORT;
else if (strcmp(argument, "viz") == 0)
opt_output_format = stg::reporting::OutputFormat::VIZ;
else
return usage();
break;
case 'o':
if (strcmp(argument, "-") == 0)
argument = "/dev/stdout";
outputs.push_back({opt_output_format, argument});
break;
case kProcessDwarf:
opt_process_dwarf = true;
break;
default:
return usage();
}
}
if (inputs.size() != 2 || opt_exact > outputs.empty()) {
return usage();
}
try {
const bool equals = opt_exact
? RunExact(inputs, opt_process_dwarf, metrics)
: Run(inputs, outputs, compare_options, opt_process_dwarf, metrics);
if (opt_metrics) {
stg::Report(metrics, std::cerr);
}
return equals ? 0 : kAbiChange;
} catch (const stg::Exception& e) {
std::cerr << e.what() << '\n';
return 1;
}
}