1 // Copyright (C) 2017 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 "repr/symbol/version_script_parser.h"
16
17 #include "repr/symbol/exported_symbol_set.h"
18 #include "utils/string_utils.h"
19
20 #include <llvm/ADT/Optional.h>
21
22 #include <iostream>
23 #include <memory>
24 #include <regex>
25 #include <set>
26 #include <string>
27 #include <vector>
28
29
30 namespace header_checker {
31 namespace repr {
32
33
34 static constexpr char DEFAULT_ARCH[] = "arm64";
35
36
GetIntroducedArchTag(const std::string & arch)37 inline std::string GetIntroducedArchTag(const std::string &arch) {
38 return "introduced-" + arch + "=";
39 }
40
41
VersionScriptParser()42 VersionScriptParser::VersionScriptParser()
43 : arch_(DEFAULT_ARCH), introduced_arch_tag_(GetIntroducedArchTag(arch_)),
44 api_level_(utils::FUTURE_API_LEVEL), stream_(nullptr), line_no_(0) {}
45
46
SetArch(const std::string & arch)47 void VersionScriptParser::SetArch(const std::string &arch) {
48 arch_ = arch;
49 introduced_arch_tag_ = GetIntroducedArchTag(arch);
50 }
51
52
ParseSymbolTags(const std::string & line)53 VersionScriptParser::ParsedTags VersionScriptParser::ParseSymbolTags(
54 const std::string &line) {
55 static const char *const POSSIBLE_ARCHES[] = {
56 "arm", "arm64", "x86", "x86_64", "mips", "mips64"};
57
58 ParsedTags result;
59
60 std::string_view line_view(line);
61 std::string::size_type comment_pos = line_view.find('#');
62 if (comment_pos == std::string::npos) {
63 return result;
64 }
65
66 std::string_view comment_line = line_view.substr(comment_pos + 1);
67 std::vector<std::string_view> tags = utils::Split(comment_line, " \t");
68
69 bool has_introduced_arch_tags = false;
70
71 for (auto &&tag : tags) {
72 // Check excluded tags.
73 if (excluded_symbol_tags_.find(tag) != excluded_symbol_tags_.end()) {
74 result.has_excluded_tags_ = true;
75 }
76
77 // Check the var tag.
78 if (tag == "var") {
79 result.has_var_tag_ = true;
80 continue;
81 }
82
83 // Check arch tags.
84 if (tag == arch_) {
85 result.has_arch_tags_ = true;
86 result.has_current_arch_tag_ = true;
87 continue;
88 }
89
90 for (auto &&possible_arch : POSSIBLE_ARCHES) {
91 if (tag == possible_arch) {
92 result.has_arch_tags_ = true;
93 break;
94 }
95 }
96
97 // Check introduced tags.
98 if (utils::StartsWith(tag, "introduced=")) {
99 llvm::Optional<utils::ApiLevel> intro = utils::ParseApiLevel(
100 std::string(tag.substr(sizeof("introduced=") - 1)));
101 if (!intro) {
102 ReportError("Bad introduced tag: " + std::string(tag));
103 } else {
104 if (!has_introduced_arch_tags) {
105 result.has_introduced_tags_ = true;
106 result.introduced_ = intro.getValue();
107 }
108 }
109 continue;
110 }
111
112 if (utils::StartsWith(tag, introduced_arch_tag_)) {
113 llvm::Optional<utils::ApiLevel> intro = utils::ParseApiLevel(
114 std::string(tag.substr(introduced_arch_tag_.size())));
115 if (!intro) {
116 ReportError("Bad introduced tag " + std::string(tag));
117 } else {
118 has_introduced_arch_tags = true;
119 result.has_introduced_tags_ = true;
120 result.introduced_ = intro.getValue();
121 }
122 continue;
123 }
124
125 // Check the future tag.
126 if (tag == "future") {
127 result.has_future_tag_ = true;
128 continue;
129 }
130
131 // Check the weak binding tag.
132 if (tag == "weak") {
133 result.has_weak_tag_ = true;
134 continue;
135 }
136 }
137
138 return result;
139 }
140
141
IsSymbolExported(const VersionScriptParser::ParsedTags & tags)142 bool VersionScriptParser::IsSymbolExported(
143 const VersionScriptParser::ParsedTags &tags) {
144 if (tags.has_excluded_tags_) {
145 return false;
146 }
147
148 if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) {
149 return false;
150 }
151
152 if (tags.has_future_tag_) {
153 return api_level_ == utils::FUTURE_API_LEVEL;
154 }
155
156 if (tags.has_introduced_tags_) {
157 return api_level_ >= tags.introduced_;
158 }
159
160 return true;
161 }
162
163
ParseSymbolLine(const std::string & line,bool is_in_extern_cpp)164 bool VersionScriptParser::ParseSymbolLine(const std::string &line,
165 bool is_in_extern_cpp) {
166 // The symbol name comes before the ';'.
167 std::string::size_type pos = line.find(";");
168 if (pos == std::string::npos) {
169 ReportError("No semicolon at the end of the symbol line: " + line);
170 return false;
171 }
172
173 std::string symbol(utils::Trim(line.substr(0, pos)));
174
175 ParsedTags tags = ParseSymbolTags(line);
176 if (!IsSymbolExported(tags)) {
177 return true;
178 }
179
180 if (is_in_extern_cpp) {
181 if (utils::IsGlobPattern(symbol)) {
182 exported_symbols_->AddDemangledCppGlobPattern(symbol);
183 } else {
184 exported_symbols_->AddDemangledCppSymbol(symbol);
185 }
186 return true;
187 }
188
189 if (utils::IsGlobPattern(symbol)) {
190 exported_symbols_->AddGlobPattern(symbol);
191 return true;
192 }
193
194 ElfSymbolIR::ElfSymbolBinding binding =
195 tags.has_weak_tag_ ? ElfSymbolIR::ElfSymbolBinding::Weak
196 : ElfSymbolIR::ElfSymbolBinding::Global;
197
198 if (tags.has_var_tag_) {
199 exported_symbols_->AddVar(symbol, binding);
200 } else {
201 exported_symbols_->AddFunction(symbol, binding);
202 }
203 return true;
204 }
205
206
ParseVersionBlock(bool ignore_symbols)207 bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols) {
208 static const std::regex EXTERN_CPP_PATTERN(R"(extern\s*"[Cc]\+\+"\s*\{)");
209
210 LineScope scope = LineScope::GLOBAL;
211 bool is_in_extern_cpp = false;
212
213 while (true) {
214 std::string line;
215 if (!ReadLine(line)) {
216 break;
217 }
218
219 if (line.find("}") != std::string::npos) {
220 if (is_in_extern_cpp) {
221 is_in_extern_cpp = false;
222 continue;
223 }
224 return true;
225 }
226
227 // Check extern "c++"
228 if (std::regex_match(line, EXTERN_CPP_PATTERN)) {
229 is_in_extern_cpp = true;
230 continue;
231 }
232
233 // Check symbol visibility label
234 if (utils::StartsWith(line, "local:")) {
235 scope = LineScope::LOCAL;
236 continue;
237 }
238 if (utils::StartsWith(line, "global:")) {
239 scope = LineScope::GLOBAL;
240 continue;
241 }
242 if (scope != LineScope::GLOBAL) {
243 continue;
244 }
245
246 // Parse symbol line
247 if (!ignore_symbols) {
248 if (!ParseSymbolLine(line, is_in_extern_cpp)) {
249 return false;
250 }
251 }
252 }
253
254 ReportError("No matching closing parenthesis");
255 return false;
256 }
257
258
Parse(std::istream & stream)259 std::unique_ptr<ExportedSymbolSet> VersionScriptParser::Parse(
260 std::istream &stream) {
261 // Initialize the parser context
262 stream_ = &stream;
263 line_no_ = 0;
264 exported_symbols_.reset(new ExportedSymbolSet());
265
266 // Parse
267 while (true) {
268 std::string line;
269 if (!ReadLine(line)) {
270 break;
271 }
272
273 std::string::size_type lparen_pos = line.find("{");
274 if (lparen_pos == std::string::npos) {
275 ReportError("No version opening parenthesis" + line);
276 return nullptr;
277 }
278
279 std::string version(utils::Trim(line.substr(0, lparen_pos - 1)));
280 bool exclude_symbol_version = (excluded_symbol_versions_.find(version) !=
281 excluded_symbol_versions_.end());
282
283 if (!ParseVersionBlock(exclude_symbol_version)) {
284 return nullptr;
285 }
286 }
287
288 return std::move(exported_symbols_);
289 }
290
291
ReadLine(std::string & line)292 bool VersionScriptParser::ReadLine(std::string &line) {
293 while (std::getline(*stream_, line)) {
294 ++line_no_;
295 line = std::string(utils::Trim(line));
296 if (line.empty() || line[0] == '#') {
297 continue;
298 }
299 return true;
300 }
301 return false;
302 }
303
304
~ErrorHandler()305 VersionScriptParser::ErrorHandler::~ErrorHandler() {}
306
307
308 } // namespace repr
309 } // namespace header_checker
310