versioner: replace availability attributes with annotate.

Major refactor to use __attribute__((annotate)) to be able to keep
track of the semantic differences between __INTRODUCED_IN(x) and
__INTRODUCED_IN_X86(x), for use in the upcoming preprocessor.

Bug: http://b/30170081
Change-Id: I6496a8c40ba7f4553de9a2be0bbddcf37c813937
diff --git a/tools/versioner/src/versioner.cpp b/tools/versioner/src/versioner.cpp
index 7238a8c..3ae0a7b 100644
--- a/tools/versioner/src/versioner.cpp
+++ b/tools/versioner/src/versioner.cpp
@@ -37,6 +37,7 @@
 #include <clang/Tooling/Tooling.h>
 #include <llvm/ADT/StringRef.h>
 
+#include "Arch.h"
 #include "DeclarationDatabase.h"
 #include "SymbolDatabase.h"
 #include "Utils.h"
@@ -47,6 +48,7 @@
 using namespace clang::tooling;
 
 bool verbose;
+static bool add_include;
 
 class HeaderCompilationDatabase : public CompilationDatabase {
   CompilationType type;
@@ -75,9 +77,17 @@
     command.push_back("-D_FORTIFY_SOURCE=2");
     command.push_back("-D_GNU_SOURCE");
     command.push_back("-Wno-unknown-attributes");
+    command.push_back("-Wno-pragma-once-outside-header");
     command.push_back("-target");
     command.push_back(arch_targets[type.arch]);
 
+    if (add_include) {
+      const char* top = getenv("ANDROID_BUILD_TOP");
+      std::string header_path = to_string(top) + "/bionic/libc/include/android/versioning.h";
+      command.push_back("-include");
+      command.push_back(std::move(header_path));
+    }
+
     return CompileCommand(cwd, filename, command);
   }
 
@@ -105,8 +115,7 @@
   std::vector<std::string> dependencies;
 };
 
