Home | History | Annotate | Download | only in src
      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