/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "GlobalVariableInitializationChecker.h"
#include "CustomMatchers.h"

void GlobalVariableInitializationChecker::registerMatchers(
    MatchFinder *AstMatcher) {
  auto FirstPartyGlobalVariable = varDecl(
      hasGlobalStorage(), unless(hasDeclContext(functionDecl())),
      isDefinition(), isFirstParty(), unless(isExpansionInSystemHeader()));

  auto FirstPartyGlobalVariableWithRuntimeInit =
      varDecl(FirstPartyGlobalVariable,
              anyOf(isConstexpr(), hasConstInitAttr(), hasConstantInitializer(),
                    hasMozGlobalType()),
              isMozGlobal(), unless(isMozGenerated()));

  AstMatcher->addMatcher(
      FirstPartyGlobalVariableWithRuntimeInit.bind("flagged-constinit_global"),
      this);

  auto FirstPartyGlobalVariableWithoutAnnotation =
      varDecl(FirstPartyGlobalVariable,
              unless(anyOf(isConstexpr(), hasConstInitAttr(), isMozGlobal(),
                           hasConstantInitializer(), hasMozGlobalType())));

  AstMatcher->addMatcher(
      FirstPartyGlobalVariableWithoutAnnotation.bind("non-constinit_global"),
      this);
}

void GlobalVariableInitializationChecker::check(
    const MatchFinder::MatchResult &Result) {
  if (const VarDecl *VD =
          Result.Nodes.getNodeAs<VarDecl>("flagged-constinit_global")) {
    if (VD->hasConstantInitialization()) {
      diag(VD->getBeginLoc(),
           "Global variable flagged as MOZ_RUNINIT but actually has constinit "
           "initialisation. Consider flagging it as constexpr or constinit "
           "instead.",
           DiagnosticIDs::Error);
    } else {
      diag(VD->getBeginLoc(),
           "Global variable flagged as MOZ_RUNINIT but actually has constant "
           "initialisation. Consider removing the annotation or (as a last "
           "resort) flagging it as MOZ_GLOBINIT.",
           DiagnosticIDs::Error);
    }
  }

  if (const VarDecl *VD =
          Result.Nodes.getNodeAs<VarDecl>("non-constinit_global")) {
    const SourceManager &SM = VD->getASTContext().getSourceManager();

    SourceLocation Loc = VD->getLocation();

    // Some macro from third party end up triggering warning, we can't fix that
    // easily so ignore them.
    if (SM.isMacroBodyExpansion(Loc))
      return;

    // FIXME: This captures some generated third party source, e.g. from
    // google-protocol, that are generated by third-party generators but are not
    // captured by `isFirstParty`, but may also catch some sources we own.
    if (getFilename(SM, Loc).ends_with(".cc")) {
      return;
    }
    diag(VD->getBeginLoc(),
         "Global variable has runtime initialisation, try to remove it, make "
         "it constexpr or constinit if possible, or as a last resort flag it "
         "as MOZ_RUNINIT.",
         DiagnosticIDs::Error);
  }
}