-static CompilationRequirements collectRequirements(const std::string& arch,
-                                                   const std::string& header_dir,
+static CompilationRequirements collectRequirements(const Arch& arch, const std::string& header_dir,
                                                    const std::string& dependency_dir) {
   std::vector<std::string> headers = collectFiles(header_dir);
 
@@ -143,7 +152,7 @@
     };
 
     collect_children(dependency_dir + "/common");
-    collect_children(dependency_dir + "/" + arch);
+    collect_children(dependency_dir + "/" + to_string(arch));
   }
 
   auto new_end = std::remove_if(headers.begin(), headers.end(), [&arch](llvm::StringRef header) {
@@ -165,10 +174,10 @@
   return result;
 }
 
-static std::set<CompilationType> generateCompilationTypes(
-    const std::set<std::string> selected_architectures, const std::set<int>& selected_levels) {
+static std::set<CompilationType> generateCompilationTypes(const std::set<Arch> selected_architectures,
+                                                          const std::set<int>& selected_levels) {
   std::set<CompilationType> result;
-  for (const std::string& arch : selected_architectures) {
+  for (const auto& arch : selected_architectures) {
     int min_api = arch_min_api[arch];
     for (int api_level : selected_levels) {
       if (api_level < min_api) {
@@ -181,31 +190,17 @@
   return result;
 }
 
-using DeclarationDatabase = std::map<std::string, std::map<CompilationType, Declaration>>;
-
-static DeclarationDatabase transposeHeaderDatabases(
-    const std::map<CompilationType, HeaderDatabase>& original) {
-  DeclarationDatabase result;
-  for (const auto& outer : original) {
-    const CompilationType& type = outer.first;
-    for (const auto& inner : outer.second.declarations) {
-      const std::string& symbol_name = inner.first;
-      result[symbol_name][type] = inner.second;
-    }
-  }
-  return result;
-}
-
-static DeclarationDatabase compileHeaders(const std::set<CompilationType>& types,
-                                          const std::string& header_dir,
-                                          const std::string& dependency_dir, bool* failed) {
+static std::unique_ptr<HeaderDatabase> compileHeaders(const std::set<CompilationType>& types,
+                                                      const std::string& header_dir,
+                                                      const std::string& dependency_dir,
+                                                      bool* failed) {
   constexpr size_t thread_count = 8;
   size_t threads_created = 0;
   std::mutex mutex;
   std::vector<std::thread> threads(thread_count);
 
   std::map<CompilationType, HeaderDatabase> header_databases;
-  std::unordered_map<std::string, CompilationRequirements> requirements;
+  std::unordered_map<Arch, CompilationRequirements> requirements;
 
   std::string cwd = getWorkingDir();
   bool errors = false;
@@ -216,6 +211,7 @@
     }
   }
 
+  auto result = std::make_unique<HeaderDatabase>();
   for (const auto& type : types) {
     size_t thread_id = threads_created++;
     if (thread_id >= thread_count) {
@@ -227,7 +223,6 @@
         [&](CompilationType type) {
           const auto& req = requirements[type.arch];
 
-          HeaderDatabase database;
           HeaderCompilationDatabase compilation_database(type, cwd, req.headers, req.dependencies);
 
           ClangTool tool(compilation_database, req.headers);
@@ -240,15 +235,12 @@
             if (diagnostics_engine.getNumWarnings() || diagnostics_engine.hasErrorOccurred()) {
               std::unique_lock<std::mutex> l(mutex);
               errors = true;
-              printf("versioner: compilation failure for %s in %s\n", type.describe().c_str(),
+              printf("versioner: compilation failure for %s in %s\n", to_string(type).c_str(),
                      ast->getOriginalSourceFileName().str().c_str());
             }
 
-            database.parseAST(ast.get());
+            result->parseAST(type, ast.get());
           }
-
-          std::unique_lock<std::mutex> l(mutex);
-          header_databases[type] = database;
         },
         type);
   }
@@ -266,83 +258,66 @@
     *failed = errors;
   }
 
-  return transposeHeaderDatabases(header_databases);
+  return result;
 }
 
-static bool sanityCheck(const std::set<CompilationType>& types,
-                        const DeclarationDatabase& database) {
+// Perform a sanity check on a symbol's declarations, enforcing the following invariants:
+//   1. At most one inline definition of the function exists.
+//   2. All of the availability declarations for a symbol are compatible.
+//      If a function is declared as an inline before a certain version, the inline definition
+//      should have no version tag.
+//   3. Each availability type must only be present globally or on a per-arch basis.
+//      (e.g. __INTRODUCED_IN_ARM(9) __INTRODUCED_IN_X86(10) __DEPRECATED_IN(11) is fine,
+//      but not __INTRODUCED_IN(9) __INTRODUCED_IN_X86(10))
+static bool checkSymbol(const Symbol& symbol) {
+  std::string cwd = getWorkingDir() + "/";
+
+  const Declaration* inline_definition = nullptr;
+  for (const auto& decl_it : symbol.declarations) {
+    const Declaration* decl = &decl_it.second;
+    if (decl->is_definition) {
+      if (inline_definition) {
+        fprintf(stderr, "versioner: multiple definitions of symbol %s\n", symbol.name.c_str());
+        symbol.dump(cwd);
+        inline_definition->dump(cwd);
+        return false;
+      }
+
+      inline_definition = decl;
+    }
+
+    DeclarationAvailability availability;
+    if (!decl->calculateAvailability(&availability)) {
+      fprintf(stderr, "versioner: failed to calculate availability for declaration:\n");
+      decl->dump(cwd, std::cout, 2);
+      return false;
+    }
+
+    if (decl->is_definition && !availability.empty()) {
+      fprintf(stderr, "versioner: inline definition has non-empty versioning information:\n");
+      decl->dump(cwd, std::cout, 2);
+      return false;
+    }
+  }
+
+  DeclarationAvailability availability;
+  if (!symbol.calculateAvailability(&availability)) {
+    fprintf(stderr, "versioner: inconsistent availability for symbol '%s'\n", symbol.name.c_str());
+    symbol.dump(cwd);
+    return false;
+  }
+
+  // TODO: Check invariant #3.
+  return true;
+}
+
+static bool sanityCheck(const HeaderDatabase* database) {
   bool error = false;
   std::string cwd = getWorkingDir() + "/";
 
-  for (auto outer : database) {
-    const std::string& symbol_name = outer.first;
-    CompilationType last_type;
-    DeclarationAvailability last_availability;
-
-    // Rely on std::set being sorted to loop through the types by architecture.
-    for (const CompilationType& type : types) {
-      auto inner = outer.second.find(type);
-      if (inner == outer.second.end()) {
-        // TODO: Check for holes.
-        continue;
-      }
-
-      const Declaration& declaration = inner->second;
-      bool found_availability = false;
-      bool availability_mismatch = false;
-      DeclarationAvailability current_availability;
-
-      // Ensure that all of the availability declarations for this symbol match.
-      for (const DeclarationLocation& location : declaration.locations) {
-        if (!found_availability) {
-          found_availability = true;
-          current_availability = location.availability;
-          continue;
-        }
-
-        if (current_availability != location.availability) {
-          availability_mismatch = true;
-          error = true;
-        }
-      }
-
-      if (availability_mismatch) {
-        printf("%s: availability mismatch for %s\n", symbol_name.c_str(), type.describe().c_str());
-        declaration.dump(cwd);
-      }
-
-      if (type.arch != last_type.arch) {
-        last_type = type;
-        last_availability = current_availability;
-        continue;
-      }
-
-      // Ensure that availability declarations are consistent across API levels for a given arch.
-      if (last_availability != current_availability) {
-        error = true;
-        printf("%s: availability mismatch between %s and %s: [%s] before, [%s] after\n",
-               symbol_name.c_str(), last_type.describe().c_str(), type.describe().c_str(),
-               last_availability.describe().c_str(), current_availability.describe().c_str());
-      }
-
-      // Ensure that at most one inline definition of a function exists.
-      std::set<DeclarationLocation> inline_definitions;
-
-      for (const DeclarationLocation& location : declaration.locations) {
-        if (location.is_definition) {
-          inline_definitions.insert(location);
-        }
-      }
-
-      if (inline_definitions.size() > 1) {
-        error = true;
-        printf("%s: multiple inline definitions found:\n", symbol_name.c_str());
-        for (const DeclarationLocation& location : declaration.locations) {
-          location.dump(cwd);
-        }
-      }
-
-      last_type = type;
+  for (const auto& symbol_it : database->symbols) {
+    if (!checkSymbol(symbol_it.second)) {
+      error = true;
     }
   }
   return !error;
@@ -351,172 +326,109 @@
 // Check that our symbol availability declarations match the actual NDK
 // platform symbol availability.
 static bool checkVersions(const std::set<CompilationType>& types,
-                          const DeclarationDatabase& declaration_database,
+                          const HeaderDatabase* header_database,
                           const NdkSymbolDatabase& symbol_database) {
+  std::string cwd = getWorkingDir() + "/";
   bool failed = false;
 
-  std::map<std::string, std::set<CompilationType>> arch_types;
+  std::map<Arch, std::set<CompilationType>> arch_types;
   for (const CompilationType& type : types) {
     arch_types[type.arch].insert(type);
   }
 
   std::set<std::string> completely_unavailable;
+  std::map<std::string, std::set<CompilationType>> missing_availability;
+  std::map<std::string, std::set<CompilationType>> extra_availability;
 
-  for (const auto& outer : declaration_database) {
-    const std::string& symbol_name = outer.first;
-    const auto& compilations = outer.second;
+  for (const auto& symbol_it : header_database->symbols) {
+    const auto& symbol_name = symbol_it.first;
+    DeclarationAvailability symbol_availability;
 
-    auto platform_availability_it = symbol_database.find(symbol_name);
+    if (!symbol_it.second.calculateAvailability(&symbol_availability)) {
+      errx(1, "failed to calculate symbol availability");
+    }
+
+    const auto platform_availability_it = symbol_database.find(symbol_name);
     if (platform_availability_it == symbol_database.end()) {
       completely_unavailable.insert(symbol_name);
       continue;
     }
 
     const auto& platform_availability = platform_availability_it->second;
-    std::set<CompilationType> missing_symbol;
-    std::set<CompilationType> missing_decl;
 
     for (const CompilationType& type : types) {
-      auto it = compilations.find(type);
-      if (it == compilations.end()) {
-        missing_decl.insert(type);
+      bool should_be_available = true;
+      const auto& global_availability = symbol_availability.global_availability;
+      const auto& arch_availability = symbol_availability.arch_availability[type.arch];
+      if (global_availability.introduced != 0 && global_availability.introduced > type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.introduced != 0 && arch_availability.introduced > type.api_level) {
+        should_be_available = false;
+      }
+
+      if (global_availability.obsoleted != 0 && global_availability.obsoleted <= type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.obsoleted != 0 && arch_availability.obsoleted <= type.api_level) {
+        should_be_available = false;
+      }
+
+      if (arch_availability.future) {
         continue;
       }
 
-      const Declaration& declaration = it->second;
+      // The function declaration might be (validly) missing for the given CompilationType.
+      if (!symbol_it.second.hasDeclaration(type)) {
+        should_be_available = false;
+      }
 
-      // sanityCheck ensured that the availability declarations for a given arch match.
-      DeclarationAvailability availability = declaration.locations.begin()->availability;
-      int api_level = type.api_level;
+      bool is_available = platform_availability.count(type);
 
-      int introduced = std::max(0, availability.introduced);
-      int obsoleted = availability.obsoleted == 0 ? INT_MAX : availability.obsoleted;
-      bool decl_available = api_level >= introduced && api_level < obsoleted;
-
-      auto symbol_availability_it = platform_availability.find(type);
-      bool symbol_available = symbol_availability_it != platform_availability.end();
-      if (decl_available) {
-        if (!symbol_available) {
-          // Ensure that either it exists in the platform, or an inline definition is visible.
-          if (!declaration.hasDefinition()) {
-            missing_symbol.insert(type);
-            continue;
-          }
+      if (should_be_available != is_available) {
+        if (is_available) {
+          extra_availability[symbol_name].insert(type);
         } else {
-          // Ensure that symbols declared as functions/variables actually are.
-          switch (declaration.type()) {
-            case DeclarationType::inconsistent:
-              printf("%s: inconsistent declaration type\n", symbol_name.c_str());
-              declaration.dump();
-              exit(1);
-
-            case DeclarationType::variable:
-              if (symbol_availability_it->second != NdkSymbolType::variable) {
-                printf("%s: declared as variable, exists in platform as function\n",
-                       symbol_name.c_str());
-                failed = true;
-              }
-              break;
-
-            case DeclarationType::function:
-              if (symbol_availability_it->second != NdkSymbolType::function) {
-                printf("%s: declared as function, exists in platform as variable\n",
-                       symbol_name.c_str());
-                failed = true;
-              }
-              break;
-          }
-        }
-      } else {
-        // Ensure that it's not available in the platform.
-        if (symbol_availability_it != platform_availability.end()) {
-          printf("%s: symbol should be unavailable in %s (declared with availability %s)\n",
-                 symbol_name.c_str(), type.describe().c_str(), availability.describe().c_str());
-          failed = true;
+          missing_availability[symbol_name].insert(type);
         }
       }
     }
+  }
 
-    // Allow declarations to be missing from an entire architecture.
-    for (const auto& arch_type : arch_types) {
-      const std::string& arch = arch_type.first;
-      bool found_all = true;
-      for (const auto& type : arch_type.second) {
-        if (missing_decl.find(type) == missing_decl.end()) {
-          found_all = false;
-          break;
-        }
-      }
+  for (const auto& it : symbol_database) {
+    const std::string& symbol_name = it.first;
 
-      if (!found_all) {
-        continue;
-      }
-
-      for (auto it = missing_decl.begin(); it != missing_decl.end();) {
-        if (it->arch == arch) {
-          it = missing_decl.erase(it);
-        } else {
-          ++it;
-        }
-      }
-    }
-
-    auto types_to_string = [](const std::set<CompilationType>& types) {
-      std::string result;
-      for (const CompilationType& type : types) {
-        result += type.describe();
-        result += ", ";
-      }
-      result.resize(result.length() - 2);
-      return result;
-    };
-
-    if (!missing_decl.empty()) {
-      printf("%s: declaration missing in %s\n", symbol_name.c_str(),
-             types_to_string(missing_decl).c_str());
-      failed = true;
-    }
-
-    if (!missing_symbol.empty()) {
+    bool symbol_error = false;
+    auto missing_it = missing_availability.find(symbol_name);
+    if (missing_it != missing_availability.end()) {
       printf("%s: declaration marked available but symbol missing in [%s]\n", symbol_name.c_str(),
-             types_to_string(missing_symbol).c_str());
+             Join(missing_it->second, ", ").c_str());
+      symbol_error = true;
       failed = true;
     }
-  }
 
-  for (const std::string& symbol_name : completely_unavailable) {
-    bool found_inline_definition = false;
-    bool future = false;
-
-    auto symbol_it = declaration_database.find(symbol_name);
-
-    // Ignore inline functions and functions that are tagged as __INTRODUCED_IN_FUTURE.
-    // Ensure that all of the declarations of that function satisfy that.
-    for (const auto& declaration_pair : symbol_it->second) {
-      const Declaration& declaration = declaration_pair.second;
-      DeclarationAvailability availability = declaration.locations.begin()->availability;
-
-      if (availability.introduced >= 10000) {
-        future = true;
-      }
-
-      if (declaration.hasDefinition()) {
-        found_inline_definition = true;
+    if (verbose) {
+      auto extra_it = extra_availability.find(symbol_name);
+      if (extra_it != extra_availability.end()) {
+        printf("%s: declaration marked unavailable but symbol available in [%s]\n",
+               symbol_name.c_str(), Join(extra_it->second, ", ").c_str());
+        symbol_error = true;
+        failed = true;
       }
     }
 
-    if (future || found_inline_definition) {
-      continue;
+    if (symbol_error) {
+      auto symbol_it = header_database->symbols.find(symbol_name);
+      if (symbol_it == header_database->symbols.end()) {
+        errx(1, "failed to find symbol in header database");
+      }
+      symbol_it->second.dump(cwd);
     }
-
-    if (missing_symbol_whitelist.count(symbol_name) != 0) {
-      continue;
-    }
-
-    printf("%s: not available in any platform\n", symbol_name.c_str());
-    failed = true;
   }
 
+  // TODO: Verify that function/variable declarations are actually function/variable symbols.
   return !failed;
 }
 
@@ -540,6 +452,7 @@
     fprintf(stderr, "  -v\t\tenable verbose warnings\n");
     fprintf(stderr, "\n");
     fprintf(stderr, "Miscellaneous:\n");
+    fprintf(stderr, "  -d\t\tdump function availability\n");
     fprintf(stderr, "  -h\t\tdisplay this message\n");
     exit(0);
   }
@@ -549,11 +462,12 @@
   std::string cwd = getWorkingDir() + "/";
   bool default_args = true;
   std::string platform_dir;
-  std::set<std::string> selected_architectures;
+  std::set<Arch> selected_architectures;
   std::set<int> selected_levels;
+  bool dump = false;
 
   int c;
-  while ((c = getopt(argc, argv, "a:r:p:vh")) != -1) {
+  while ((c = getopt(argc, argv, "a:r:p:vdhi")) != -1) {
     default_args = false;
     switch (c) {
       case 'a': {
@@ -572,10 +486,8 @@
       }
 
       case 'r': {
-        if (supported_archs.count(optarg) == 0) {
-          errx(1, "unsupported architecture: %s", optarg);
-        }
-        selected_architectures.insert(optarg);
+        Arch arch = arch_from_string(optarg);
+        selected_architectures.insert(arch);
         break;
       }
 
@@ -600,10 +512,19 @@
         verbose = true;
         break;
 
+      case 'd':
+        dump = true;
+        break;
+
       case 'h':
         usage(true);
         break;
 
+      case 'i':
+        // Secret option for tests to -include <android/versioning.h>.
+        add_include = true;
+        break;
+
       default:
         usage();
         break;
@@ -617,15 +538,15 @@
   std::string header_dir;
   std::string dependency_dir;
 
+  const char* top = getenv("ANDROID_BUILD_TOP");
+  if (!top && (optind == argc || add_include)) {
+    fprintf(stderr, "versioner: failed to autodetect bionic paths. Is ANDROID_BUILD_TOP set?\n");
+    usage();
+  }
+
   if (optind == argc) {
     // Neither HEADER_PATH nor DEPS_PATH were specified, so try to figure them out.
-    const char* top = getenv("ANDROID_BUILD_TOP");
-    if (!top) {
-      fprintf(stderr, "versioner: failed to autodetect bionic paths. Is ANDROID_BUILD_TOP set?\n");
-      usage();
-    }
-
-    std::string versioner_dir = std::to_string(top) + "/bionic/tools/versioner";
+    std::string versioner_dir = to_string(top) + "/bionic/tools/versioner";
     header_dir = versioner_dir + "/current";
     dependency_dir = versioner_dir + "/dependencies";
     if (platform_dir.empty()) {
@@ -656,7 +577,6 @@
   }
 
   std::set<CompilationType> compilation_types;
-  DeclarationDatabase declaration_database;
   NdkSymbolDatabase symbol_database;
 
   compilation_types = generateCompilationTypes(selected_architectures, selected_levels);
@@ -668,19 +588,23 @@
   }
 
   bool failed = false;
-  declaration_database = compileHeaders(compilation_types, header_dir, dependency_dir, &failed);
+  std::unique_ptr<HeaderDatabase> declaration_database =
+      compileHeaders(compilation_types, header_dir, dependency_dir, &failed);
 
-  if (!sanityCheck(compilation_types, declaration_database)) {
-    printf("versioner: sanity check failed\n");
-    failed = true;
-  }
-
-  if (!platform_dir.empty()) {
-    if (!checkVersions(compilation_types, declaration_database, symbol_database)) {
-      printf("versioner: version check failed\n");
+  if (dump) {
+    declaration_database->dump(header_dir + "/");
+  } else {
+    if (!sanityCheck(declaration_database.get())) {
+      printf("versioner: sanity check failed\n");
       failed = true;
     }
-  }
 
+    if (!platform_dir.empty()) {
+      if (!checkVersions(compilation_types, declaration_database.get(), symbol_database)) {
+        printf("versioner: version check failed\n");
+        failed = true;
+      }
+    }
+  }
   return failed;
 }