There are some instances where we want to know _if_ a diagnostic has been emitted, but we're not interested in the actual diagnostic. Emitting the diagnostics can be rather costly, especially because we add the callstack as notes.
225 lines
7.2 KiB
C++
225 lines
7.2 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) {
|
|
EvalStatus.DiagEmitted = true;
|
|
if (EvalStatus.Diag)
|
|
return diag(E->getExprLoc(), DiagId, ExtraNotes, false);
|
|
setActiveDiagnostic(false);
|
|
return OptionalDiagnostic();
|
|
}
|
|
|
|
OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId,
|
|
unsigned ExtraNotes) {
|
|
EvalStatus.DiagEmitted = true;
|
|
if (EvalStatus.Diag)
|
|
return diag(SI.getLoc(), DiagId, ExtraNotes, false);
|
|
setActiveDiagnostic(false);
|
|
return OptionalDiagnostic();
|
|
}
|
|
|
|
OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId,
|
|
unsigned ExtraNotes) {
|
|
EvalStatus.DiagEmitted = true;
|
|
// 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();
|
|
for (const Frame *F = Top; F->getCaller() != nullptr;
|
|
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");
|
|
}
|