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