1 // Copyright (C) 2016 The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "diff/abi_diff.h"
16
17 #include "utils/config_file.h"
18
19 #include <llvm/ADT/SmallString.h>
20 #include <llvm/Support/CommandLine.h>
21 #include <llvm/Support/FileSystem.h>
22 #include <llvm/Support/Path.h>
23 #include <llvm/Support/raw_ostream.h>
24
25 #include <fstream>
26
27
28 using header_checker::diff::HeaderAbiDiff;
29 using header_checker::repr::CompatibilityStatusIR;
30 using header_checker::repr::DiffPolicyOptions;
31 using header_checker::repr::TextFormatIR;
32 using header_checker::utils::ConfigFile;
33 using header_checker::utils::ConfigSection;
34
35
36 static llvm::cl::OptionCategory header_checker_category(
37 "header-abi-diff options");
38
39 static llvm::cl::opt<std::string> compatibility_report(
40 "o", llvm::cl::desc("<compatibility report>"), llvm::cl::Required,
41 llvm::cl::cat(header_checker_category));
42
43 static llvm::cl::opt<std::string> lib_name(
44 "lib", llvm::cl::desc("<lib name>"), llvm::cl::Required,
45 llvm::cl::cat(header_checker_category));
46
47 static llvm::cl::opt<std::string> arch(
48 "arch", llvm::cl::desc("<arch>"), llvm::cl::Required,
49 llvm::cl::cat(header_checker_category));
50
51 static llvm::cl::opt<std::string> new_dump(
52 "new", llvm::cl::desc("<new dump>"), llvm::cl::Required,
53 llvm::cl::cat(header_checker_category));
54
55 static llvm::cl::opt<std::string> old_dump(
56 "old", llvm::cl::desc("<old dump>"), llvm::cl::Required,
57 llvm::cl::cat(header_checker_category));
58
59 static llvm::cl::opt<std::string> ignore_symbol_list(
60 "ignore-symbols", llvm::cl::desc("ignore symbols"), llvm::cl::Optional,
61 llvm::cl::cat(header_checker_category));
62
63 static llvm::cl::opt<bool> advice_only(
64 "advice-only", llvm::cl::desc("Advisory mode only"), llvm::cl::Optional,
65 llvm::cl::cat(header_checker_category));
66
67 static llvm::cl::opt<bool> elf_unreferenced_symbol_errors(
68 "elf-unreferenced-symbol-errors",
69 llvm::cl::desc("This option is deprecated and has no effect."),
70 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
71
72 static llvm::cl::opt<bool> check_all_apis(
73 "check-all-apis",
74 llvm::cl::desc("All apis, whether referenced or not, by exported symbols in"
75 " the dynsym table of a shared library are checked"),
76 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
77
78 static llvm::cl::opt<bool> allow_extensions(
79 "allow-extensions",
80 llvm::cl::desc("Do not return a non zero status on extensions"),
81 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
82
83 static llvm::cl::opt<bool> allow_unreferenced_elf_symbol_changes(
84 "allow-unreferenced-elf-symbol-changes",
85 llvm::cl::desc("Do not return a non zero status on changes to elf symbols"
86 "not referenced by metadata in exported headers"),
87 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
88
89 static llvm::cl::opt<bool> allow_unreferenced_changes(
90 "allow-unreferenced-changes",
91 llvm::cl::desc("Do not return a non zero status on changes to data"
92 " structures which are not directly referenced by exported"
93 " APIs."),
94 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
95
96 static llvm::cl::opt<bool> consider_opaque_types_different(
97 "consider-opaque-types-different",
98 llvm::cl::desc("Consider opaque types with different names as different. "
99 "This should not be used while comparing C++ library ABIs"),
100 llvm::cl::Optional, llvm::cl::cat(header_checker_category));
101
102 static llvm::cl::opt<TextFormatIR> text_format_old(
103 "input-format-old", llvm::cl::desc("Specify input format of old abi dump"),
104 llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat,
105 "ProtobufTextFormat", "ProtobufTextFormat"),
106 clEnumValN(TextFormatIR::Json, "Json", "JSON")),
107 llvm::cl::init(TextFormatIR::Json),
108 llvm::cl::cat(header_checker_category));
109
110 static llvm::cl::opt<TextFormatIR> text_format_new(
111 "input-format-new", llvm::cl::desc("Specify input format of new abi dump"),
112 llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat,
113 "ProtobufTextFormat", "ProtobufTextFormat"),
114 clEnumValN(TextFormatIR::Json, "Json", "JSON")),
115 llvm::cl::init(TextFormatIR::Json),
116 llvm::cl::cat(header_checker_category));
117
118 static llvm::cl::opt<TextFormatIR> text_format_diff(
119 "text-format-diff", llvm::cl::desc("Specify text format of abi-diff"),
120 llvm::cl::values(clEnumValN(TextFormatIR::ProtobufTextFormat,
121 "ProtobufTextFormat", "ProtobufTextFormat")),
122 llvm::cl::init(TextFormatIR::ProtobufTextFormat),
123 llvm::cl::cat(header_checker_category));
124
125 static llvm::cl::opt<bool> allow_adding_removing_weak_symbols(
126 "allow-adding-removing-weak-symbols",
127 llvm::cl::desc("Do not treat addition or removal of weak symbols as "
128 "incompatible changes."),
129 llvm::cl::init(false), llvm::cl::Optional,
130 llvm::cl::cat(header_checker_category));
131
132 static llvm::cl::opt<std::string> target_version(
133 "target-version",
134 llvm::cl::desc(
135 "Load the flags for <target version> and <lib name> from config.json in "
136 "the old dump's parent directory."
137 ),
138 llvm::cl::init("current"), llvm::cl::Optional,
139 llvm::cl::cat(header_checker_category));
140
141 static llvm::cl::list<std::string> ignore_linker_set_keys(
142 "ignore-linker-set-key",
143 llvm::cl::desc("Ignore a specific type or function in the comparison."),
144 llvm::cl::ZeroOrMore, llvm::cl::cat(header_checker_category));
145
LoadIgnoredSymbols(std::string & symbol_list_path)146 static std::set<std::string> LoadIgnoredSymbols(std::string &symbol_list_path) {
147 std::ifstream symbol_ifstream(symbol_list_path);
148 std::set<std::string> ignored_symbols;
149 if (!symbol_ifstream) {
150 llvm::errs() << "Failed to open file containing symbols to ignore\n";
151 ::exit(1);
152 }
153 std::string line = "";
154 while (std::getline(symbol_ifstream, line)) {
155 ignored_symbols.insert(line);
156 }
157 return ignored_symbols;
158 }
159
GetConfigFilePath(const std::string & dump_file_path)160 static std::string GetConfigFilePath(const std::string &dump_file_path) {
161 llvm::SmallString<128> config_file_path(dump_file_path);
162 llvm::sys::path::remove_filename(config_file_path);
163 llvm::sys::path::append(config_file_path, "config.json");
164 return std::string(config_file_path);
165 }
166
UpdateFlags(const ConfigSection & section)167 static void UpdateFlags(const ConfigSection §ion) {
168 for (auto &&i : section.GetIgnoredLinkerSetKeys()) {
169 ignore_linker_set_keys.push_back(i);
170 }
171 for (auto &&p : section) {
172 auto &&key = p.first;
173 bool value_bool = p.second;
174 if (key == "allow_adding_removing_weak_symbols") {
175 allow_adding_removing_weak_symbols = value_bool;
176 } else if (key == "advice_only") {
177 advice_only = value_bool;
178 } else if (key == "elf_unreferenced_symbol_errors") {
179 elf_unreferenced_symbol_errors = value_bool;
180 } else if (key == "check_all_apis") {
181 check_all_apis = value_bool;
182 } else if (key == "allow_extensions") {
183 allow_extensions = value_bool;
184 } else if (key == "allow_unreferenced_elf_symbol_changes") {
185 allow_unreferenced_elf_symbol_changes = value_bool;
186 } else if (key == "allow_unreferenced_changes") {
187 allow_unreferenced_changes = value_bool;
188 } else if (key == "consider_opaque_types_different") {
189 consider_opaque_types_different = value_bool;
190 }
191 }
192 }
193
ReadConfigFile(const std::string & config_file_path)194 static void ReadConfigFile(const std::string &config_file_path) {
195 ConfigFile cfg;
196 if (!cfg.Load(config_file_path)) {
197 ::exit(1);
198 }
199 if (cfg.HasGlobalSection()) {
200 UpdateFlags(cfg.GetGlobalSection());
201 }
202 if (cfg.HasSection(lib_name, target_version)) {
203 UpdateFlags(cfg.GetSection(lib_name, target_version));
204 }
205 }
206
GetErrorMessage(CompatibilityStatusIR status)207 static std::string GetErrorMessage(CompatibilityStatusIR status) {
208 if (status & CompatibilityStatusIR::Incompatible) {
209 return "INCOMPATIBLE CHANGES";
210 }
211 if (!allow_unreferenced_elf_symbol_changes &&
212 (status & CompatibilityStatusIR::ElfIncompatible)) {
213 return "ELF Symbols not referenced by exported headers removed";
214 }
215 if (!allow_extensions && (status & CompatibilityStatusIR::Extension)) {
216 return "EXTENDING CHANGES";
217 }
218 if (!allow_unreferenced_changes &&
219 (status & CompatibilityStatusIR::UnreferencedChanges)) {
220 return "changes in exported headers, which are not directly referenced "
221 "by exported symbols. This MIGHT be an ABI breaking change due to "
222 "internal typecasts";
223 }
224 return "";
225 }
226
main(int argc,const char ** argv)227 int main(int argc, const char **argv) {
228 llvm::cl::ParseCommandLineOptions(argc, argv, "header-checker");
229
230 const std::string config_file_path = GetConfigFilePath(old_dump);
231 if (llvm::sys::fs::exists(config_file_path)) {
232 ReadConfigFile(config_file_path);
233 }
234
235 std::set<std::string> ignored_symbols;
236 if (llvm::sys::fs::exists(ignore_symbol_list)) {
237 ignored_symbols = LoadIgnoredSymbols(ignore_symbol_list);
238 }
239
240 std::set<std::string> ignored_linker_set_keys_set(
241 ignore_linker_set_keys.begin(), ignore_linker_set_keys.end());
242
243 DiffPolicyOptions diff_policy_options(consider_opaque_types_different);
244
245 HeaderAbiDiff judge(lib_name, arch, old_dump, new_dump, compatibility_report,
246 ignored_symbols, ignored_linker_set_keys_set,
247 allow_adding_removing_weak_symbols, diff_policy_options,
248 check_all_apis, text_format_old, text_format_new,
249 text_format_diff);
250
251 CompatibilityStatusIR status = judge.GenerateCompatibilityReport();
252
253 std::string status_str = GetErrorMessage(status);
254 if (!status_str.empty()) {
255 llvm::errs() << "******************************************************\n"
256 << "\033[31;1merror: \033[0m"
257 << lib_name
258 << "'s ABI has "
259 << status_str
260 << ". Please check compatibility report at: "
261 << compatibility_report << "\n"
262 << "******************************************************\n";
263 }
264
265 return (advice_only || status_str.empty()) ? CompatibilityStatusIR::Compatible
266 : status;
267 }
268