Skip to content

Commit d660701

Browse files
Support json_errdetail in FRONTEND code
Allocate memory for the error message inside memory owned by the JsonLexContext and move responsibility away from the caller for freeing it. This means that we can partially revert b44669b as this is now safe to use in FRONTEND code. The motivation for this comes from the OAuth and incremental JSON patchsets but it also adds value on its own. Author: Jacob Champion <jacob.champion@enterprisedb.com> Reviewed-by: Michael Paquier <michael@paquier.xyz> Discussion: https://postgr.es/m/CAOYmi+mWdTd6ujtyF7MsvXvk7ToLRVG_tYAcaGbQLvf=N4KrQw@mail.gmail.com
1 parent 33f1316 commit d660701

File tree

4 files changed

+73
-54
lines changed

4 files changed

+73
-54
lines changed

src/bin/pg_verifybackup/t/005_bad_manifest.pl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
my $tempdir = PostgreSQL::Test::Utils::tempdir;
1414

1515
test_bad_manifest('input string ended unexpectedly',
16-
qr/could not parse backup manifest: parsing failed/, <<EOM);
16+
qr/could not parse backup manifest: The input string ended unexpectedly/,
17+
<<EOM);
1718
{
1819
EOM
1920

src/common/jsonapi.c

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
161161
else
162162
memset(lex, 0, sizeof(JsonLexContext));
163163

164+
lex->errormsg = NULL;
164165
lex->input = lex->token_terminator = lex->line_start = json;
165166
lex->line_number = 1;
166167
lex->input_length = len;
@@ -175,18 +176,21 @@ makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
175176
}
176177

177178
/*
178-
* Free memory in a JsonLexContext. There's no need for this if a *lex
179-
* pointer was given when the object was made and need_escapes was false,
180-
* or (in backend environment) a memory context delete/reset is imminent.
179+
* Free memory in a JsonLexContext.
180+
*
181+
* There's no need for this if a *lex pointer was given when the object was
182+
* made, need_escapes was false, and json_errdetail() was not called; or if (in
183+
* backend environment) a memory context delete/reset is imminent.
181184
*/
182185
void
183186
freeJsonLexContext(JsonLexContext *lex)
184187
{
185188
if (lex->flags & JSONLEX_FREE_STRVAL)
186-
{
187-
pfree(lex->strval->data);
188-
pfree(lex->strval);
189-
}
189+
destroyStringInfo(lex->strval);
190+
191+
if (lex->errormsg)
192+
destroyStringInfo(lex->errormsg);
193+
190194
if (lex->flags & JSONLEX_FREE_STRUCT)
191195
pfree(lex);
192196
}
@@ -1145,72 +1149,71 @@ report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
11451149
return JSON_SUCCESS; /* silence stupider compilers */
11461150
}
11471151

