versioner: introduce.
Add a clang-based tool to inspect header availability attributes and
verify them against the NDK platform definitions.
Bug: http://b/28178111
Change-Id: I1bb1925a620e98cc9606cb5a3360b1224c700bd0
diff --git a/tools/versioner/src/DeclarationDatabase.cpp b/tools/versioner/src/DeclarationDatabase.cpp
new file mode 100644
index 0000000..9f02588
--- /dev/null
+++ b/tools/versioner/src/DeclarationDatabase.cpp
@@ -0,0 +1,179 @@
+/*
+ * Copyright 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 <iostream>
+#include <map>
+#include <set>
+#include <string>
+
+#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;
+
+class Visitor : public RecursiveASTVisitor<Visitor> {
+ HeaderDatabase& database;
+ SourceManager& src_manager;
+ std::unique_ptr<MangleContext> mangler;
+
+ public:
+ Visitor(HeaderDatabase& database, ASTContext& ctx)
+ : database(database), 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>";
+ }
+ }
+
+ if (mangler->shouldMangleDeclName(decl)) {
+ std::string mangled;
+ llvm::raw_string_ostream ss(mangled);
+ mangler->mangleName(decl, ss);
+ return mangled;
+ }
+
+ auto identifier = decl->getIdentifier();
+ if (!identifier) {
+ return "<error>";
+ }
+ return identifier->getName();
+ }
+
+ bool VisitDecl(Decl* decl) {
+ // 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;
+ }
+
+ DeclarationType declaration_type;
+ std::string declaration_name = getDeclName(named_decl);
+ bool is_extern = named_decl->getFormalLinkage() == ExternalLinkage;
+ bool is_definition = false;
+
+ if (auto function_decl = dyn_cast<FunctionDecl>(decl)) {
+ declaration_type = DeclarationType::function;
+ is_definition = function_decl->isThisDeclarationADefinition();
+ } else if (auto var_decl = dyn_cast<VarDecl>(decl)) {
+ if (!var_decl->isFileVarDecl()) {
+ return true;
+ }
+
+ declaration_type = DeclarationType::variable;
+ 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;
+ }
+
+ // Look for availability annotations.
+ DeclarationAvailability availability;
+ for (const AvailabilityAttr* attr : decl->specific_attrs<AvailabilityAttr>()) {
+ if (attr->getPlatform()->getName() != "android") {
+ fprintf(stderr, "skipping non-android platform %s\n",
+ attr->getPlatform()->getName().str().c_str());
+ continue;
+ }
+ if (attr->getIntroduced().getMajor() != 0) {
+ availability.introduced = attr->getIntroduced().getMajor();
+ }
+ if (attr->getDeprecated().getMajor() != 0) {
+ availability.deprecated = attr->getDeprecated().getMajor();
+ }
+ if (attr->getObsoleted().getMajor() != 0) {
+ availability.obsoleted = attr->getObsoleted().getMajor();
+ }
+ }
+
+ // Find or insert an entry for the declaration.
+ auto declaration_it = database.declarations.find(declaration_name);
+ if (declaration_it == database.declarations.end()) {
+ Declaration declaration = {.name = declaration_name };
+ bool inserted;
+ std::tie(declaration_it, inserted) =
+ database.declarations.insert({ declaration_name, declaration });
+ }
+
+ auto& declaration_locations = declaration_it->second.locations;
+ auto presumed_loc = src_manager.getPresumedLoc(decl->getLocation());
+ DeclarationLocation location = {
+ .filename = presumed_loc.getFilename(),
+ .line_number = presumed_loc.getLine(),
+ .column = presumed_loc.getColumn(),
+ .type = declaration_type,
+ .is_extern = is_extern,
+ .is_definition = is_definition,
+ .availability = availability,
+ };
+
+ // It's fine if the location is already there, we'll get an iterator to the existing element.
+ auto location_it = declaration_locations.begin();
+ bool inserted = false;
+ std::tie(location_it, inserted) = declaration_locations.insert(location);
+
+ // If we didn't insert, check to see if the availability attributes are identical.
+ if (!inserted) {
+ if (location_it->availability != availability) {
+ fprintf(stderr, "ERROR: availability attribute mismatch\n");
+ decl->dump();
+ abort();
+ }
+ }
+
+ return true;
+ }
+};
+
+void HeaderDatabase::parseAST(ASTUnit* ast) {
+ ASTContext& ctx = ast->getASTContext();
+ Visitor visitor(*this, ctx);
+ visitor.TraverseDecl(ctx.getTranslationUnitDecl());
+}