diff options
author | Hsin-Yi Chen <hsinyichen@google.com> | 2024-04-04 23:38:52 +0800 |
---|---|---|
committer | Hsin-Yi Chen <hsinyichen@google.com> | 2024-04-15 12:02:51 +0800 |
commit | ce1e865e0c3caaa1d698ec396f73520c716afd2a (patch) | |
tree | aab810983aefa2b0f68c7b6a937a74958194bdfc | |
parent | 7ccad504114ed41ba92791beb98c16f6303aa1da (diff) | |
download | development-ce1e865e0c3caaa1d698ec396f73520c716afd2a.tar.gz |
Parse mode tags in version scripts
header-abi-linker filters symbols tagged with llndk, apex, or systemapi
in version scripts. The filtering logic is copied from
build/soong/cc/symbolfile.
Usage:
- By default, header-abi-linker includes every symbol in the version
script.
- `-api 34 -include-symbol-tag llndk=202404` includes LLNDK 202404 and
NDK 34.
- `-api 35 -include-symbol-tag none` includes NDK 35, but excludes
LLNDK and APEX.
Test: LD_LIBRARY_PATH=out/soong/dist/lib64 \
out/soong/host/linux-x86/nativetest64/header-checker-unittests/header-checker-unittests
Bug: 314010764
Change-Id: I362995068af0ea6dff317078b8624be46e3b404a
4 files changed, 203 insertions, 29 deletions
diff --git a/vndk/tools/header-checker/src/linker/header_abi_linker.cpp b/vndk/tools/header-checker/src/linker/header_abi_linker.cpp index 657504ecc..a12677796 100644 --- a/vndk/tools/header-checker/src/linker/header_abi_linker.cpp +++ b/vndk/tools/header-checker/src/linker/header_abi_linker.cpp @@ -81,9 +81,19 @@ static llvm::cl::list<std::string> excluded_symbol_tags( "exclude-symbol-tag", llvm::cl::Optional, llvm::cl::cat(header_linker_category)); +static llvm::cl::list<std::string> included_symbol_tags( + "include-symbol-tag", + llvm::cl::desc("Filter the symbols in the version script by mode tag, " + "such as llndk, apex, and systemapi. The format is " + "<tag>=<level> or <tag>. If this option is not specified, " + "all mode tags are included."), + llvm::cl::Optional, llvm::cl::cat(header_linker_category)); + static llvm::cl::opt<std::string> api( - "api", llvm::cl::desc("<api>"), llvm::cl::Optional, - llvm::cl::init("current"), + "api", + llvm::cl::desc("Filter the symbols in the version script by comparing " + "\"introduced\" tags and the specified API level."), + llvm::cl::Optional, llvm::cl::init("current"), llvm::cl::cat(header_linker_category)); static llvm::cl::opt<std::string> api_map( @@ -480,6 +490,12 @@ static bool InitializeVersionScriptParser(repr::VersionScriptParser &parser) { for (auto &&tag : excluded_symbol_tags) { parser.AddExcludedSymbolTag(tag); } + for (auto &&tag : included_symbol_tags) { + if (!parser.AddModeTag(tag)) { + llvm::errs() << "Failed to parse -include-symbol-tag " << tag << "\n"; + return false; + } + } return true; } diff --git a/vndk/tools/header-checker/src/repr/symbol/version_script_parser.cpp b/vndk/tools/header-checker/src/repr/symbol/version_script_parser.cpp index fe1735587..931064b21 100644 --- a/vndk/tools/header-checker/src/repr/symbol/version_script_parser.cpp +++ b/vndk/tools/header-checker/src/repr/symbol/version_script_parser.cpp @@ -30,8 +30,14 @@ namespace header_checker { namespace repr { -static constexpr char DEFAULT_ARCH[] = "arm64"; +using ModeTagLevel = std::pair<std::string_view, utils::ApiLevel>; + +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<std::string_view> KNOWN_MODE_TAGS{"apex", "llndk", + "systemapi"}; inline std::string GetIntroducedArchTag(const std::string &arch) { return "introduced-" + arch + "="; @@ -55,12 +61,39 @@ void VersionScriptParser::SetApiLevelMap(utils::ApiLevelMap api_level_map) { } +static std::optional<ModeTagLevel> ParseModeTag(std::string_view tag, + utils::ApiLevel default_level) { + std::vector<std::string_view> 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 std::string &line, const ParsedTags &initial_value) { static const char *const POSSIBLE_ARCHES[] = { "arm", "arm64", "x86", "x86_64", "mips", "mips64"}; - ParsedTags result; + ParsedTags result = initial_value; std::string_view line_view(line); std::string::size_type comment_pos = line_view.find('#'); @@ -138,36 +171,62 @@ VersionScriptParser::ParsedTags VersionScriptParser::ParseSymbolTags( 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::IsSymbolExported( - const VersionScriptParser::ParsedTags &tags) { - if (tags.has_excluded_tags_) { +bool VersionScriptParser::MatchModeTags(const ParsedTags &tags) { + 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; +} - if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) { + +bool VersionScriptParser::IsSymbolExported(const ParsedTags &tags) { + if (tags.has_excluded_tags_) { return false; } - if (tags.has_future_tag_) { - return api_level_ == utils::FUTURE_API_LEVEL; + if (tags.has_arch_tags_ && !tags.has_current_arch_tag_) { + return false; } - if (tags.has_introduced_tags_) { - return api_level_ >= tags.introduced_; + if (!included_mode_tags_.empty() && !tags.mode_tags_.empty()) { + return MatchModeTags(tags); } - return true; + return MatchIntroducedTags(tags); } -bool VersionScriptParser::ParseSymbolLine(const std::string &line, - bool is_in_extern_cpp) { +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) { @@ -177,7 +236,7 @@ bool VersionScriptParser::ParseSymbolLine(const std::string &line, std::string symbol(utils::Trim(line.substr(0, pos))); - ParsedTags tags = ParseSymbolTags(line); + ParsedTags tags = ParseSymbolTags(line, version_block_tags); if (!IsSymbolExported(tags)) { return true; } @@ -209,7 +268,8 @@ bool VersionScriptParser::ParseSymbolLine(const std::string &line, } -bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols) { +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; @@ -250,7 +310,7 @@ bool VersionScriptParser::ParseVersionBlock(bool ignore_symbols) { // Parse symbol line if (!ignore_symbols) { - if (!ParseSymbolLine(line, is_in_extern_cpp)) { + if (!ParseSymbolLine(line, is_in_extern_cpp, tags)) { return false; } } @@ -285,8 +345,8 @@ std::unique_ptr<ExportedSymbolSet> VersionScriptParser::Parse( bool exclude_symbol_version = utils::HasMatchingGlobPattern( excluded_symbol_versions_, version.c_str()); - ParsedTags tags = ParseSymbolTags(line); - if (!ParseVersionBlock(exclude_symbol_version || !IsSymbolExported(tags))) { + ParsedTags tags = ParseSymbolTags(line, ParsedTags()); + if (!ParseVersionBlock(exclude_symbol_version, tags)) { return nullptr; } } diff --git a/vndk/tools/header-checker/src/repr/symbol/version_script_parser.h b/vndk/tools/header-checker/src/repr/symbol/version_script_parser.h index 74ca72f1a..0f468b55d 100644 --- a/vndk/tools/header-checker/src/repr/symbol/version_script_parser.h +++ b/vndk/tools/header-checker/src/repr/symbol/version_script_parser.h @@ -20,7 +20,7 @@ #include "utils/api_level.h" #include <functional> -#include <set> +#include <map> #include <string> @@ -30,12 +30,14 @@ namespace repr { class VersionScriptParser { private: + // This comparison function allows finding elements by string_view. + using ModeTagLevelMap = std::map<std::string, utils::ApiLevel, std::less<>>; + enum class LineScope { GLOBAL, LOCAL, }; - struct ParsedTags { public: unsigned has_arch_tags_ : 1; @@ -46,7 +48,7 @@ class VersionScriptParser { unsigned has_var_tag_ : 1; unsigned has_weak_tag_ : 1; utils::ApiLevel introduced_; - + ModeTagLevelMap mode_tags_; public: ParsedTags() @@ -84,6 +86,9 @@ class VersionScriptParser { excluded_symbol_tags_.insert(tag); } + // Returns whether the argument is valid. + bool AddModeTag(std::string_view mode_tag); + void SetErrorHandler(std::unique_ptr<ErrorHandler> error_handler) { error_handler_ = std::move(error_handler); } @@ -94,11 +99,17 @@ class VersionScriptParser { private: bool ReadLine(std::string &line); - bool ParseVersionBlock(bool ignore_symbols); + bool ParseVersionBlock(bool ignore_symbols, const ParsedTags &tags); + + bool ParseSymbolLine(const std::string &line, bool is_cpp_symbol, + const ParsedTags &version_block_tags); + + ParsedTags ParseSymbolTags(const std::string &line, + const ParsedTags &initial_value); - bool ParseSymbolLine(const std::string &line, bool is_cpp_symbol); + bool MatchModeTags(const ParsedTags &tags); - ParsedTags ParseSymbolTags(const std::string &line); + bool MatchIntroducedTags(const ParsedTags &tags); bool IsSymbolExported(const ParsedTags &tags); @@ -121,6 +132,7 @@ class VersionScriptParser { utils::StringSet excluded_symbol_versions_; utils::StringSet excluded_symbol_tags_; + ModeTagLevelMap included_mode_tags_; std::istream *stream_; int line_no_; diff --git a/vndk/tools/header-checker/src/repr/symbol/version_script_parser_test.cpp b/vndk/tools/header-checker/src/repr/symbol/version_script_parser_test.cpp index 15e13517e..41955f3b0 100644 --- a/vndk/tools/header-checker/src/repr/symbol/version_script_parser_test.cpp +++ b/vndk/tools/header-checker/src/repr/symbol/version_script_parser_test.cpp @@ -174,8 +174,9 @@ TEST(VersionScriptParserTest, ParseSymbolTagsIntroduced) { ASSERT_TRUE(result); const ExportedSymbolSet::FunctionMap &funcs = result->GetFunctions(); - - EXPECT_TRUE(funcs.empty()); + // This may be an undefined behavior. ndkstubgen includes it in llndk mode, + // but excludes it in ndk mode. + EXPECT_THAT(funcs, ElementsAre(Key("test5"))); } { @@ -355,6 +356,91 @@ TEST(VersionScriptParserTest, ExcludeSymbolTags) { } +TEST(VersionScriptParserTest, IncludeSymbolTags) { + static const char testdata[] = R"TESTDATA( + LIBEX_1.0 { + global: + always; # unknown + api34; # introduced=34 + api35; # introduced=35 + api35_llndk202404; # introduced=35 llndk=202404 + systemapi; # systemapi + systemapi_llndk; # systemapi llndk + }; + LIBEX_2.0 { # introduced=36 + api36_llndk202504; + api36_llndk202504; # llndk=202504 + }; + )TESTDATA"; + + { + VersionScriptParser parser; + parser.SetApiLevel(34); + parser.AddModeTag("llndk=202404"); + + std::istringstream stream(testdata); + std::unique_ptr<ExportedSymbolSet> result(parser.Parse(stream)); + ASSERT_TRUE(result); + + const ExportedSymbolSet::FunctionMap &funcs = result->GetFunctions(); + + EXPECT_THAT(funcs, + ElementsAre(Key("always"), Key("api34"), + Key("api35_llndk202404"), Key("systemapi_llndk"))); + } + + { + VersionScriptParser parser; + parser.SetApiLevel(34); + parser.AddModeTag("llndk"); + + std::istringstream stream(testdata); + std::unique_ptr<ExportedSymbolSet> result(parser.Parse(stream)); + ASSERT_TRUE(result); + + const ExportedSymbolSet::FunctionMap &funcs = result->GetFunctions(); + + EXPECT_THAT( + funcs, + ElementsAre(Key("always"), Key("api34"), Key("api35_llndk202404"), + Key("api36_llndk202504"), Key("systemapi_llndk"))); + } + + // Include all mode tags + { + VersionScriptParser parser; + parser.SetApiLevel(utils::FUTURE_API_LEVEL); + + std::istringstream stream(testdata); + std::unique_ptr<ExportedSymbolSet> result(parser.Parse(stream)); + ASSERT_TRUE(result); + + const ExportedSymbolSet::FunctionMap &funcs = result->GetFunctions(); + + EXPECT_THAT(funcs, + ElementsAre(Key("always"), Key("api34"), Key("api35"), + Key("api35_llndk202404"), Key("api36_llndk202504"), + Key("systemapi"), Key("systemapi_llndk"))); + } + + // Exclude all mode tags + { + VersionScriptParser parser; + parser.SetApiLevel(utils::FUTURE_API_LEVEL); + parser.AddModeTag("none"); + + std::istringstream stream(testdata); + std::unique_ptr<ExportedSymbolSet> result(parser.Parse(stream)); + ASSERT_TRUE(result); + + const ExportedSymbolSet::FunctionMap &funcs = result->GetFunctions(); + + EXPECT_THAT(funcs, ElementsAre(Key("always"), Key("api34"), Key("api35"), + Key("api36_llndk202504"))); + } +} + + TEST(VersionScriptParserTest, ParseExternCpp) { static const char testdata[] = R"TESTDATA( LIBEX_1.0 { |