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: 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. 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. 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. 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. 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 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. 214 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. 232 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 == decodeApiLevelValue(api_level); 270 } 271 272 return compilation_type.api_level >= decodeApiLevelValue(intro); 273 } 274 275 // Extract and decode the integer API level from api-level or introduced tags. 276 static int decodeApiLevelValue(const std::string& tag) { 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 return std::stoi(api_level); 283 } 284 285 private: 286 const std::string& file_path; 287 const CompilationType& compilation_type; 288 const std::string api_level_arch_prefix; 289 const std::string intro_arch_perfix; 290 291 std::ifstream file; 292 std::string curr_line; 293 std::vector<std::string> curr_tags; 294 size_t curr_line_num; 295 }; 296 297 } // anonymous namespace 298 299 300 std::optional<SymbolMap> parseSymbolFile(const std::string& file_path, 301 const CompilationType& type) { 302 SymbolFileParser parser(file_path, type); 303 return parser.parse(); 304 } 305