1148-
1149-
#ifndef FRONTEND
1150-
/*
1151-
* Extract the current token from a lexing context, for error reporting.
1152-
*/
1153-
static char *
1154-
extract_token(JsonLexContext *lex)
1155-
{
1156-
int toklen = lex->token_terminator - lex->token_start;
1157-
char *token = palloc(toklen + 1);
1158-
1159-
memcpy(token, lex->token_start, toklen);
1160-
token[toklen] = '\0';
1161-
return token;
1162-
}
1163-
11641152
/*
11651153
* Construct an (already translated) detail message for a JSON error.
11661154
*
1167-
* Note that the error message generated by this routine may not be
1168-
* palloc'd, making it unsafe for frontend code as there is no way to
1169-
* know if this can be safely pfree'd or not.
1155+
* The returned pointer should not be freed, the allocation is either static
1156+
* or owned by the JsonLexContext.
11701157
*/
11711158
char *
11721159
json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
11731160
{
1161+
if (lex->errormsg)
1162+
resetStringInfo(lex->errormsg);
1163+
else
1164+
lex->errormsg = makeStringInfo();
1165+
1166+
/*
1167+
* A helper for error messages that should print the current token. The
1168+
* format must contain exactly one %.*s specifier.
1169+
*/
1170+
#define token_error(lex, format) \
1171+
appendStringInfo((lex)->errormsg, _(format), \
1172+
(int) ((lex)->token_terminator - (lex)->token_start), \
1173+
(lex)->token_start);
1174+
11741175
switch (error)
11751176
{
11761177
case JSON_SUCCESS:
11771178
/* fall through to the error code after switch */
11781179
break;
11791180
case JSON_ESCAPING_INVALID:
1180-
return psprintf(_("Escape sequence \"\\%s\" is invalid."),
1181-
extract_token(lex));
1181+
token_error(lex, "Escape sequence \"\\%.*s\" is invalid.");
1182+
break;
11821183
case JSON_ESCAPING_REQUIRED:
1183-
return psprintf(_("Character with value 0x%02x must be escaped."),
1184-
(unsigned char) *(lex->token_terminator));
1184+
appendStringInfo(lex->errormsg,
1185+
_("Character with value 0x%02x must be escaped."),
1186+
(unsigned char) *(lex->token_terminator));
1187+
break;
11851188
case JSON_EXPECTED_END:
1186-
return psprintf(_("Expected end of input, but found \"%s\"."),
1187-
extract_token(lex));
1189+
token_error(lex, "Expected end of input, but found \"%.*s\".");
1190+
break;
11881191
case JSON_EXPECTED_ARRAY_FIRST:
1189-
return psprintf(_("Expected array element or \"]\", but found \"%s\"."),
1190-
extract_token(lex));
1192+
token_error(lex, "Expected array element or \"]\", but found \"%.*s\".");
1193+
break;
11911194
case JSON_EXPECTED_ARRAY_NEXT:
1192-
return psprintf(_("Expected \",\" or \"]\", but found \"%s\"."),
1193-
extract_token(lex));
1195+
token_error(lex, "Expected \",\" or \"]\", but found \"%.*s\".");
1196+
break;
11941197
case JSON_EXPECTED_COLON:
1195-
return psprintf(_("Expected \":\", but found \"%s\"."),
1196-
extract_token(lex));
1198+
token_error(lex, "Expected \":\", but found \"%.*s\".");
1199+
break;
11971200
case JSON_EXPECTED_JSON:
1198-
return psprintf(_("Expected JSON value, but found \"%s\"."),
1199-
extract_token(lex));
1201+
token_error(lex, "Expected JSON value, but found \"%.*s\".");
1202+
break;
12001203
case JSON_EXPECTED_MORE:
12011204
return _("The input string ended unexpectedly.");
12021205
case JSON_EXPECTED_OBJECT_FIRST:
1203-
return psprintf(_("Expected string or \"}\", but found \"%s\"."),
1204-
extract_token(lex));
1206+
token_error(lex, "Expected string or \"}\", but found \"%.*s\".");
1207+
break;
12051208
case JSON_EXPECTED_OBJECT_NEXT:
1206-
return psprintf(_("Expected \",\" or \"}\", but found \"%s\"."),
1207-
extract_token(lex));
1209+
token_error(lex, "Expected \",\" or \"}\", but found \"%.*s\".");
1210+
break;
12081211
case JSON_EXPECTED_STRING:
1209-
return psprintf(_("Expected string, but found \"%s\"."),
1210-
extract_token(lex));
1212+
token_error(lex, "Expected string, but found \"%.*s\".");
1213+
break;
12111214
case JSON_INVALID_TOKEN:
1212-
return psprintf(_("Token \"%s\" is invalid."),
1213-
extract_token(lex));
1215+
token_error(lex, "Token \"%.*s\" is invalid.");
1216+
break;
12141217
case JSON_UNICODE_CODE_POINT_ZERO:
12151218
return _("\\u0000 cannot be converted to text.");
12161219
case JSON_UNICODE_ESCAPE_FORMAT:
@@ -1219,9 +1222,19 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
12191222
/* note: this case is only reachable in frontend not backend */
12201223
return _("Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8.");
12211224
case JSON_UNICODE_UNTRANSLATABLE:
1222-
/* note: this case is only reachable in backend not frontend */
1225+
1226+
/*
1227+
* Note: this case is only reachable in backend and not frontend.
1228+
* #ifdef it away so the frontend doesn't try to link against
1229+
* backend functionality.
1230+
*/
1231+
#ifndef FRONTEND
12231232
return psprintf(_("Unicode escape value could not be translated to the server's encoding %s."),
12241233
GetDatabaseEncodingName());
1234+
#else
1235+
Assert(false);
1236+
break;
1237+
#endif
12251238
case JSON_UNICODE_HIGH_SURROGATE:
12261239
return _("Unicode high surrogate must not follow a high surrogate.");
12271240
case JSON_UNICODE_LOW_SURROGATE:
@@ -1230,13 +1243,17 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
12301243
/* fall through to the error code after switch */
12311244
break;
12321245
}
1246+
#undef token_error
12331247

12341248
/*
12351249
* We don't use a default: case, so that the compiler will warn about
12361250
* unhandled enum values. But this needs to be here anyway to cover the
12371251
* possibility of an incorrect input.
12381252
*/
1239-
elog(ERROR, "unexpected json parse error type: %d", (int) error);
1240-
return NULL;
1253+
if (lex->errormsg->len == 0)
1254+
appendStringInfo(lex->errormsg,
1255+
_("unexpected json parse error type: %d"),
1256+
(int) error);
1257+
1258+
return lex->errormsg->data;
12411259
}
1242-
#endif

src/common/parse_manifest.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
152152
/* Run the actual JSON parser. */
153153
json_error = pg_parse_json(lex, &sem);
154154
if (json_error != JSON_SUCCESS)
155-
json_manifest_parse_failure(context, "parsing failed");
155+
json_manifest_parse_failure(context, json_errdetail(json_error, lex));
156156
if (parse.state != JM_EXPECT_EOF)
157157
json_manifest_parse_failure(context, "manifest ended unexpectedly");
158158

src/include/common/jsonapi.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ typedef struct JsonLexContext
8989
int line_number; /* line number, starting from 1 */
9090
char *line_start; /* where that line starts within input */
9191
StringInfo strval;
92+
StringInfo errormsg;
9293
} JsonLexContext;
9394

9495
typedef JsonParseErrorType (*json_struct_action) (void *state);

0 commit comments

Comments
 (0)