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