Files
Timm Baeder 136bbde262 [clang][ExprConst] Move shared EvalInfo state into interp::State (#177738)
Instead of having `InterpState` call into its parent `EvalInfo`, just
save the state in `interp::State`, where both subclasses can access it.
2026-02-02 09:00:10 +01:00

222 lines
7.1 KiB
C++

//===--- 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<PartialDiagnosticAt> 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<CXXConstructorDecl>(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");
}