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