| /* |
| * Copyright (C) 2016 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 "DeclarationDatabase.h" |
| |
| #include <err.h> |
| |
| #include <iostream> |
| #include <map> |
| #include <mutex> |
| #include <set> |
| #include <sstream> |
| #include <string> |
| #include <utility> |
| |
| #include <clang/AST/AST.h> |
| #include <clang/AST/Attr.h> |
| #include <clang/AST/Mangle.h> |
| #include <clang/AST/RecursiveASTVisitor.h> |
| #include <clang/Frontend/ASTUnit.h> |
| #include <llvm/Support/raw_ostream.h> |
| |
| using namespace clang; |
| |
| static bool shouldMangle(MangleContext* mangler, NamedDecl* decl) { |
| // Passing a decl with static linkage to the mangler gives incorrect results. |
| // Check some things ourselves before handing it off to the mangler. |
| if (auto FD = dyn_cast<FunctionDecl>(decl)) { |
| if (FD->isExternC()) { |
| return false; |
| } |
| |
| if (FD->isInExternCContext()) { |
| return false; |
| } |
| } |
| |
| return mangler->shouldMangleDeclName(decl); |
| } |
| |
| class Visitor : public RecursiveASTVisitor<Visitor> { |
| HeaderDatabase& database; |
| CompilationType type; |
| SourceManager& src_manager; |
| std::unique_ptr<MangleContext> mangler; |
| |
| public: |
| Visitor(HeaderDatabase& database, CompilationType type, ASTContext& ctx) |
| : database(database), type(type), src_manager(ctx.getSourceManager()) { |
| mangler.reset(ItaniumMangleContext::create(ctx, ctx.getDiagnostics())); |
| } |
| |
| std::string getDeclName(NamedDecl* decl) { |
| if (auto var_decl = dyn_cast<VarDecl>(decl)) { |
| if (!var_decl->isFileVarDecl()) { |
| return "<local var>"; |
| } |
| } |
| |
| // <math.h> maps fool onto foo on 32-bit, since long double is the same as double. |
| if (auto asm_attr = decl->getAttr<AsmLabelAttr>()) { |
| return asm_attr->getLabel().str(); |
| } |
| |
| // The decl might not have a name (e.g. bitfields). |
| if (auto identifier = decl->getIdentifier()) { |
| if (shouldMangle(mangler.get(), decl)) { |
| std::string mangled; |
| llvm::raw_string_ostream ss(mangled); |
| mangler->mangleName(decl, ss); |
| return mangled; |
| } |
| |
| return identifier->getName().str(); |
| } |
| |
| return "<unnamed>"; |
| } |
| |
| bool VisitDeclaratorDecl(DeclaratorDecl* decl, SourceRange range) { |
| // Skip declarations inside of functions (function arguments, variable declarations inside of |
| // inline functions, etc). |
| if (decl->getParentFunctionOrMethod()) { |
| return true; |
| } |
| |
| auto named_decl = dyn_cast<NamedDecl>(decl); |
| if (!named_decl) { |
| return true; |
| } |
| |
| std::string declaration_name = getDeclName(named_decl); |
| bool is_extern = named_decl->getFormalLinkage() == ExternalLinkage; |
| bool is_definition = false; |
| bool no_guard = false; |
| bool fortify_inline = false; |
| |
| if (auto function_decl = dyn_cast<FunctionDecl>(decl)) { |
| is_definition = function_decl->isThisDeclarationADefinition(); |
| } else if (auto var_decl = dyn_cast<VarDecl>(decl)) { |
| if (!var_decl->isFileVarDecl()) { |
| return true; |
| } |
| |
| switch (var_decl->isThisDeclarationADefinition()) { |
| case VarDecl::DeclarationOnly: |
| is_definition = false; |
| break; |
| |
| case VarDecl::Definition: |
| is_definition = true; |
| break; |
| |
| case VarDecl::TentativeDefinition: |
| // Forbid tentative definitions in headers. |
| fprintf(stderr, "ERROR: declaration '%s' is a tentative definition\n", |
| declaration_name.c_str()); |
| decl->dump(); |
| abort(); |
| } |
| } else { |
| // We only care about function and variable declarations. |
| return true; |
| } |
| |
| if (decl->hasAttr<UnavailableAttr>()) { |
| // Skip declarations that exist only for compile-time diagnostics. |
| return true; |
| } |
| |
| DeclarationAvailability availability; |
| |
| // Find and parse __ANDROID_AVAILABILITY_DUMP__ annotations. |
| for (const AnnotateAttr* attr : decl->specific_attrs<AnnotateAttr>()) { |
| llvm::StringRef annotation = attr->getAnnotation(); |
| if (annotation == "versioner_no_guard") { |
| no_guard = true; |
| } else if (annotation == "versioner_fortify_inline") { |
| fortify_inline = true; |
| } else { |
| llvm::SmallVector<llvm::StringRef, 2> fragments; |
| annotation.split(fragments, "="); |
| if (fragments.size() != 2) { |
| continue; |
| } |
| |
| auto& global_availability = availability.global_availability; |
| auto& arch_availability = availability.arch_availability; |
| std::map<std::string, std::vector<int*>> prefix_map = { |
| { "introduced_in", { &global_availability.introduced } }, |
| { "deprecated_in", { &global_availability.deprecated } }, |
| { "obsoleted_in", { &global_availability.obsoleted } }, |
| { "introduced_in_arm", { &arch_availability[Arch::arm].introduced } }, |
| { "introduced_in_x86", { &arch_availability[Arch::x86].introduced } }, |
| { "introduced_in_32", |
| { &arch_availability[Arch::arm].introduced, |
| &arch_availability[Arch::x86].introduced } }, |
| { "introduced_in_64", |
| { &arch_availability[Arch::arm64].introduced, |
| &arch_availability[Arch::riscv64].introduced, |
| &arch_availability[Arch::x86_64].introduced } }, |
| }; |
| |
| if (auto it = prefix_map.find(fragments[0].str()); it != prefix_map.end()) { |
| int value; |
| if (fragments[1].getAsInteger(10, value)) { |
| errx(1, "invalid __ANDROID_AVAILABILITY_DUMP__ annotation: '%s'", |
| annotation.str().c_str()); |
| } |
| |
| for (int* ptr : it->second) { |
| *ptr = value; |
| } |
| } |
| } |
| } |
| |
| auto symbol_it = database.symbols.find(declaration_name); |
| if (symbol_it == database.symbols.end()) { |
| Symbol symbol = {.name = declaration_name }; |
| bool unused; |
| std::tie(symbol_it, unused) = database.symbols.insert({declaration_name, symbol}); |
| } |
| |
| auto expansion_range = src_manager.getExpansionRange(range); |
| auto filename = src_manager.getFilename(expansion_range.getBegin()); |
| if (filename != src_manager.getFilename(expansion_range.getEnd())) { |
| errx(1, "expansion range filenames don't match"); |
| } |
| |
| Location location = { |
| .filename = filename.str(), |
| .start = { |
| .line = src_manager.getExpansionLineNumber(expansion_range.getBegin()), |
| .column = src_manager.getExpansionColumnNumber(expansion_range.getBegin()), |
| }, |
| .end = { |
| .line = src_manager.getExpansionLineNumber(expansion_range.getEnd()), |
| .column = src_manager.getExpansionColumnNumber(expansion_range.getEnd()), |
| } |
| }; |
| |
| // Find or insert an entry for the declaration. |
| if (auto declaration_it = symbol_it->second.declarations.find(location); |
| declaration_it != symbol_it->second.declarations.end()) { |
| if (declaration_it->second.is_extern != is_extern || |
| declaration_it->second.is_definition != is_definition || |
| declaration_it->second.no_guard != no_guard || |
| declaration_it->second.fortify_inline != fortify_inline) { |
| errx(1, "varying declaration of '%s' at %s:%u:%u", declaration_name.c_str(), |
| location.filename.c_str(), location.start.line, location.start.column); |
| } |
| declaration_it->second.availability.insert(std::make_pair(type, availability)); |
| } else { |
| Declaration declaration; |
| declaration.name = declaration_name; |
| declaration.location = location; |
| declaration.is_extern = is_extern; |
| declaration.is_definition = is_definition; |
| declaration.no_guard = no_guard; |
| declaration.fortify_inline = fortify_inline; |
| declaration.availability.insert(std::make_pair(type, availability)); |
| symbol_it->second.declarations.insert(std::make_pair(location, declaration)); |
| } |
| |
| return true; |
| } |
| |
| bool VisitDeclaratorDecl(DeclaratorDecl* decl) { |
| return VisitDeclaratorDecl(decl, decl->getSourceRange()); |
| } |
| |
| bool TraverseLinkageSpecDecl(LinkageSpecDecl* decl) { |
| // Make sure that we correctly calculate the SourceRange of a declaration that has a non-braced |
| // extern "C"/"C++". |
| if (!decl->hasBraces()) { |
| DeclaratorDecl* child = nullptr; |
| for (auto child_decl : decl->decls()) { |
| if (child != nullptr) { |
| errx(1, "LinkageSpecDecl has multiple children"); |
| } |
| |
| if (DeclaratorDecl* declarator_decl = dyn_cast<DeclaratorDecl>(child_decl)) { |
| child = declarator_decl; |
| } else { |
| errx(1, "child of LinkageSpecDecl is not a DeclaratorDecl"); |
| } |
| } |
| |
| return VisitDeclaratorDecl(child, decl->getSourceRange()); |
| } |
| |
| for (auto child : decl->decls()) { |
| if (!TraverseDecl(child)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| }; |
| |
| bool DeclarationAvailability::merge(const DeclarationAvailability& other) { |
| #define check_avail(expr) error |= (!this->expr.empty() && this->expr != other.expr); |
| bool error = false; |
| |
| if (!other.global_availability.empty()) { |
| check_avail(global_availability); |
| this->global_availability = other.global_availability; |
| } |
| |
| for (Arch arch : supported_archs) { |
| if (!other.arch_availability[arch].empty()) { |
| check_avail(arch_availability[arch]); |
| this->arch_availability[arch] = other.arch_availability[arch]; |
| } |
| } |
| #undef check_avail |
| |
| return !error; |
| } |
| |
| bool Declaration::calculateAvailability(DeclarationAvailability* output) const { |
| DeclarationAvailability avail; |
| for (const auto& it : this->availability) { |
| if (!avail.merge(it.second)) { |
| return false; |
| } |
| } |
| *output = avail; |
| return true; |
| } |
| |
| bool Symbol::calculateAvailability(DeclarationAvailability* output) const { |
| DeclarationAvailability avail; |
| for (const auto& it : this->declarations) { |
| // Don't merge availability for inline functions (because they shouldn't have any). |
| if (it.second.is_definition) { |
| continue; |
| } |
| |
| DeclarationAvailability decl_availability; |
| if (!it.second.calculateAvailability(&decl_availability)) { |
| return false; |
| abort(); |
| } |
| |
| if (!avail.merge(decl_availability)) { |
| return false; |
| } |
| } |
| *output = avail; |
| return true; |
| } |
| |
| bool Symbol::hasDeclaration(const CompilationType& type) const { |
| for (const auto& decl_it : this->declarations) { |
| for (const auto& compilation_it : decl_it.second.availability) { |
| if (compilation_it.first == type) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| void HeaderDatabase::parseAST(CompilationType type, ASTContext& ctx) { |
| std::unique_lock<std::mutex> lock(this->mutex); |
| Visitor visitor(*this, type, ctx); |
| visitor.TraverseDecl(ctx.getTranslationUnitDecl()); |
| } |
| |
| std::string to_string(const AvailabilityValues& av) { |
| std::stringstream ss; |
| |
| if (av.introduced != 0) { |
| ss << "introduced = " << av.introduced << ", "; |
| } |
| |
| if (av.deprecated != 0) { |
| ss << "deprecated = " << av.deprecated << ", "; |
| } |
| |
| if (av.obsoleted != 0) { |
| ss << "obsoleted = " << av.obsoleted << ", "; |
| } |
| |
| std::string result = ss.str(); |
| if (!result.empty()) { |
| result = result.substr(0, result.length() - 2); |
| } |
| return result; |
| } |
| |
| std::string to_string(const DeclarationType& type) { |
| switch (type) { |
| case DeclarationType::function: |
| return "function"; |
| case DeclarationType::variable: |
| return "variable"; |
| case DeclarationType::inconsistent: |
| return "inconsistent"; |
| } |
| abort(); |
| } |
| |
| std::string to_string(const DeclarationAvailability& decl_av) { |
| std::stringstream ss; |
| if (!decl_av.global_availability.empty()) { |
| ss << to_string(decl_av.global_availability) << ", "; |
| } |
| |
| for (const auto& it : decl_av.arch_availability) { |
| if (!it.second.empty()) { |
| ss << to_string(it.first) << ": " << to_string(it.second) << ", "; |
| } |
| } |
| |
| std::string result = ss.str(); |
| if (result.size() == 0) { |
| return "no availability"; |
| } |
| |
| return result.substr(0, result.length() - 2); |
| } |
| |
| std::string to_string(const Location& loc) { |
| std::stringstream ss; |
| ss << loc.filename << ":" << loc.start.line << ":" << loc.start.column; |
| return ss.str(); |
| } |