//===--- State.cpp - State chain for the VM and AST Walker ------*- C++ -*-===// // // 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 "State.h" #include "Frame.h" #include "Program.h" #include "clang/AST/ASTContext.h" #include "clang/AST/CXXInheritance.h" #include "clang/AST/OptionalDiagnostic.h" using namespace clang; using namespace clang::interp; State::~State() {} OptionalDiagnostic State::FFDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { return diag(Loc, DiagId, ExtraNotes, false); } OptionalDiagnostic State::FFDiag(const Expr *E, diag::kind DiagId, unsigned ExtraNotes) { if (EvalStatus.Diag) return diag(E->getExprLoc(), DiagId, ExtraNotes, false); setActiveDiagnostic(false); return OptionalDiagnostic(); } OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId, unsigned ExtraNotes) { if (EvalStatus.Diag) return diag(SI.getLoc(), DiagId, ExtraNotes, false); setActiveDiagnostic(false); return OptionalDiagnostic(); } OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes) { // Don't override a previous diagnostic. Don't bother collecting // diagnostics if we're evaluating for overflow. if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) { setActiveDiagnostic(false); return OptionalDiagnostic(); } return diag(Loc, DiagId, ExtraNotes, true); } OptionalDiagnostic State::CCEDiag(const Expr *E, diag::kind DiagId, unsigned ExtraNotes) { return CCEDiag(E->getExprLoc(), DiagId, ExtraNotes); } OptionalDiagnostic State::CCEDiag(SourceInfo SI, diag::kind DiagId, unsigned ExtraNotes) { return CCEDiag(SI.getLoc(), DiagId, ExtraNotes); } OptionalDiagnostic State::Note(SourceLocation Loc, diag::kind DiagId) { if (!hasActiveDiagnostic()) return OptionalDiagnostic(); return OptionalDiagnostic(&addDiag(Loc, DiagId)); } void State::addNotes(ArrayRef Diags) { if (hasActiveDiagnostic()) llvm::append_range(*EvalStatus.Diag, Diags); } DiagnosticBuilder State::report(SourceLocation Loc, diag::kind DiagId) { return Ctx.getDiagnostics().Report(Loc, DiagId); } /// Add a diagnostic to the diagnostics list. PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) { PartialDiagnostic PD(DiagId, Ctx.getDiagAllocator()); EvalStatus.Diag->push_back(std::make_pair(Loc, PD)); return EvalStatus.Diag->back().second; } OptionalDiagnostic State::diag(SourceLocation Loc, diag::kind DiagId, unsigned ExtraNotes, bool IsCCEDiag) { if (EvalStatus.Diag) { if (hasPriorDiagnostic()) { return OptionalDiagnostic(); } unsigned CallStackNotes = getCallStackDepth() - 1; unsigned Limit = Ctx.getDiagnostics().getConstexprBacktraceLimit(); if (Limit) CallStackNotes = std::min(CallStackNotes, Limit + 1); if (checkingPotentialConstantExpression()) CallStackNotes = 0; setActiveDiagnostic(true); setFoldFailureDiagnostic(!IsCCEDiag); EvalStatus.Diag->clear(); EvalStatus.Diag->reserve(1 + ExtraNotes + CallStackNotes); addDiag(Loc, DiagId); if (!checkingPotentialConstantExpression()) { addCallStack(Limit); } return OptionalDiagnostic(&(*EvalStatus.Diag)[0].second); } setActiveDiagnostic(false); return OptionalDiagnostic(); } void State::addCallStack(unsigned Limit) { // Determine which calls to skip, if any. unsigned ActiveCalls = getCallStackDepth() - 1; unsigned SkipStart = ActiveCalls, SkipEnd = SkipStart; if (Limit && Limit < ActiveCalls) { SkipStart = Limit / 2 + Limit % 2; SkipEnd = ActiveCalls - Limit / 2; } // Walk the call stack and add the diagnostics. unsigned CallIdx = 0; const Frame *Top = getCurrentFrame(); const Frame *Bottom = getBottomFrame(); for (const Frame *F = Top; F != Bottom; F = F->getCaller(), ++CallIdx) { SourceRange CallRange = F->getCallRange(); assert(CallRange.isValid()); // Skip this call? if (CallIdx >= SkipStart && CallIdx < SkipEnd) { if (CallIdx == SkipStart) { // Note that we're skipping calls. addDiag(CallRange.getBegin(), diag::note_constexpr_calls_suppressed) << unsigned(ActiveCalls - Limit); } continue; } // Use a different note for an inheriting constructor, because from the // user's perspective it's not really a function at all. if (const auto *CD = dyn_cast_if_present(F->getCallee()); CD && CD->isInheritingConstructor()) { addDiag(CallRange.getBegin(), diag::note_constexpr_inherited_ctor_call_here) << CD->getParent(); continue; } SmallString<128> Buffer; llvm::raw_svector_ostream Out(Buffer); F->describe(Out); if (!Buffer.empty()) addDiag(CallRange.getBegin(), diag::note_constexpr_call_here) << Out.str() << CallRange; } } bool State::hasPriorDiagnostic() { if (!EvalStatus.Diag->empty()) { switch (EvalMode) { case EvaluationMode::ConstantFold: case EvaluationMode::IgnoreSideEffects: if (!HasFoldFailureDiagnostic) break; // We've already failed to fold something. Keep that diagnostic. [[fallthrough]]; case EvaluationMode::ConstantExpression: case EvaluationMode::ConstantExpressionUnevaluated: setActiveDiagnostic(false); return true; } } return false; } bool State::keepEvaluatingAfterFailure() const { uint64_t Limit = Ctx.getLangOpts().ConstexprStepLimit; if (Limit != 0 && !stepsLeft()) return false; switch (EvalMode) { case EvaluationMode::ConstantExpression: case EvaluationMode::ConstantExpressionUnevaluated: case EvaluationMode::ConstantFold: case EvaluationMode::IgnoreSideEffects: return checkingPotentialConstantExpression() || checkingForUndefinedBehavior(); } llvm_unreachable("Missed EvalMode case"); } bool State::keepEvaluatingAfterSideEffect() const { switch (EvalMode) { case EvaluationMode::IgnoreSideEffects: return true; case EvaluationMode::ConstantExpression: case EvaluationMode::ConstantExpressionUnevaluated: case EvaluationMode::ConstantFold: // By default, assume any side effect might be valid in some other // evaluation of this expression from a different context. return checkingPotentialConstantExpression() || checkingForUndefinedBehavior(); } llvm_unreachable("Missed EvalMode case"); } bool State::keepEvaluatingAfterUndefinedBehavior() const { switch (EvalMode) { case EvaluationMode::IgnoreSideEffects: case EvaluationMode::ConstantFold: return true; case EvaluationMode::ConstantExpression: case EvaluationMode::ConstantExpressionUnevaluated: return checkingForUndefinedBehavior(); } llvm_unreachable("Missed EvalMode case"); }