/*
|
* Copyright (C) 2018 The Android Open Source Project
|
*
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
* you may not use this file except in compliance with the License.
|
* You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing, software
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* See the License for the specific language governing permissions and
|
* limitations under the License.
|
*/
|
|
#include "SymbolFileParser.h"
|
|
#include "Arch.h"
|
#include "CompilationType.h"
|
|
#include <android-base/strings.h>
|
|
#include <fstream>
|
#include <ios>
|
#include <optional>
|
#include <string>
|
#include <unordered_map>
|
#include <vector>
|
|
#include <err.h>
|
|
namespace {
|
|
using TagList = std::vector<std::string>;
|
|
struct SymbolEnt {
|
std::string name;
|
TagList tags;
|
};
|
|
using SymbolList = std::vector<SymbolEnt>;
|
|
struct Version {
|
std::string name;
|
std::string base;
|
SymbolList symbols;
|
TagList tags;
|
};
|
|
class SymbolFileParser {
|
public:
|
SymbolFileParser(const std::string& path, const CompilationType& type)
|
: file_path(path),
|
compilation_type(type),
|
api_level_arch_prefix("api-level-" + to_string(type.arch) + "="),
|
intro_arch_perfix("introduced-" + to_string(type.arch) + "="),
|
file(path, std::ios_base::in),
|
curr_line_num(0) {
|
}
|
|
// Parse the version script and build a symbol map.
|
std::optional<SymbolMap> parse() {
|
if (!file) {
|
return std::nullopt;
|
}
|
|
SymbolMap symbol_map;
|
while (hasNextLine()) {
|
auto&& version = parseVersion();
|
if (!version) {
|
return std::nullopt;
|
}
|
|
if (isInArch(version->tags) && isInApi(version->tags)) {
|
for (auto&& [name, tags] : version->symbols) {
|
if (isInArch(tags) && isInApi(tags)) {
|
symbol_map[name] = getSymbolType(tags);
|
}
|
}
|
}
|
}
|
return std::make_optional(std::move(symbol_map));
|
}
|
|
private:
|
// Read a non-empty line from the input and split at the first '#' character.
|
bool hasNextLine() {
|
std::string line;
|
while (std::getline(file, line)) {
|
++curr_line_num;
|
|
size_t hash_pos = line.find('#');
|
curr_line = android::base::Trim(line.substr(0, hash_pos));
|
if (!curr_line.empty()) {
|
if (hash_pos != std::string::npos) {
|
curr_tags = parseTags(line.substr(hash_pos + 1));
|
} else {
|
curr_tags.clear();
|
}
|
return true;
|
}
|
}
|
return false;
|
}
|
|
// Tokenize the tags after the '#' character.
|
static std::vector<std::string> parseTags(const std::string& tags_line) {
|
std::vector<std::string> tags = android::base::Split(tags_line, " \t");
|
tags.erase(std::remove(tags.begin(), tags.end(), ""), tags.end());
|
return tags;
|
}
|
|
// Parse a version scope.
|
std::optional<Version> parseVersion() {
|
size_t start_line_num = curr_line_num;
|
|
std::string::size_type lparen_pos = curr_line.find('{');
|
if (lparen_pos == std::string::npos) {
|
errx(1, "%s:%zu: error: expected '{' cannot be found in this line",
|
file_path.c_str(), curr_line_num);
|
}
|
|
// Record the version name and version tags (before hasNextLine()).
|
std::string name = android::base::Trim(curr_line.substr(0, lparen_pos));
|
TagList tags = std::move(curr_tags);
|
|
// Read symbol lines.
|
SymbolList symbols;
|
bool global_scope = true;
|
bool cpp_scope = false;
|
while (hasNextLine()) {
|
size_t rparen_pos = curr_line.find('}');
|
if (rparen_pos != std::string::npos) {
|
size_t semicolon_pos = curr_line.find(';', rparen_pos + 1);
|
if (semicolon_pos == std::string::npos) {
|
errx(1, "%s:%zu: error: the line that ends a scope must end with ';'",
|
file_path.c_str(), curr_line_num);
|
}
|
|
if (cpp_scope) {
|
cpp_scope = false;
|
continue;
|
}
|
|
std::string base = android::base::Trim(
|
curr_line.substr(rparen_pos + 1, semicolon_pos - 1));
|
|
return std::make_optional(Version{std::move(name), std::move(base),
|
std::move(symbols), std::move(tags)});
|
}
|
|
if (android::base::StartsWith(curr_line, R"(extern "C++" {)")) {
|
cpp_scope = true;
|
continue;
|
}
|
|
if (cpp_scope) {
|
continue;
|
}
|
|
size_t colon_pos = curr_line.find(':');
|
if (colon_pos != std::string::npos) {
|
std::string visibility =
|
android::base::Trim(curr_line.substr(0, colon_pos));
|
|
if (visibility == "global") {
|
global_scope = true;
|
} else if (visibility == "local") {
|
global_scope = false;
|
} else {
|
errx(1, "%s:%zu: error: unknown version visibility: %s",
|
file_path.c_str(), curr_line_num, visibility.c_str());
|
}
|
continue;
|
}
|
|
if (global_scope) {
|
size_t semicolon_pos = curr_line.find(';');
|
if (semicolon_pos == std::string::npos) {
|
errx(1, "%s:%zu: error: symbol name line must end with ';'",
|
file_path.c_str(), curr_line_num);
|
}
|
|
std::string symbol_name =
|
android::base::Trim(curr_line.substr(0, semicolon_pos));
|
|
size_t asterisk_pos = symbol_name.find('*');
|
if (asterisk_pos != std::string::npos) {
|
errx(1, "%s:%zu: error: global symbol name must not have wildcards",
|
file_path.c_str(), curr_line_num);
|
}
|
|
symbols.push_back(SymbolEnt{std::move(symbol_name),
|
std::move(curr_tags)});
|
}
|
}
|
|
errx(1, "%s:%zu: error: scope started from %zu must be closed before EOF",
|
file_path.c_str(), curr_line_num, start_line_num);
|
}
|
|
static NdkSymbolType getSymbolType(const TagList& tags) {
|
for (auto&& tag : tags) {
|
if (tag == "var") {
|
return NdkSymbolType::variable;
|
}
|
}
|
return NdkSymbolType::function;
|
}
|
|
// isInArch() returns true if there is a matching arch-specific tag or there
|
// are no arch-specific tags.
|
bool isInArch(const TagList& tags) const {
|
bool has_arch_tags = false;
|
for (auto&& tag : tags) {
|
std::optional<Arch> arch = arch_from_string(tag);
|
if (!arch) {
|
continue;
|
}
|
if (*arch == compilation_type.arch) {
|
return true;
|
}
|
has_arch_tags = true;
|
}
|
return !has_arch_tags;
|
}
|
|
// isInApi() returns true if the specified API level is equal to the
|
// api-level tag, or the specified API level is greater than or equal to the
|
// introduced tag, or there are no api-level or introduced tags.
|
bool isInApi(const TagList& tags) const {
|
bool api_level_arch = false;
|
bool intro_arch = false;
|
std::string api_level;
|
std::string intro;
|
|
for (const std::string& tag : tags) {
|
// Check api-level tags.
|
if (android::base::StartsWith(tag, "api-level=") && !api_level_arch) {
|
api_level = tag;
|
continue;
|
}
|
if (android::base::StartsWith(tag, api_level_arch_prefix)) {
|
api_level = tag;
|
api_level_arch = true;
|
continue;
|
}
|
|
// Check introduced tags.
|
if (android::base::StartsWith(tag, "introduced=") && !intro_arch) {
|
intro = tag;
|
continue;
|
}
|
if (android::base::StartsWith(tag, intro_arch_perfix)) {
|
intro = tag;
|
intro_arch = true;
|
continue;
|
}
|
}
|
|
if (intro.empty() && api_level.empty()) {
|
return true;
|
}
|
|
if (!api_level.empty()) {
|
// If an api-level tag is specified, it must be an exact match (mainly
|
// for versioner unit tests).
|
return compilation_type.api_level == decodeApiLevelValue(api_level);
|
}
|
|
return compilation_type.api_level >= decodeApiLevelValue(intro);
|
}
|
|
// Extract and decode the integer API level from api-level or introduced tags.
|
static int decodeApiLevelValue(const std::string& tag) {
|
std::string api_level = tag.substr(tag.find('=') + 1);
|
auto it = api_codename_map.find(api_level);
|
if (it != api_codename_map.end()) {
|
return it->second;
|
}
|
return std::stoi(api_level);
|
}
|
|
private:
|
const std::string& file_path;
|
const CompilationType& compilation_type;
|
const std::string api_level_arch_prefix;
|
const std::string intro_arch_perfix;
|
|
std::ifstream file;
|
std::string curr_line;
|
std::vector<std::string> curr_tags;
|
size_t curr_line_num;
|
};
|
|
} // anonymous namespace
|
|
|
std::optional<SymbolMap> parseSymbolFile(const std::string& file_path,
|
const CompilationType& type) {
|
SymbolFileParser parser(file_path, type);
|
return parser.parse();
|
}
|