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