//===----- NondeterministicPointerIterationOrderCheck.cpp - clang-tidy ----===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "NondeterministicPointerIterationOrderCheck.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::bugprone {

void NondeterministicPointerIterationOrderCheck::registerMatchers(
    MatchFinder *Finder) {

  auto LoopVariable = varDecl(hasType(
      qualType(hasCanonicalType(anyOf(referenceType(), pointerType())))));

  auto RangeInit = declRefExpr(to(varDecl(
      hasType(recordDecl(hasAnyName("std::unordered_set", "std::unordered_map",
                                    "std::unordered_multiset",
                                    "std::unordered_multimap"))
                  .bind("recorddecl")))));

  Finder->addMatcher(cxxForRangeStmt(hasLoopVariable(LoopVariable),
                                     hasRangeInit(RangeInit.bind("rangeinit")))
                         .bind("cxxForRangeStmt"),
                     this);

  auto SortFuncM = callee(functionDecl(hasAnyName(
      "std::is_sorted", "std::nth_element", "std::sort", "std::partial_sort",
      "std::partition", "std::stable_partition", "std::stable_sort")));

  auto IteratesPointerEltsM = hasArgument(
      0,
      cxxMemberCallExpr(on(hasType(cxxRecordDecl(has(fieldDecl(hasType(qualType(
          hasCanonicalType(pointsTo(hasCanonicalType(pointerType()))))))))))));

  Finder->addMatcher(
      callExpr(allOf(SortFuncM, IteratesPointerEltsM)).bind("sortsemantic"),
      this);
}

void NondeterministicPointerIterationOrderCheck::check(
    const MatchFinder::MatchResult &Result) {
  const auto *ForRangePointers =
      Result.Nodes.getNodeAs<CXXForRangeStmt>("cxxForRangeStmt");

  if ((ForRangePointers) && !(ForRangePointers->getBeginLoc().isMacroID())) {
    const auto *RangeInit = Result.Nodes.getNodeAs<Stmt>("rangeinit");
    if (const auto *ClassTemplate =
            Result.Nodes.getNodeAs<ClassTemplateSpecializationDecl>(
                "recorddecl")) {
      const TemplateArgumentList &TemplateArgs =
          ClassTemplate->getTemplateArgs();
      const bool IsAlgoArgPointer =
          TemplateArgs[0].getAsType()->isPointerType();

      if (IsAlgoArgPointer) {
        SourceRange R = RangeInit->getSourceRange();
        diag(R.getBegin(), "iteration of pointers is nondeterministic") << R;
      }
    }
    return;
  }
  const auto *SortPointers = Result.Nodes.getNodeAs<Stmt>("sortsemantic");

  if ((SortPointers) && !(SortPointers->getBeginLoc().isMacroID())) {
    SourceRange R = SortPointers->getSourceRange();
    diag(R.getBegin(), "sorting pointers is nondeterministic") << R;
  }
}

} // namespace clang::tidy::bugprone
