1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "SymbolFileParser.h"
18 
19 #include "Arch.h"
20 #include "CompilationType.h"
21 
22 #include <android-base/strings.h>
23 
24 #include <fstream>
25 #include <ios>
26 #include <optional>
27 #include <string>
28 #include <unordered_map>
29 #include <vector>
30 
31 #include <err.h>
32 
33 namespace {
34 
35 using TagList = std::vector<std::string>;
36 
37 struct SymbolEnt {
38   std::string name;
39   TagList tags;
40 };
41 
42 using SymbolList = std::vector<SymbolEnt>;
43 
44 struct Version {
45   std::string name;
46   std::string base;
47   SymbolList symbols;
48   TagList tags;
49 };
50 
51 class SymbolFileParser {
52  public:
SymbolFileParser(const std::string & path,const CompilationType & type)53   SymbolFileParser(const std::string& path, const CompilationType& type)
54     : file_path(path),
55       compilation_type(type),
56       api_level_arch_prefix("api-level-" + to_string(type.arch) + "="),
57       intro_arch_perfix("introduced-" + to_string(type.arch) + "="),
58       file(path, std::ios_base::in),
59       curr_line_num(0) {
60   }
61 
62   // Parse the version script and build a symbol map.
parse()63   std::optional<SymbolMap> parse() {
64     if (!file) {
65       return std::nullopt;
66     }
67 
68     SymbolMap symbol_map;
69     while (hasNextLine()) {
70       auto&& version = parseVersion();
71       if (!version) {
72         return std::nullopt;
73       }
74 
75       if (isInArch(version->tags) && isInApi(version->tags)) {
76         for (auto&& [name, tags] : version->symbols) {
77           if (isInArch(tags) && isInApi(tags)) {
78             symbol_map[name] = getSymbolType(tags);
79           }
80         }
81       }
82     }
83     return std::make_optional(std::move(symbol_map));
84   }
85 
86  private:
87   // Read a non-empty line from the input and split at the first '#' character.
hasNextLine()88   bool hasNextLine() {
89     std::string line;
90     while (std::getline(file, line)) {
91       ++curr_line_num;
92 
93       size_t hash_pos = line.find('#');
94       curr_line = android::base::Trim(line.substr(0, hash_pos));
95       if (!curr_line.empty()) {
96         if (hash_pos != std::string::npos) {
97           curr_tags = parseTags(line.substr(hash_pos + 1));
98         } else {
99           curr_tags.clear();
100         }
101         return true;
102       }
103     }
104     return false;
105   }
106 
107   // Tokenize the tags after the '#' character.
parseTags(const std::string & tags_line)108   static std::vector<std::string> parseTags(const std::string& tags_line) {
109     std::vector<std::string> tags = android::base::Split(tags_line, " \t");
110     tags.erase(std::remove(tags.begin(), tags.end(), ""), tags.end());
111     return tags;
112   }
113 
114   // Parse a version scope.
parseVersion()115   std::optional<Version> parseVersion() {
116     size_t start_line_num = curr_line_num;
117 
118     std::string::size_type lparen_pos = curr_line.find('{');
119     if (lparen_pos == std::string::npos) {
120       errx(1, "%s:%zu: error: expected '{' cannot be found in this line",
121            file_path.c_str(), curr_line_num);
122     }
123 
124     // Record the version name and version tags (before hasNextLine()).
125     std::string name = android::base::Trim(curr_line.substr(0, lparen_pos));
126     TagList tags = std::move(curr_tags);
127 
128     // Read symbol lines.
129     SymbolList symbols;
130     bool global_scope = true;
131     bool cpp_scope = false;
132     while (hasNextLine()) {
133       size_t rparen_pos = curr_line.find('}');
134       if (rparen_pos != std::string::npos) {
135         size_t semicolon_pos = curr_line.find(';', rparen_pos + 1);
136         if (semicolon_pos == std::string::npos) {
137           errx(1, "%s:%zu: error: the line that ends a scope must end with ';'",
138                file_path.c_str(), curr_line_num);
139         }
140 
141         if (cpp_scope) {
142           cpp_scope = false;
143           continue;
144         }
145 
146         std::string base = android::base::Trim(
147           curr_line.substr(rparen_pos + 1, semicolon_pos - 1));
148 
149         return std::make_optional(Version{std::move(name), std::move(base),
150                                           std::move(symbols), std::move(tags)});
151       }
152 
153       if (android::base::StartsWith(curr_line, R"(extern "C++" {)")) {
154         cpp_scope = true;
155         continue;
156       }
157 
158       if (cpp_scope) {
159         continue;
160       }
161 
162       size_t colon_pos = curr_line.find(':');
163       if (colon_pos != std::string::npos) {
164         std::string visibility =
165           android::base::Trim(curr_line.substr(0, colon_pos));
166 
167         if (visibility == "global") {
168           global_scope = true;
169         } else if (visibility == "local") {
170           global_scope = false;
171         } else {
172           errx(1, "%s:%zu: error: unknown version visibility: %s",
173                file_path.c_str(), curr_line_num, visibility.c_str());
174         }
175         continue;
176       }
177 
178       if (global_scope) {
179         size_t semicolon_pos = curr_line.find(';');
180         if (semicolon_pos == std::string::npos) {
181           errx(1, "%s:%zu: error: symbol name line must end with ';'",
182                file_path.c_str(), curr_line_num);
183         }
184 
185         std::string symbol_name =
186           android::base::Trim(curr_line.substr(0, semicolon_pos));
187 
188         size_t asterisk_pos = symbol_name.find('*');
189         if (asterisk_pos != std::string::npos) {
190           errx(1, "%s:%zu: error: global symbol name must not have wildcards",
191                file_path.c_str(), curr_line_num);
192         }
193 
194         symbols.push_back(SymbolEnt{std::move(symbol_name),
195                                     std::move(curr_tags)});
196       }
197     }
198 
199     errx(1, "%s:%zu: error: scope started from %zu must be closed before EOF",
200          file_path.c_str(), curr_line_num, start_line_num);
201   }
202 
getSymbolType(const TagList & tags)203   static NdkSymbolType getSymbolType(const TagList& tags) {
204     for (auto&& tag : tags) {
205       if (tag == "var") {
206         return NdkSymbolType::variable;
207       }
208     }
209     return NdkSymbolType::function;
210   }
211 
212   // isInArch() returns true if there is a matching arch-specific tag or there
213   // are no arch-specific tags.
isInArch(const TagList & tags) const214   bool isInArch(const TagList& tags) const {
215     bool has_arch_tags = false;
216     for (auto&& tag : tags) {
217       std::optional<Arch> arch = arch_from_string(tag);
218       if (!arch) {
219         continue;
220       }
221       if (*arch == compilation_type.arch) {
222         return true;
223       }
224       has_arch_tags = true;
225     }
226     return !has_arch_tags;
227   }
228 
229   // isInApi() returns true if the specified API level is equal to the
230   // api-level tag, or the specified API level is greater than or equal to the
231   // introduced tag, or there are no api-level or introduced tags.
isInApi(const TagList & tags) const232   bool isInApi(const TagList& tags) const {
233     bool api_level_arch = false;
234     bool intro_arch = false;
235     std::string api_level;
236     std::string intro;
237 
238     for (const std::string& tag : tags) {
239       // Check api-level tags.
240       if (android::base::StartsWith(tag, "api-level=") && !api_level_arch) {
241         api_level = tag;
242         continue;
243       }
244       if (android::base::StartsWith(tag, api_level_arch_prefix)) {
245         api_level = tag;
246         api_level_arch = true;
247         continue;
248       }
249 
250       // Check introduced tags.
251       if (android::base::StartsWith(tag, "introduced=") && !intro_arch) {
252         intro = tag;
253         continue;
254       }
255       if (android::base::StartsWith(tag, intro_arch_perfix)) {
256         intro = tag;
257         intro_arch = true;
258         continue;
259       }
260     }
261 
262     if (intro.empty() && api_level.empty()) {
263       return true;
264     }
265 
266     if (!api_level.empty()) {
267       // If an api-level tag is specified, it must be an exact match (mainly
268       // for versioner unit tests).
269       return compilation_type.api_level == parseApiLevelValue(api_level);
270     }
271 
272     return compilation_type.api_level >= parseApiLevelValue(intro);
273   }
274 
275   // Parse the integer API level from api-level or introduced tags.
parseApiLevelValue(const std::string & tag) const276   int parseApiLevelValue(const std::string& tag) const {
277     std::string api_level = tag.substr(tag.find('=') + 1);
278     auto it = api_codename_map.find(api_level);
279     if (it != api_codename_map.end()) {
280       return it->second;
281     }
282     if (api_level.find_first_not_of("0123456789") != std::string::npos) {
283       errx(1, "%s:%zu: error: unknown API level codename specified: \"%s\"",
284            file_path.c_str(), curr_line_num, tag.c_str());
285     }
286     return std::stoi(api_level);
287   }
288 
289  private:
290   const std::string& file_path;
291   const CompilationType& compilation_type;
292   const std::string api_level_arch_prefix;
293   const std::string intro_arch_perfix;
294 
295   std::ifstream file;
296   std::string curr_line;
297   std::vector<std::string> curr_tags;
298   size_t curr_line_num;
299 };
300 
301 }  // anonymous namespace
302 
303 
parseSymbolFile(const std::string & file_path,const CompilationType & type)304 std::optional<SymbolMap> parseSymbolFile(const std::string& file_path,
305                                          const CompilationType& type) {
306   SymbolFileParser parser(file_path, type);
307   return parser.parse();
308 }
309