/* 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())),
      allOf(isDefinition(), isFirstParty(), unless(isExpansionInSystemHeader()))
  );

  auto FirstPartyGlobalVariableWithRuntimeInit = varDecl(FirstPartyGlobalVariable,
      allOf(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 MOZ_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 MOZ_CONSTINIT if possible, or as a last resort flag it as MOZ_RUNINIT.",
         DiagnosticIDs::Error);
  }
}
