Skip to content

Commit 4ba97fb

Browse files
habermancopybara-github
authored andcommitted
Made a common longjmp()-handling layer, which can optionally be used on upb_EpsCopyInputStream.
`upb_EpsCopyInputStream` and `upb_WireReader` can now report all errors via `longjmp()` when an error handler is installed. This lets us unwind some complexity: * The decoder can now just use `upb_WireReader_Read*()`, instead of having a separate copy of those functions solely so that errors can `longjmp()`. * `upb_EpsCopyInputStream` no longer needs to support a custom callback for `IsDoneFallback`, which existed solely so that errors could `longjmp()`. This allows us to move `upb_EpsCopyInputStream_IsDoneFallback()` out of the header and into the source file, where fallback functions belong. In future CLs we can apply the same pattern to `upb_Arena` so that allocation failures will `longjmp()`. PiperOrigin-RevId: 840817107
1 parent 2504a29 commit 4ba97fb

File tree

17 files changed

+346
-289
lines changed

17 files changed

+346
-289
lines changed

upb/base/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ cc_library(
1818
],
1919
hdrs = [
2020
"descriptor_constants.h",
21+
"error_handler.h",
2122
"status.h",
2223
"status.hpp",
2324
"string_view.h",

upb/base/error_handler.h

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Protocol Buffers - Google's data interchange format
2+
// Copyright 2025 Google LLC. All rights reserved.
3+
//
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file or at
6+
// https://developers.google.com/open-source/licenses/bsd
7+
8+
#ifndef GOOGLE_UPB_UPB_BASE_ERROR_HANDLER_H__
9+
#define GOOGLE_UPB_UPB_BASE_ERROR_HANDLER_H__
10+
11+
#include <setjmp.h>
12+
13+
// Must be last.
14+
#include "upb/port/def.inc"
15+
16+
// upb_ErrorHandler is a standard longjmp()-based exception handler for UPB.
17+
// It is used for efficient error handling in cases where longjmp() is safe to
18+
// use, such as in highly performance-sensitive C parsing code.
19+
//
20+
// This structure contains both a jmp_buf and an error code; the error code is
21+
// stored in the structure prior to calling longjmp(). This is necessary because
22+
// per the C standard, it is not possible to store the result of setjmp(), so
23+
// the error code must be passed out-of-band.
24+
//
25+
// upb_ErrorHandler is generally not C++-compatible, because longjmp() does not
26+
// run C++ destructors. So any library that supports upb_ErrorHandler should
27+
// also support a regular return-based error handling mechanism. (Note: we
28+
// could conceivably extend this to take a callback, which could either call
29+
// longjmp() or throw a C++ exception. But since C++ exceptions are forbidden
30+
// by the C++ style guide, there's not likely to be a demand for this.)
31+
//
32+
// To support both cases (longjmp() or return-based status) efficiently, code
33+
// can be written like this:
34+
//
35+
// UPB_ATTR_CONST bool upb_Arena_HasErrHandler(const upb_Arena* a);
36+
//
37+
// INLINE void* upb_Arena_Malloc(upb_Arena* a, size_t size) {
38+
// if (UPB_UNLIKELY(a->end - a->ptr < size)) {
39+
// void* ret = upb_Arena_MallocFallback(a, size);
40+
// UPB_MAYBE_ASSUME(upb_Arena_HasErrHandler(a), ret != NULL);
41+
// return ret;
42+
// }
43+
// void* ret = a->ptr;
44+
// a->ptr += size;
45+
// UPB_ASSUME(ret != NULL);
46+
// return ret;
47+
// }
48+
//
49+
// If the optimizer can prove that an error handler is present, it can assume
50+
// that upb_Arena_Malloc() will not return NULL.
51+
52+
// We need to standardize on any error code that might be thrown by an error
53+
// handler.
54+
55+
typedef enum {
56+
kUpb_ErrorCode_Ok = 0,
57+
kUpb_ErrorCode_OutOfMemory = 1,
58+
kUpb_ErrorCode_Malformed = 2,
59+
} upb_ErrorCode;
60+
61+
typedef struct {
62+
int code;
63+
jmp_buf buf;
64+
} upb_ErrorHandler;
65+
66+
UPB_INLINE void upb_ErrorHandler_Init(upb_ErrorHandler* e) {
67+
e->code = kUpb_ErrorCode_Ok;
68+
}
69+
70+
UPB_INLINE bool upb_ErrorHandler_Setjmp(upb_ErrorHandler* e) {
71+
return UPB_SETJMP(e->buf) == 0;
72+
}
73+
74+
UPB_INLINE UPB_NORETURN void upb_ErrorHandler_ThrowError(upb_ErrorHandler* e,
75+
int code) {
76+
UPB_ASSERT(code != kUpb_ErrorCode_Ok);
77+
e->code = code;
78+
UPB_LONGJMP(e->buf, 1);
79+
}
80+
81+
#include "upb/port/undef.inc"
82+
83+
#endif // GOOGLE_UPB_UPB_BASE_ERROR_HANDLER_H__

upb/port/def.inc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,15 @@ Error, UINTPTR_MAX is undefined
317317
#define UPB_ASSUME(expr) assert(expr)
318318
#endif
319319

320+
#ifdef __GNUC__
321+
#define UPB_MAYBE_ASSUME(pred, x) \
322+
if (__builtin_constant_p(pred) && pred) UPB_ASSUME(x)
323+
#define UPB_ATTR_CONST __attribute__((const))
324+
#else
325+
#define UPB_MAYBE_ASSUME(pred, x)
326+
#define UPB_ATTR_CONST
327+
#endif
328+
320329
/* UPB_ASSERT(): in release mode, we use the expression without letting it be
321330
* evaluated. This prevents "unused variable" warnings. */
322331
#ifdef NDEBUG

0 commit comments

Comments
 (0)