• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
GetIntroducedArchTag(const std::string & arch)35 inline std::string GetIntroducedArchTag(const std::string &arch) {
36   return "introduced-" + arch + "=";
37 }
38 
39 
VersionScriptParser()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 
SetArch(const std::string & arch)45 void VersionScriptParser::SetArch(const std::string &arch) {
46   arch_ = arch;
47   introduced_arch_tag_ = GetIntroducedArchTag(arch);
48 }
49 
50 
ParseSymbolTags(const std::string & line)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 
IsSymbolExported(const VersionScriptParser::ParsedTags & tags)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 
ParseSymbolLine(const std::string & line,bool is_in_extern_cpp)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 
ParseVersionBlock(bool ignore_symbols)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 
Parse(std::istream & stream)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 
ReadLine(std::string & line)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 
~ErrorHandler()303 VersionScriptParser::ErrorHandler::~ErrorHandler() {}
304 
305 
306 }  // namespace repr
307 }  // namespace header_checker
308