1//===--- State.cpp - State chain for the VM and AST Walker ------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "State.h"
10#include "Frame.h"
11#include "Program.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/CXXInheritance.h"
14#include "clang/AST/OptionalDiagnostic.h"
15
16using namespace clang;
17using namespace clang::interp;
18
19State::~State() {}
20
21OptionalDiagnostic State::FFDiag(SourceLocation Loc, diag::kind DiagId,
22 unsigned ExtraNotes) {
23 return diag(Loc, DiagId, ExtraNotes, IsCCEDiag: false);
24}
25
26OptionalDiagnostic State::FFDiag(const Expr *E, diag::kind DiagId,
27 unsigned ExtraNotes) {
28 if (EvalStatus.Diag)
29 return diag(Loc: E->getExprLoc(), DiagId, ExtraNotes, IsCCEDiag: false);
30 setActiveDiagnostic(false);
31 return OptionalDiagnostic();
32}
33
34OptionalDiagnostic State::FFDiag(SourceInfo SI, diag::kind DiagId,
35 unsigned ExtraNotes) {
36 if (EvalStatus.Diag)
37 return diag(Loc: SI.getLoc(), DiagId, ExtraNotes, IsCCEDiag: false);
38 setActiveDiagnostic(false);
39 return OptionalDiagnostic();
40}
41
42OptionalDiagnostic State::CCEDiag(SourceLocation Loc, diag::kind DiagId,
43 unsigned ExtraNotes) {
44 // Don't override a previous diagnostic. Don't bother collecting
45 // diagnostics if we're evaluating for overflow.
46 if (!EvalStatus.Diag || !EvalStatus.Diag->empty()) {
47 setActiveDiagnostic(false);
48 return OptionalDiagnostic();
49 }
50 return diag(Loc, DiagId, ExtraNotes, IsCCEDiag: true);
51}
52
53OptionalDiagnostic State::CCEDiag(const Expr *E, diag::kind DiagId,
54 unsigned ExtraNotes) {
55 return CCEDiag(Loc: E->getExprLoc(), DiagId, ExtraNotes);
56}
57
58OptionalDiagnostic State::CCEDiag(SourceInfo SI, diag::kind DiagId,
59 unsigned ExtraNotes) {
60 return CCEDiag(Loc: SI.getLoc(), DiagId, ExtraNotes);
61}
62
63OptionalDiagnostic State::Note(SourceLocation Loc, diag::kind DiagId) {
64 if (!hasActiveDiagnostic())
65 return OptionalDiagnostic();
66 return OptionalDiagnostic(&addDiag(Loc, DiagId));
67}
68
69void State::addNotes(ArrayRef<PartialDiagnosticAt> Diags) {
70 if (hasActiveDiagnostic())
71 llvm::append_range(C&: *EvalStatus.Diag, R&: Diags);
72}
73
74DiagnosticBuilder State::report(SourceLocation Loc, diag::kind DiagId) {
75 return Ctx.getDiagnostics().Report(Loc, DiagID: DiagId);
76}
77
78/// Add a diagnostic to the diagnostics list.
79PartialDiagnostic &State::addDiag(SourceLocation Loc, diag::kind DiagId) {
80 PartialDiagnostic PD(DiagId, Ctx.getDiagAllocator());
81 EvalStatus.Diag->push_back(Elt: std::make_pair(x&: Loc, y&: PD));
82 return EvalStatus.Diag->back().second;
83}
84
85OptionalDiagnostic State::diag(SourceLocation Loc, diag::kind DiagId,
86 unsigned ExtraNotes, bool IsCCEDiag) {
87 if (EvalStatus.Diag) {
88 if (hasPriorDiagnostic()) {
89 return OptionalDiagnostic();
90 }
91
92 unsigned CallStackNotes = getCallStackDepth() - 1;
93 unsigned Limit = Ctx.getDiagnostics().getConstexprBacktraceLimit();
94 if (Limit)
95 CallStackNotes = std::min(a: CallStackNotes, b: Limit + 1);
96 if (checkingPotentialConstantExpression())
97 CallStackNotes = 0;
98
99 setActiveDiagnostic(true);
100 setFoldFailureDiagnostic(!IsCCEDiag);
101 EvalStatus.Diag->clear();
102 EvalStatus.Diag->reserve(N: 1 + ExtraNotes + CallStackNotes);
103 addDiag(Loc, DiagId);
104 if (!checkingPotentialConstantExpression()) {
105 addCallStack(Limit);
106 }
107 return OptionalDiagnostic(&(*EvalStatus.Diag)[0].second);
108 }
109 setActiveDiagnostic(false);
110 return OptionalDiagnostic();
111}
112
113void State::addCallStack(unsigned Limit) {
114 // Determine which calls to skip, if any.
115 unsigned ActiveCalls = getCallStackDepth() - 1;
116 unsigned SkipStart = ActiveCalls, SkipEnd = SkipStart;
117 if (Limit && Limit < ActiveCalls) {
118 SkipStart = Limit / 2 + Limit % 2;
119 SkipEnd = ActiveCalls - Limit / 2;
120 }
121
122 // Walk the call stack and add the diagnostics.
123 unsigned CallIdx = 0;
124 const Frame *Top = getCurrentFrame();
125 const Frame *Bottom = getBottomFrame();
126 for (const Frame *F = Top; F != Bottom; F = F->getCaller(), ++CallIdx) {
127 SourceRange CallRange = F->getCallRange();
128 assert(CallRange.isValid());
129
130 // Skip this call?
131 if (CallIdx >= SkipStart && CallIdx < SkipEnd) {
132 if (CallIdx == SkipStart) {
133 // Note that we're skipping calls.
134 addDiag(Loc: CallRange.getBegin(), DiagId: diag::note_constexpr_calls_suppressed)
135 << unsigned(ActiveCalls - Limit);
136 }
137 continue;
138 }
139
140 // Use a different note for an inheriting constructor, because from the
141 // user's perspective it's not really a function at all.
142 if (const auto *CD =
143 dyn_cast_if_present<CXXConstructorDecl>(Val: F->getCallee());
144 CD && CD->isInheritingConstructor()) {
145 addDiag(Loc: CallRange.getBegin(),
146 DiagId: diag::note_constexpr_inherited_ctor_call_here)
147 << CD->getParent();
148 continue;
149 }
150
151 SmallString<128> Buffer;
152 llvm::raw_svector_ostream Out(Buffer);
153 F->describe(OS&: Out);
154 if (!Buffer.empty())
155 addDiag(Loc: CallRange.getBegin(), DiagId: diag::note_constexpr_call_here)
156 << Out.str() << CallRange;
157 }
158}
159
160bool State::hasPriorDiagnostic() {
161 if (!EvalStatus.Diag->empty()) {
162 switch (EvalMode) {
163 case EvaluationMode::ConstantFold:
164 case EvaluationMode::IgnoreSideEffects:
165 if (!HasFoldFailureDiagnostic)
166 break;
167 // We've already failed to fold something. Keep that diagnostic.
168 [[fallthrough]];
169 case EvaluationMode::ConstantExpression:
170 case EvaluationMode::ConstantExpressionUnevaluated:
171 setActiveDiagnostic(false);
172 return true;
173 }
174 }
175 return false;
176}
177
178bool State::keepEvaluatingAfterFailure() const {
179 uint64_t Limit = Ctx.getLangOpts().ConstexprStepLimit;
180 if (Limit != 0 && !stepsLeft())
181 return false;
182
183 switch (EvalMode) {
184 case EvaluationMode::ConstantExpression:
185 case EvaluationMode::ConstantExpressionUnevaluated:
186 case EvaluationMode::ConstantFold:
187 case EvaluationMode::IgnoreSideEffects:
188 return checkingPotentialConstantExpression() ||
189 checkingForUndefinedBehavior();
190 }
191 llvm_unreachable("Missed EvalMode case");
192}
193
194bool State::keepEvaluatingAfterSideEffect() const {
195 switch (EvalMode) {
196 case EvaluationMode::IgnoreSideEffects:
197 return true;
198
199 case EvaluationMode::ConstantExpression:
200 case EvaluationMode::ConstantExpressionUnevaluated:
201 case EvaluationMode::ConstantFold:
202 // By default, assume any side effect might be valid in some other
203 // evaluation of this expression from a different context.
204 return checkingPotentialConstantExpression() ||
205 checkingForUndefinedBehavior();
206 }
207 llvm_unreachable("Missed EvalMode case");
208}
209
210bool State::keepEvaluatingAfterUndefinedBehavior() const {
211 switch (EvalMode) {
212 case EvaluationMode::IgnoreSideEffects:
213 case EvaluationMode::ConstantFold:
214 return true;
215
216 case EvaluationMode::ConstantExpression:
217 case EvaluationMode::ConstantExpressionUnevaluated:
218 return checkingForUndefinedBehavior();
219 }
220 llvm_unreachable("Missed EvalMode case");
221}
222