Home | History | Annotate | Download | only in symbol
      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 <regex>
     23 #include <set>
     24 #include <string>
     25 #include <vector>
     26 
     27 
     28 namespace header_checker {
     29 namespace repr {
     30 
     31 
     32 static constexpr char DEFAULT_ARCH[] = "arm64";
     33 
     34 
     35 inline std::string GetIntroducedArchTag(const std::string &arch) {
     36   return "introduced-" + arch + "=";
     37 }
     38 
     39 
     40 VersionScriptParser::VersionScriptParser()
     41     : arch_(DEFAULT_ARCH), introduced_arch_tag_(GetIntroducedArchTag(arch_)),
     42       api_level_(utils::FUTURE_API_LEVEL), stream_(nullptr), line_no_(0) {}
     43 
     44 
     45 void VersionScriptParser::SetArch(const std::string &arch) {
     46   arch_ = arch;
     47   introduced_arch_tag_ = GetIntroducedArchTag(arch);
     48 }
     49 
     50 
     51 VersionScriptParser::ParsedTags VersionScriptParser::ParseSymbolTags(
     52     const std::string &line) {
     53   static const char *const POSSIBLE_ARCHES[] = {
     54       "arm", "arm64", "x86", "x86_64", "mips", "mips64"};
     55 
     56   ParsedTags result;
     57 
     58   std::string_view line_view(line);
     59   std::string::size_type comment_pos = line_view.find('#');
     60   if (comment_pos == std::string::npos) {
     61     return result;
     62   }
     63 
     64   std::string_view comment_line = line_view.substr(comment_pos + 1);
     65   std::vector<std::string_view> tags = utils::Split(comment_line, " \t");
     66 
     67   bool has_introduced_arch_tags = false;
     68 
     69   for (auto &&tag : tags) {
     70     // Check excluded tags.
     71     if (excluded_symbol_tags_.find(tag) != excluded_symbol_tags_.end()) {
     72       result.has_excluded_tags_ = true;
     73     }
     74 
     75     // Check the var tag.
     76     if (tag == "var") {
     77       result.has_var_tag_ = true;
     78       continue;
     79     }
     80 
     81     // Check arch tags.
     82     if (tag == arch_) {
     83       result.has_arch_tags_ = true;
     84       result.has_current_arch_tag_ = true;
     85       continue;
     86     }
     87 
     88     for (auto &&possible_arch : POSSIBLE_ARCHES) {
     89       if (tag == possible_arch) {
     90         result.has_arch_tags_ = true;
     91         break;
     92       }
     93     }
     94 
     95     // Check introduced tags.
     96     if (utils::StartsWith(tag, "introduced=")) {
     97       std::optional<utils::ApiLevel> intro = utils::ParseApiLevel(
     98           std::string(tag.substr(sizeof("introduced=") - 1)));
     99       if (!intro) {
    100         ReportError("Bad introduced tag: " + std::string(tag));
    101       } else {
    102         if (!has_introduced_arch_tags) {
    103           result.has_introduced_tags_ = true;
    104           result.introduced_ = intro.value();
    105         }
    106       }
    107       continue;
    108     }
    109 
    110     if (utils::StartsWith(tag, introduced_arch_tag_)) {
    111       std::optional<utils::ApiLevel> intro = utils::ParseApiLevel(
    112           std::string(tag.substr(introduced_arch_tag_.size())));
    113       if (!intro) {
    114         ReportError("Bad introduced tag " + std::string(tag));
    115       } else {
    116         has_introduced_arch_tags = true;
    117         result.has_introduced_tags_ = true;
    118         result.introduced_ = intro.value();
    119       }
    120       continue;
    121     }
    122 
    123     // Check the future tag.
    124     if (tag == "future") {
    125       result.has_future_tag_ = true;
    126       continue;
    127     }
    128 
    129     // Check the weak binding tag.
    130     if (tag == "weak") {
    131       result.has_weak_tag_ = true;
    132       continue;
    133     }
    134   }
    135 
    136   return result;
    137 }
    138 
    139 
    140 bool VersionScriptParser::IsSymbolExported(
    141     const VersionScriptParser::ParsedTags &tags) {
    142   if (tags.has_excluded_tags_) {
    143     return false;
    144   }
    145 
    146   if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) {
    147     return false;
    148   }
    149 
    150   if (tags.has_future_tag_) {
    151     return api_level_ == utils::FUTURE_API_LEVEL;
    152   }
    153 
    154   if (tags.has_introduced_tags_) {
    155     return api_level_ >= tags.introduced_;
    156   }
    157 
    158   return true;
    159 }
    160 
    161 
    162 bool VersionScriptParser::ParseSymbolLine(const std::string &line,
    163                                           bool is_in_extern_cpp) {
    164   // The symbol name comes before the ';'.
    165   std::string::size_type pos = line.find(";");
    166   if (pos == std::string::npos) {
    167     ReportError("No semicolon at the end of the symbol line: " + line);
    168     return false;
    169   }
    170 
    171   std::string symbol(utils::Trim(line.substr(0, pos)));
    172 
    173   ParsedTags tags = ParseSymbolTags(line);
    174   if (!IsSymbolExported(tags)) {
    175     return true;
    176   }
    177 
    178   if (is_in_extern_cpp) {
    179     if (utils::IsGlobPattern(symbol)) {
    180       exported_symbols_->AddDemangledCppGlobPattern(symbol);
    181     } else {
    182       exported_symbols_->AddDemangledCppSymbol(symbol);
    183     }
    184     return true;
    185   }
    186 
    187   if (utils::IsGlobPattern(symbol)) {
    188     exported_symbols_->AddGlobPattern(symbol);
    189     return true;
    190   }
    191 
    192   ElfSymbolIR::ElfSymbolBinding binding =
    193       tags.has_weak_tag_ ? ElfSymbolIR::ElfSymbolBinding::Weak
    194                          : ElfSymbolIR::ElfSymbolBinding::Global;
    195 
    196   if (tags.has_var_tag_) {
    197     exported_symbols_->AddVar(symbol, binding);
    198   } else {
    199     exported_symbols_->AddFunction(symbol, binding);
    200   }
    201   return true;
    202 }
    203 
    204 
    205 bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols) {
    206   static const std::regex EXTERN_CPP_PATTERN(R"(extern\s*"[Cc]\+\+"\s*\{)");
    207 
    208   LineScope scope = LineScope::GLOBAL;
    209   bool is_in_extern_cpp = false;
    210 
    211   while (true) {
    212     std::string line;
    213     if (!ReadLine(line)) {
    214       break;
    215     }
    216 
    217     if (line.find("}") != std::string::npos) {
    218       if (is_in_extern_cpp) {
    219         is_in_extern_cpp = false;
    220         continue;
    221       }
    222       return true;
    223     }
    224 
    225     // Check extern "c++"
    226     if (std::regex_match(line, EXTERN_CPP_PATTERN)) {
    227       is_in_extern_cpp = true;
    228       continue;
    229     }
    230 
    231     // Check symbol visibility label
    232     if (utils::StartsWith(line, "local:")) {
    233       scope = LineScope::LOCAL;
    234       continue;
    235     }
    236     if (utils::StartsWith(line, "global:")) {
    237       scope = LineScope::GLOBAL;
    238       continue;
    239     }
    240     if (scope != LineScope::GLOBAL) {
    241       continue;
    242     }
    243 
    244     // Parse symbol line
    245     if (!ignore_symbols) {
    246       if (!ParseSymbolLine(line, is_in_extern_cpp)) {
    247         return false;
    248       }
    249     }
    250   }
    251 
    252   ReportError("No matching closing parenthesis");
    253   return false;
    254 }
    255 
    256 
    257 std::unique_ptr<ExportedSymbolSet> VersionScriptParser::Parse(
    258     std::istream &stream) {
    259   // Initialize the parser context
    260   stream_ = &stream;
    261   line_no_ = 0;
    262   exported_symbols_.reset(new ExportedSymbolSet());
    263 
    264   // Parse
    265   while (true) {
    266     std::string line;
    267     if (!ReadLine(line)) {
    268       break;
    269     }
    270 
    271     std::string::size_type lparen_pos = line.find("{");
    272     if (lparen_pos == std::string::npos) {
    273       ReportError("No version opening parenthesis" + line);
    274       return nullptr;
    275     }
    276 
    277     std::string version(utils::Trim(line.substr(0, lparen_pos - 1)));
    278     bool exclude_symbol_version = (excluded_symbol_versions_.find(version) !=
    279                                    excluded_symbol_versions_.end());
    280 
    281     if (!ParseVersionBlock(exclude_symbol_version)) {
    282       return nullptr;
    283     }
    284   }
    285 
    286   return std::move(exported_symbols_);
    287 }
    288 
    289 
    290 bool VersionScriptParser::ReadLine(std::string &line) {
    291   while (std::getline(*stream_, line)) {
    292     ++line_no_;
    293     line = std::string(utils::Trim(line));
    294     if (line.empty() || line[0] == '#') {
    295       continue;
    296     }
    297     return true;
    298   }
    299   return false;
    300 }
    301 
    302 
    303 VersionScriptParser::ErrorHandler::~ErrorHandler() {}
    304 
    305 
    306 }  // namespace repr
    307 }  // namespace header_checker
    308