CppInterOp
C++ Language Interoperability Layer
Loading...
Searching...
No Matches
ErrorInternal.cpp
Go to the documentation of this file.
1//===- ErrorInternal.cpp - Impl-side Result<T> + ErrorRef + Diag --- C++ -===//
2//
3// Part of the compiler-research project, under the Apache License v2.0 with
4// LLVM Exceptions.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "CppInterOp/Error.h"
10
12
13#include "Compatibility.h"
14#include "ErrorInternal.h"
15#include "InterpreterInfo.h"
16
17#include "clang/Basic/Diagnostic.h"
18#include "clang/Basic/SourceLocation.h"
19#include "clang/Basic/SourceManager.h"
20#include "clang/Frontend/CompilerInstance.h"
21
22#include "llvm/ADT/SmallString.h"
23#include "llvm/Support/raw_ostream.h"
24
25#include <cstdio>
26#include <cstdlib>
27#include <memory>
28#include <utility>
29
30namespace Cpp {
31
32// A null DiagnosticRef returns defaults so callers don't have to
33// branch on isNull() before every access.
34static const StoredDiagView* AsView(DiagnosticRef D) {
35 return static_cast<const StoredDiagView*>(D.data);
36}
37
38CPPINTEROP_API DiagnosticSeverity GetDiagnosticSeverity(DiagnosticRef D) {
39 if (const StoredDiagView* V = AsView(D))
40 return V->Sev;
41 return DiagnosticSeverity::Note;
42}
43
44CPPINTEROP_API const char* GetDiagnosticMessage(DiagnosticRef D) {
45 if (const StoredDiagView* V = AsView(D))
46 return V->Message.c_str();
47 return "";
48}
49
50CPPINTEROP_API const char* GetDiagnosticFile(DiagnosticRef D) {
51 if (const StoredDiagView* V = AsView(D))
52 return V->File.c_str();
53 return "";
54}
55
56CPPINTEROP_API unsigned GetDiagnosticLine(DiagnosticRef D) {
57 if (const StoredDiagView* V = AsView(D))
58 return V->Line;
59 return 0;
60}
61
62CPPINTEROP_API unsigned GetDiagnosticColumn(DiagnosticRef D) {
63 if (const StoredDiagView* V = AsView(D))
64 return V->Column;
65 return 0;
66}
67
68// Member-function forwarders.
69DiagnosticSeverity DiagnosticRef::severity() const {
70 return GetDiagnosticSeverity(*this);
71}
72const char* DiagnosticRef::message() const {
73 return GetDiagnosticMessage(*this);
74}
75const char* DiagnosticRef::file() const { return GetDiagnosticFile(*this); }
76unsigned DiagnosticRef::line() const { return GetDiagnosticLine(*this); }
77unsigned DiagnosticRef::column() const { return GetDiagnosticColumn(*this); }
78
79CPPINTEROP_API Status GetStatus(ErrorRef E) {
80 if (E.isOk())
81 return Status::Ok;
82 if (E.isInline())
83 return E.inlineStatus();
84 if (const ErrorSlice* S = E.slice())
85 return S->Code;
86 return Status::Ok;
87}
88
89CPPINTEROP_API ArrayView<DiagnosticRef> GetDiagnostics(ErrorRef E) {
90 const ErrorSlice* S = E.slice();
91 if (!S || S->Diagnostics.empty())
92 return {};
93 // DiagnosticRefs are constructed lazily into a thread-local buffer
94 // so the returned ArrayView<DiagnosticRef> can alias contiguous
95 // storage (slice.Diagnostics is a std::deque -- not contiguous).
96 // The buffer survives until the next GetDiagnostics call on the
97 // same thread; callers that need to retain the view should copy it.
98 static thread_local std::vector<DiagnosticRef> Buf;
99 Buf.clear();
100 Buf.reserve(S->Diagnostics.size());
101 for (const StoredDiagView& Dv : S->Diagnostics)
102 Buf.push_back(DiagnosticRef{&Dv});
103 return ArrayView<DiagnosticRef>{Buf.data(), Buf.size()};
104}
105
106// Member-function forwarders.
107Status ErrorRef::status() const { return GetStatus(*this); }
108ArrayView<DiagnosticRef> ErrorRef::diagnostics() const {
109 return GetDiagnostics(*this);
110}
111const char* ErrorRef::producer() const {
112 const ErrorSlice* S = slice();
113 return S ? S->Producer : nullptr;
114}
115const char* ErrorRef::producerSignature() const {
116 const ErrorSlice* S = slice();
117 return S ? S->ProducerSignature : nullptr;
118}
119
120ErrorRecord ErrorRef::record() const {
121 ErrorRecord R;
122 R.Code = status();
123 const ErrorSlice* S = slice();
124 if (!S)
125 return R;
126 R.Diagnostics.reserve(S->Diagnostics.size());
127 for (const StoredDiagView& Dv : S->Diagnostics) {
128 DiagnosticInfo Di;
129 Di.Severity = Dv.Sev;
130 Di.Message = Dv.Message;
131 Di.File = Dv.File;
132 Di.Line = Dv.Line;
133 Di.Column = Dv.Column;
134 R.Diagnostics.push_back(std::move(Di));
135 }
136 R.Producer = S->Producer;
137 R.ProducerSignature = S->ProducerSignature;
138 return R;
139}
140
142 // Inline-encoded errors carry no refcount; the bits are pure value.
143 if (!E.isSlice())
144 return;
145 E.slice()->Refcount.fetch_add(1, std::memory_order_acq_rel);
146}
147
149 if (!E.isSlice())
150 return;
151 const ErrorSlice* S = E.slice();
152 if (S->Refcount.fetch_sub(1, std::memory_order_acq_rel) == 1)
153 delete S;
154}
155
156char StatusError::ID = 0;
157
158void StatusError::log(llvm::raw_ostream& OS) const {
159 OS << "CppInterOp status error: " << Message;
160}
161std::error_code StatusError::convertToErrorCode() const {
162 return llvm::inconvertibleErrorCode();
163}
164
166 auto* S = new ErrorSlice();
167 S->Owner = Owner;
168 return S;
169}
170
171CPPINTEROP_API ErrorRef makeError(Status S) { return ErrorRef::makeInline(S); }
172
174 std::string Message, const char* Producer,
175 const char* ProducerSig) {
176 ErrorSlice* Slc = AllocSlice(II);
177 Slc->Code = S;
178 Slc->Producer = Producer;
179 Slc->ProducerSignature = ProducerSig;
180 if (!Message.empty()) {
182 Dv.Message = std::move(Message);
183 Dv.Sev = DiagnosticSeverity::Error;
184 Slc->Diagnostics.push_back(std::move(Dv));
185 }
186 DrainPendingInto(II, Slc);
187 // Refcount stays at the default 0; the wrapping Result<T> bumps to 1
188 // via its ErrorRef ctor.
189 return ErrorRef::makeSlice(Slc);
190}
191
192CPPINTEROP_API ErrorRef drainError(InterpreterInfo* II, llvm::Error E,
193 const char* Producer,
194 const char* ProducerSig) {
195 // Decompose the llvm::Error chain into a single slice. StatusError
196 // sets the Status directly; any other ErrorInfo subclass is
197 // stringified via its log() and surfaced as Status::CompileError.
198 // Typed payloads (overload / deduction) and their llvm::Error
199 // subclasses arrive with the migrations that need them.
200 Status Code = Status::CompileError;
201 std::string Msg;
202
203 llvm::handleAllErrors(
204 std::move(E),
205 [&](const StatusError& SE) {
206 Code = SE.Code;
207 if (Msg.empty())
208 Msg = SE.Message;
209 },
210 [&](const llvm::ErrorInfoBase& EIB) {
211 // Unknown subclass: stringify and surface as CompileError.
212 if (Msg.empty()) {
213 llvm::raw_string_ostream OS(Msg);
214 EIB.log(OS);
215 }
216 });
217
218 ErrorSlice* Slc = AllocSlice(II);
219 Slc->Code = Code;
220 Slc->Producer = Producer;
221 Slc->ProducerSignature = ProducerSig;
222 if (!Msg.empty()) {
224 Dv.Message = std::move(Msg);
225 Dv.Sev = DiagnosticSeverity::Error;
226 Slc->Diagnostics.push_back(std::move(Dv));
227 }
228 DrainPendingInto(II, Slc);
229 return ErrorRef::makeSlice(Slc);
230}
231
232namespace {
233/// Captures every diagnostic the parser/sema emits into the owning
234/// interpreter's StoredDiags and forwards to the previously installed
235/// consumer (typically Clang's TextDiagnosticPrinter) so existing
236/// stderr output is preserved. Owned holds the chained consumer when
237/// the engine owned it (clang-REPL); Raw points at it regardless of
238/// ownership so cling's externally-owned consumer also forwards.
239class CppInteropDiagConsumer : public clang::DiagnosticConsumer {
240public:
241 CppInteropDiagConsumer(InterpreterInfo* II,
242 std::unique_ptr<clang::DiagnosticConsumer> Owned,
243 clang::DiagnosticConsumer* Raw)
244 : II(II), Owned(std::move(Owned)), Raw(Raw) {}
245
246 void BeginSourceFile(const clang::LangOptions& LO,
247 const clang::Preprocessor* PP) override {
248 if (Raw)
249 Raw->BeginSourceFile(LO, PP);
250 }
251 void EndSourceFile() override {
252 if (Raw)
253 Raw->EndSourceFile();
254 }
255 void HandleDiagnostic(clang::DiagnosticsEngine::Level Level,
256 const clang::Diagnostic& Info) override;
257
258private:
259 InterpreterInfo* II;
260 std::unique_ptr<clang::DiagnosticConsumer> Owned;
261 clang::DiagnosticConsumer* Raw;
262};
263
264DiagnosticSeverity MapClangSeverity(clang::DiagnosticsEngine::Level L) {
265 switch (L) {
266 case clang::DiagnosticsEngine::Ignored:
267 case clang::DiagnosticsEngine::Note:
268 case clang::DiagnosticsEngine::Remark:
269 return DiagnosticSeverity::Note;
270 case clang::DiagnosticsEngine::Warning:
271 return DiagnosticSeverity::Warning;
272 case clang::DiagnosticsEngine::Error:
273 return DiagnosticSeverity::Error;
274 case clang::DiagnosticsEngine::Fatal:
275 return DiagnosticSeverity::Fatal;
276 }
277 return DiagnosticSeverity::Error;
278}
279} // namespace
280
281void CppInteropDiagConsumer::HandleDiagnostic(
282 clang::DiagnosticsEngine::Level Level, const clang::Diagnostic& Info) {
283 // Update the base consumer's NumErrors so hasErrorOccurred() keeps
284 // working for callers that still rely on it.
285 clang::DiagnosticConsumer::HandleDiagnostic(Level, Info);
286 if (Raw)
287 Raw->HandleDiagnostic(Level, Info);
288
289 llvm::SmallString<128> Buf;
290 Info.FormatDiagnostic(Buf);
291
292 StoredDiagView Dv;
293 Dv.Message = Buf.str().str();
294 Dv.Sev = MapClangSeverity(Level);
295
296 if (Info.hasSourceManager() && Info.getLocation().isValid()) {
297 clang::SourceManager& SM = Info.getSourceManager();
298 clang::PresumedLoc PLoc = SM.getPresumedLoc(Info.getLocation());
299 if (PLoc.isValid()) {
300 if (const char* F = PLoc.getFilename())
301 Dv.File = F;
302 Dv.Line = PLoc.getLine();
303 Dv.Column = PLoc.getColumn();
304 }
305 }
306
307 II->StoredDiags.push_back(std::move(Dv));
308}
309
311 // Chain to whatever consumer Clang already installed so existing
312 // stderr behaviour is preserved. takeClient transfers ownership when
313 // the engine owns it (clang-REPL); cling installs its consumer with
314 // ShouldOwnClient=false, so takeClient returns null there and we
315 // forward through the raw pointer instead. getClient still returns
316 // the original between takeClient and setClient.
317 clang::DiagnosticsEngine& Diag = II->Interpreter->getCI()->getDiagnostics();
318 std::unique_ptr<clang::DiagnosticConsumer> PrevOwned = Diag.takeClient();
319 clang::DiagnosticConsumer* PrevRaw = Diag.getClient();
320 auto* New = new CppInteropDiagConsumer(II, std::move(PrevOwned), PrevRaw);
321 Diag.setClient(New, /*ShouldOwnClient=*/true);
322}
323
325 for (auto& Dv : II->StoredDiags)
326 S->Diagnostics.push_back(std::move(Dv));
327 II->StoredDiags.clear();
328}
329
331 II->StoredDiags.clear();
332}
333
335 return static_cast<unsigned>(GetInterpInfo(I)->StoredDiags.size());
336}
337
338CPPINTEROP_API DiagnosticRef GetPendingDiagnostic(unsigned Idx, InterpRef I) {
340 if (Idx >= II->StoredDiags.size())
341 return DiagnosticRef{};
342 return DiagnosticRef{&II->StoredDiags[Idx]};
343}
344
346 GetInterpInfo(I)->StoredDiags.clear();
347}
348
349[[noreturn]] CPPINTEROP_API void
350ResultAbort_ValueOnError(const ErrorRef& /*Err*/) {
351 std::fputs("Cpp::Result<T>::value() called on an error-bearing "
352 "Result. Use value_or(fallback) for lenient semantics, "
353 "or branch on .ok() / .error() before calling .value().\n",
354 stderr);
355 std::abort();
356}
357
358#ifndef NDEBUG
359[[noreturn]] CPPINTEROP_API void
360ResultAbort_UncheckedOnDtor(const ErrorRef& /*Err*/) {
361 std::fputs("Cpp::Result destroyed without check (likely a dropped "
362 "error). Call .ok() / .error() / .value() to inspect, "
363 "or .ignore() to acknowledge.\n",
364 stderr);
365 std::abort();
366}
367#endif
368
369} // namespace Cpp
#define CPPINTEROP_API
std::string Message
Definition Box.h:70
void RetainErrorRef(ErrorRef E)
void InstallDiagConsumer(InterpreterInfo *II)
Wire CppInterOp's DiagnosticConsumer into the interpreter's DiagnosticsEngine so parser/sema diagnost...
InterpreterInfo * GetInterpInfo(InterpRef I)
Resolve an InterpRef to the impl-side struct.
Status GetStatus(ErrorRef E)
unsigned GetPendingDiagnosticCount(InterpRef I)
const char * GetDiagnosticFile(DiagnosticRef D)
ErrorRef makeError(Status S)
Build an inline-status ErrorRef (no diagnostics, no payload).
void ResultAbort_UncheckedOnDtor(const ErrorRef &)
unsigned GetDiagnosticLine(DiagnosticRef D)
ErrorRef drainError(InterpreterInfo *II, llvm::Error E, const char *Producer, const char *ProducerSig)
Drain E into a fresh slice, returning the slice via ErrorRef.
DiagnosticSeverity GetDiagnosticSeverity(DiagnosticRef D)
void ClearPendingDiagnostics(InterpRef I)
ArrayView< DiagnosticRef > GetDiagnostics(ErrorRef E)
void DrainPendingInto(InterpreterInfo *II, ErrorSlice *S)
Drain the consumer's per-interp buffer into a slice on failure.
const char * GetDiagnosticMessage(DiagnosticRef D)
ErrorSlice * AllocSlice(InterpreterInfo *Owner)
Allocate a fresh slice on the heap, refcount 0.
void ReleaseErrorRef(ErrorRef E)
void ResultAbort_ValueOnError(const ErrorRef &)
DiagnosticRef GetPendingDiagnostic(unsigned Idx, InterpRef I)
unsigned GetDiagnosticColumn(DiagnosticRef D)
static const StoredDiagView * AsView(DiagnosticRef D)
void ClearPending(InterpreterInfo *II)
Body of a slice-encoded error: drained diagnostics, producer attribution, refcount.
const char * ProducerSignature
const char * Producer
std::deque< StoredDiagView > Diagnostics
std::deque< StoredDiagView > StoredDiags
Owning record for one captured diagnostic.
DiagnosticSeverity Sev