// Copyright (C) 2017 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "repr/symbol/version_script_parser.h" #include "repr/symbol/exported_symbol_set.h" #include "utils/string_utils.h" #include #include #include #include #include #include #include namespace header_checker { namespace repr { using ModeTagLevel = std::pair; static constexpr char DEFAULT_ARCH[] = "arm64"; static constexpr utils::ApiLevel MIN_MODE_TAG_LEVEL = 0; static constexpr utils::ApiLevel MAX_MODE_TAG_LEVEL = 1000000; static const std::set KNOWN_MODE_TAGS{"apex", "llndk", "systemapi"}; inline std::string GetIntroducedArchTag(const std::string &arch) { return "introduced-" + arch + "="; } VersionScriptParser::VersionScriptParser() : arch_(DEFAULT_ARCH), introduced_arch_tag_(GetIntroducedArchTag(arch_)), api_level_(utils::FUTURE_API_LEVEL), api_level_map_(), stream_(nullptr), line_no_(0) {} void VersionScriptParser::SetArch(const std::string &arch) { arch_ = arch; introduced_arch_tag_ = GetIntroducedArchTag(arch); } void VersionScriptParser::SetApiLevelMap(utils::ApiLevelMap api_level_map) { api_level_map_ = std::move(api_level_map); } static std::optional ParseModeTag(std::string_view tag, utils::ApiLevel default_level) { std::vector split_tag = utils::Split(tag, "="); utils::ApiLevel level = default_level; if (split_tag.size() == 2) { auto level = utils::ParseInt(std::string(split_tag[1])); if (level.has_value()) { return {{split_tag[0], level.value()}}; } } else if (split_tag.size() == 1) { return {{split_tag[0], default_level}}; } return {}; } bool VersionScriptParser::AddModeTag(std::string_view mode_tag) { auto parsed_mode_tag = ParseModeTag(mode_tag, MAX_MODE_TAG_LEVEL); if (parsed_mode_tag.has_value()) { included_mode_tags_[std::string(parsed_mode_tag->first)] = parsed_mode_tag->second; return true; } return false; } VersionScriptParser::ParsedTags VersionScriptParser::ParseSymbolTags( const std::string &line, const ParsedTags &initial_value) { static const char *const POSSIBLE_ARCHES[] = { "arm", "arm64", "x86", "x86_64", "mips", "mips64"}; ParsedTags result = initial_value; std::string_view line_view(line); std::string::size_type comment_pos = line_view.find('#'); if (comment_pos == std::string::npos) { return result; } std::string_view comment_line = line_view.substr(comment_pos + 1); std::vector tags = utils::Split(comment_line, " \t"); bool has_introduced_arch_tags = false; for (auto &&tag : tags) { // Check excluded tags. if (excluded_symbol_tags_.find(tag) != excluded_symbol_tags_.end()) { result.has_excluded_tags_ = true; } // Check the var tag. if (tag == "var") { result.has_var_tag_ = true; continue; } // Check arch tags. if (tag == arch_) { result.has_arch_tags_ = true; result.has_current_arch_tag_ = true; continue; } for (auto &&possible_arch : POSSIBLE_ARCHES) { if (tag == possible_arch) { result.has_arch_tags_ = true; break; } } // Check introduced tags. if (utils::StartsWith(tag, "introduced=")) { std::optional intro = api_level_map_.Parse( std::string(tag.substr(sizeof("introduced=") - 1))); if (!intro) { ReportError("Bad introduced tag: " + std::string(tag)); } else { if (!has_introduced_arch_tags) { result.has_introduced_tags_ = true; result.introduced_ = intro.value(); } } continue; } if (utils::StartsWith(tag, introduced_arch_tag_)) { std::optional intro = api_level_map_.Parse( std::string(tag.substr(introduced_arch_tag_.size()))); if (!intro) { ReportError("Bad introduced tag " + std::string(tag)); } else { has_introduced_arch_tags = true; result.has_introduced_tags_ = true; result.introduced_ = intro.value(); } continue; } // Check the future tag. if (tag == "future") { result.has_future_tag_ = true; continue; } // Check the weak binding tag. if (tag == "weak") { result.has_weak_tag_ = true; continue; } auto mode_tag = ParseModeTag(tag, MIN_MODE_TAG_LEVEL); if (mode_tag.has_value() && (KNOWN_MODE_TAGS.count(mode_tag->first) > 0 || included_mode_tags_.count(mode_tag->first) > 0)) { result.mode_tags_[std::string(mode_tag->first)] = mode_tag->second; } } return result; } bool VersionScriptParser::MatchModeTags(const ParsedTags &tags) { if (included_mode_tags_.empty()) { // Include all tags if the user does not specify the option. return true; } for (const auto &mode_tag : tags.mode_tags_) { auto included_mode_tag = included_mode_tags_.find(mode_tag.first); if (included_mode_tag != included_mode_tags_.end() && included_mode_tag->second >= mode_tag.second) { return true; } } return false; } bool VersionScriptParser::MatchIntroducedTags(const ParsedTags &tags) { if (tags.has_future_tag_ && api_level_ < utils::FUTURE_API_LEVEL) { return false; } if (tags.has_introduced_tags_ && api_level_ < tags.introduced_) { return false; } return true; } bool VersionScriptParser::IsSymbolExported(const ParsedTags &tags) { if (tags.has_excluded_tags_) { return false; } if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) { return false; } if (tags.mode_tags_.empty()) { return MatchIntroducedTags(tags); } switch (mode_tag_policy_) { case MatchTagAndApi: return MatchModeTags(tags) && MatchIntroducedTags(tags); case MatchTagOnly: return MatchModeTags(tags); } // Unreachable return false; } bool VersionScriptParser::ParseSymbolLine( const std::string &line, bool is_in_extern_cpp, const ParsedTags &version_block_tags) { // The symbol name comes before the ';'. std::string::size_type pos = line.find(";"); if (pos == std::string::npos) { ReportError("No semicolon at the end of the symbol line: " + line); return false; } std::string symbol(utils::Trim(line.substr(0, pos))); ParsedTags tags = ParseSymbolTags(line, version_block_tags); if (!IsSymbolExported(tags)) { return true; } if (is_in_extern_cpp) { if (utils::IsGlobPattern(symbol)) { exported_symbols_->AddDemangledCppGlobPattern(symbol); } else { exported_symbols_->AddDemangledCppSymbol(symbol); } return true; } if (utils::IsGlobPattern(symbol)) { exported_symbols_->AddGlobPattern(symbol); return true; } ElfSymbolIR::ElfSymbolBinding binding = tags.has_weak_tag_ ? ElfSymbolIR::ElfSymbolBinding::Weak : ElfSymbolIR::ElfSymbolBinding::Global; if (tags.has_var_tag_) { exported_symbols_->AddVar(symbol, binding); } else { exported_symbols_->AddFunction(symbol, binding); } return true; } bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols, const ParsedTags &tags) { static const std::regex EXTERN_CPP_PATTERN(R"(extern\s*"[Cc]\+\+"\s*\{)"); LineScope scope = LineScope::GLOBAL; bool is_in_extern_cpp = false; while (true) { std::string line; if (!ReadLine(line)) { break; } if (line.find("}") != std::string::npos) { if (is_in_extern_cpp) { is_in_extern_cpp = false; continue; } return true; } // Check extern "c++" if (std::regex_match(line, EXTERN_CPP_PATTERN)) { is_in_extern_cpp = true; continue; } // Check symbol visibility label if (utils::StartsWith(line, "local:")) { scope = LineScope::LOCAL; continue; } if (utils::StartsWith(line, "global:")) { scope = LineScope::GLOBAL; continue; } if (scope != LineScope::GLOBAL) { continue; } // Parse symbol line if (!ignore_symbols) { if (!ParseSymbolLine(line, is_in_extern_cpp, tags)) { return false; } } } ReportError("No matching closing parenthesis"); return false; } std::unique_ptr VersionScriptParser::Parse( std::istream &stream) { // Initialize the parser context stream_ = &stream; line_no_ = 0; exported_symbols_.reset(new ExportedSymbolSet()); // Parse while (true) { std::string line; if (!ReadLine(line)) { break; } std::string::size_type lparen_pos = line.find("{"); if (lparen_pos == std::string::npos) { ReportError("No version opening parenthesis" + line); return nullptr; } std::string version(utils::Trim(line.substr(0, lparen_pos - 1))); bool exclude_symbol_version = utils::HasMatchingGlobPattern( excluded_symbol_versions_, version.c_str()); ParsedTags tags = ParseSymbolTags(line, ParsedTags()); if (!ParseVersionBlock(exclude_symbol_version, tags)) { return nullptr; } } return std::move(exported_symbols_); } bool VersionScriptParser::ReadLine(std::string &line) { while (std::getline(*stream_, line)) { ++line_no_; line = std::string(utils::Trim(line)); if (line.empty() || line[0] == '#') { continue; } return true; } return false; } VersionScriptParser::ErrorHandler::~ErrorHandler() {} } // namespace repr } // namespace header_checker