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 <iostream>
21 #include <memory>
22 #include <optional>
23 #include <regex>
24 #include <set>
25 #include <string>
26 #include <vector>
27
28
29 namespace header_checker {
30 namespace repr {
31
32
33 using ModeTagLevel = std::pair<std::string_view, utils::ApiLevel>;
34
35
36 static constexpr char DEFAULT_ARCH[] = "arm64";
37 static constexpr utils::ApiLevel MIN_MODE_TAG_LEVEL = 0;
38 static constexpr utils::ApiLevel MAX_MODE_TAG_LEVEL = 1000000;
39 static const std::set<std::string_view> KNOWN_MODE_TAGS{"apex", "llndk",
40 "systemapi"};
41
GetIntroducedArchTag(const std::string & arch)42 inline std::string GetIntroducedArchTag(const std::string &arch) {
43 return "introduced-" + arch + "=";
44 }
45
46
VersionScriptParser()47 VersionScriptParser::VersionScriptParser()
48 : arch_(DEFAULT_ARCH), introduced_arch_tag_(GetIntroducedArchTag(arch_)),
49 api_level_(utils::FUTURE_API_LEVEL), api_level_map_(),
50 stream_(nullptr), line_no_(0) {}
51
52
SetArch(const std::string & arch)53 void VersionScriptParser::SetArch(const std::string &arch) {
54 arch_ = arch;
55 introduced_arch_tag_ = GetIntroducedArchTag(arch);
56 }
57
58
SetApiLevelMap(utils::ApiLevelMap api_level_map)59 void VersionScriptParser::SetApiLevelMap(utils::ApiLevelMap api_level_map) {
60 api_level_map_ = std::move(api_level_map);
61 }
62
63
ParseModeTag(std::string_view tag,utils::ApiLevel default_level)64 static std::optional<ModeTagLevel> ParseModeTag(std::string_view tag,
65 utils::ApiLevel default_level) {
66 std::vector<std::string_view> split_tag = utils::Split(tag, "=");
67 utils::ApiLevel level = default_level;
68 if (split_tag.size() == 2) {
69 auto level = utils::ParseInt(std::string(split_tag[1]));
70 if (level.has_value()) {
71 return {{split_tag[0], level.value()}};
72 }
73 } else if (split_tag.size() == 1) {
74 return {{split_tag[0], default_level}};
75 }
76 return {};
77 }
78
79
AddModeTag(std::string_view mode_tag)80 bool VersionScriptParser::AddModeTag(std::string_view mode_tag) {
81 auto parsed_mode_tag = ParseModeTag(mode_tag, MAX_MODE_TAG_LEVEL);
82 if (parsed_mode_tag.has_value()) {
83 included_mode_tags_[std::string(parsed_mode_tag->first)] =
84 parsed_mode_tag->second;
85 return true;
86 }
87 return false;
88 }
89
90
ParseSymbolTags(const std::string & line,const ParsedTags & initial_value)91 VersionScriptParser::ParsedTags VersionScriptParser::ParseSymbolTags(
92 const std::string &line, const ParsedTags &initial_value) {
93 static const char *const POSSIBLE_ARCHES[] = {
94 "arm", "arm64", "x86", "x86_64", "mips", "mips64"};
95
96 ParsedTags result = initial_value;
97
98 std::string_view line_view(line);
99 std::string::size_type comment_pos = line_view.find('#');
100 if (comment_pos == std::string::npos) {
101 return result;
102 }
103
104 std::string_view comment_line = line_view.substr(comment_pos + 1);
105 std::vector<std::string_view> tags = utils::Split(comment_line, " \t");
106
107 bool has_introduced_arch_tags = false;
108
109 for (auto &&tag : tags) {
110 // Check excluded tags.
111 if (excluded_symbol_tags_.find(tag) != excluded_symbol_tags_.end()) {
112 result.has_excluded_tags_ = true;
113 }
114
115 // Check the var tag.
116 if (tag == "var") {
117 result.has_var_tag_ = true;
118 continue;
119 }
120
121 // Check arch tags.
122 if (tag == arch_) {
123 result.has_arch_tags_ = true;
124 result.has_current_arch_tag_ = true;
125 continue;
126 }
127
128 for (auto &&possible_arch : POSSIBLE_ARCHES) {
129 if (tag == possible_arch) {
130 result.has_arch_tags_ = true;
131 break;
132 }
133 }
134
135 // Check introduced tags.
136 if (utils::StartsWith(tag, "introduced=")) {
137 std::optional<utils::ApiLevel> intro = api_level_map_.Parse(
138 std::string(tag.substr(sizeof("introduced=") - 1)));
139 if (!intro) {
140 ReportError("Bad introduced tag: " + std::string(tag));
141 } else {
142 if (!has_introduced_arch_tags) {
143 result.has_introduced_tags_ = true;
144 result.introduced_ = intro.value();
145 }
146 }
147 continue;
148 }
149
150 if (utils::StartsWith(tag, introduced_arch_tag_)) {
151 std::optional<utils::ApiLevel> intro = api_level_map_.Parse(
152 std::string(tag.substr(introduced_arch_tag_.size())));
153 if (!intro) {
154 ReportError("Bad introduced tag " + std::string(tag));
155 } else {
156 has_introduced_arch_tags = true;
157 result.has_introduced_tags_ = true;
158 result.introduced_ = intro.value();
159 }
160 continue;
161 }
162
163 // Check the future tag.
164 if (tag == "future") {
165 result.has_future_tag_ = true;
166 continue;
167 }
168
169 // Check the weak binding tag.
170 if (tag == "weak") {
171 result.has_weak_tag_ = true;
172 continue;
173 }
174
175 auto mode_tag = ParseModeTag(tag, MIN_MODE_TAG_LEVEL);
176 if (mode_tag.has_value() &&
177 (KNOWN_MODE_TAGS.count(mode_tag->first) > 0 ||
178 included_mode_tags_.count(mode_tag->first) > 0)) {
179 result.mode_tags_[std::string(mode_tag->first)] = mode_tag->second;
180 }
181 }
182
183 return result;
184 }
185
186
MatchModeTags(const ParsedTags & tags)187 bool VersionScriptParser::MatchModeTags(const ParsedTags &tags) {
188 if (included_mode_tags_.empty()) {
189 // Include all tags if the user does not specify the option.
190 return true;
191 }
192 for (const auto &mode_tag : tags.mode_tags_) {
193 auto included_mode_tag = included_mode_tags_.find(mode_tag.first);
194 if (included_mode_tag != included_mode_tags_.end() &&
195 included_mode_tag->second >= mode_tag.second) {
196 return true;
197 }
198 }
199 return false;
200 }
201
202
MatchIntroducedTags(const ParsedTags & tags)203 bool VersionScriptParser::MatchIntroducedTags(const ParsedTags &tags) {
204 if (tags.has_future_tag_ && api_level_ < utils::FUTURE_API_LEVEL) {
205 return false;
206 }
207 if (tags.has_introduced_tags_ && api_level_ < tags.introduced_) {
208 return false;
209 }
210 return true;
211 }
212
213
IsSymbolExported(const ParsedTags & tags)214 bool VersionScriptParser::IsSymbolExported(const ParsedTags &tags) {
215 if (tags.has_excluded_tags_) {
216 return false;
217 }
218
219 if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) {
220 return false;
221 }
222
223 if (tags.mode_tags_.empty()) {
224 return MatchIntroducedTags(tags);
225 }
226
227 switch (mode_tag_policy_) {
228 case MatchTagAndApi:
229 return MatchModeTags(tags) && MatchIntroducedTags(tags);
230 case MatchTagOnly:
231 return MatchModeTags(tags);
232 }
233 // Unreachable
234 return false;
235 }
236
237
ParseSymbolLine(const std::string & line,bool is_in_extern_cpp,const ParsedTags & version_block_tags)238 bool VersionScriptParser::ParseSymbolLine(
239 const std::string &line, bool is_in_extern_cpp,
240 const ParsedTags &version_block_tags) {
241 // The symbol name comes before the ';'.
242 std::string::size_type pos = line.find(";");
243 if (pos == std::string::npos) {
244 ReportError("No semicolon at the end of the symbol line: " + line);
245 return false;
246 }
247
248 std::string symbol(utils::Trim(line.substr(0, pos)));
249
250 ParsedTags tags = ParseSymbolTags(line, version_block_tags);
251 if (!IsSymbolExported(tags)) {
252 return true;
253 }
254
255 if (is_in_extern_cpp) {
256 if (utils::IsGlobPattern(symbol)) {
257 exported_symbols_->AddDemangledCppGlobPattern(symbol);
258 } else {
259 exported_symbols_->AddDemangledCppSymbol(symbol);
260 }
261 return true;
262 }
263
264 if (utils::IsGlobPattern(symbol)) {
265 exported_symbols_->AddGlobPattern(symbol);
266 return true;
267 }
268
269 ElfSymbolIR::ElfSymbolBinding binding =
270 tags.has_weak_tag_ ? ElfSymbolIR::ElfSymbolBinding::Weak
271 : ElfSymbolIR::ElfSymbolBinding::Global;
272
273 if (tags.has_var_tag_) {
274 exported_symbols_->AddVar(symbol, binding);
275 } else {
276 exported_symbols_->AddFunction(symbol, binding);
277 }
278 return true;
279 }
280
281
ParseVersionBlock(bool ignore_symbols,const ParsedTags & tags)282 bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols,
283 const ParsedTags &tags) {
284 static const std::regex EXTERN_CPP_PATTERN(R"(extern\s*"[Cc]\+\+"\s*\{)");
285
286 LineScope scope = LineScope::GLOBAL;
287 bool is_in_extern_cpp = false;
288
289 while (true) {
290 std::string line;
291 if (!ReadLine(line)) {
292 break;
293 }
294
295 if (line.find("}") != std::string::npos) {
296 if (is_in_extern_cpp) {
297 is_in_extern_cpp = false;
298 continue;
299 }
300 return true;
301 }
302
303 // Check extern "c++"
304 if (std::regex_match(line, EXTERN_CPP_PATTERN)) {
305 is_in_extern_cpp = true;
306 continue;
307 }
308
309 // Check symbol visibility label
310 if (utils::StartsWith(line, "local:")) {
311 scope = LineScope::LOCAL;
312 continue;
313 }
314 if (utils::StartsWith(line, "global:")) {
315 scope = LineScope::GLOBAL;
316 continue;
317 }
318 if (scope != LineScope::GLOBAL) {
319 continue;
320 }
321
322 // Parse symbol line
323 if (!ignore_symbols) {
324 if (!ParseSymbolLine(line, is_in_extern_cpp, tags)) {
325 return false;
326 }
327 }
328 }
329
330 ReportError("No matching closing parenthesis");
331 return false;
332 }
333
334
Parse(std::istream & stream)335 std::unique_ptr<ExportedSymbolSet> VersionScriptParser::Parse(
336 std::istream &stream) {
337 // Initialize the parser context
338 stream_ = &stream;
339 line_no_ = 0;
340 exported_symbols_.reset(new ExportedSymbolSet());
341
342 // Parse
343 while (true) {
344 std::string line;
345 if (!ReadLine(line)) {
346 break;
347 }
348
349 std::string::size_type lparen_pos = line.find("{");
350 if (lparen_pos == std::string::npos) {
351 ReportError("No version opening parenthesis" + line);
352 return nullptr;
353 }
354
355 std::string version(utils::Trim(line.substr(0, lparen_pos - 1)));
356 bool exclude_symbol_version = utils::HasMatchingGlobPattern(
357 excluded_symbol_versions_, version.c_str());
358
359 ParsedTags tags = ParseSymbolTags(line, ParsedTags());
360 if (!ParseVersionBlock(exclude_symbol_version, tags)) {
361 return nullptr;
362 }
363 }
364
365 return std::move(exported_symbols_);
366 }
367
368
ReadLine(std::string & line)369 bool VersionScriptParser::ReadLine(std::string &line) {
370 while (std::getline(*stream_, line)) {
371 ++line_no_;
372 line = std::string(utils::Trim(line));
373 if (line.empty() || line[0] == '#') {
374 continue;
375 }
376 return true;
377 }
378 return false;
379 }
380
381
~ErrorHandler()382 VersionScriptParser::ErrorHandler::~ErrorHandler() {}
383
384
385 } // namespace repr
386 } // namespace header_checker
387