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