Support json_errdetail in FRONTEND code
authorDaniel Gustafsson <[email protected]>
Sun, 17 Mar 2024 22:56:15 +0000 (23:56 +0100)
committerDaniel Gustafsson <[email protected]>
Sun, 17 Mar 2024 22:56:15 +0000 (23:56 +0100)
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 b44669b2ca
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 <[email protected]>
Reviewed-by: Michael Paquier <[email protected]>
Discussion: https://postgr.es/m/CAOYmi+mWdTd6ujtyF7MsvXvk7ToLRVG_tYAcaGbQLvf=N4KrQw@mail.gmail.com

src/bin/pg_verifybackup/t/005_bad_manifest.pl
src/common/jsonapi.c
src/common/parse_manifest.c
src/include/common/jsonapi.h

index 77fdfbb9d0726e78e485268bb8f80dd4fcfc0046..2068babac5a23a1e2e099db33b2a604e63159ea6 100644 (file)
@@ -13,7 +13,8 @@ use Test::More;
 my $tempdir = PostgreSQL::Test::Utils::tempdir;
 
 test_bad_manifest('input string ended unexpectedly',
-   qr/could not parse backup manifest: parsing failed/, <<EOM);
+   qr/could not parse backup manifest: The input string ended unexpectedly/,
+   <<EOM);
 {
 EOM
 
index 32931ded826253b9054a67ce410fc0be593775cf..98d6e66a217bc77496e2e58e80e58d08aa154a48 100644 (file)
@@ -161,6 +161,7 @@ makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
    else
        memset(lex, 0, sizeof(JsonLexContext));
 
+   lex->errormsg = NULL;
    lex->input = lex->token_terminator = lex->line_start = json;
    lex->line_number = 1;
    lex->input_length = len;
@@ -175,18 +176,21 @@ makeJsonLexContextCstringLen(JsonLexContext *lex, char *json,
 }
 
 /*
- * Free memory in a JsonLexContext.  There's no need for this if a *lex
- * pointer was given when the object was made and need_escapes was false,
- * or (in backend environment) a memory context delete/reset is imminent.
+ * Free memory in a JsonLexContext.
+ *
+ * There's no need for this if a *lex pointer was given when the object was
+ * made, need_escapes was false, and json_errdetail() was not called; or if (in
+ * backend environment) a memory context delete/reset is imminent.
  */
 void
 freeJsonLexContext(JsonLexContext *lex)
 {
    if (lex->flags & JSONLEX_FREE_STRVAL)
-   {
-       pfree(lex->strval->data);
-       pfree(lex->strval);
-   }
+       destroyStringInfo(lex->strval);
+
+   if (lex->errormsg)
+       destroyStringInfo(lex->errormsg);
+
    if (lex->flags & JSONLEX_FREE_STRUCT)
        pfree(lex);
 }
@@ -1145,72 +1149,71 @@ report_parse_error(JsonParseContext ctx, JsonLexContext *lex)
    return JSON_SUCCESS;        /* silence stupider compilers */
 }
 
-
-#ifndef FRONTEND
-/*
- * Extract the current token from a lexing context, for error reporting.
- */
-static char *
-extract_token(JsonLexContext *lex)
-{
-   int         toklen = lex->token_terminator - lex->token_start;
-   char       *token = palloc(toklen + 1);
-
-   memcpy(token, lex->token_start, toklen);
-   token[toklen] = '\0';
-   return token;
-}
-
 /*
  * Construct an (already translated) detail message for a JSON error.
  *
- * Note that the error message generated by this routine may not be
- * palloc'd, making it unsafe for frontend code as there is no way to
- * know if this can be safely pfree'd or not.
+ * The returned pointer should not be freed, the allocation is either static
+ * or owned by the JsonLexContext.
  */
 char *
 json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
 {
+   if (lex->errormsg)
+       resetStringInfo(lex->errormsg);
+   else
+       lex->errormsg = makeStringInfo();
+
+   /*
+    * A helper for error messages that should print the current token. The
+    * format must contain exactly one %.*s specifier.
+    */
+#define token_error(lex, format) \
+   appendStringInfo((lex)->errormsg, _(format), \
+                    (int) ((lex)->token_terminator - (lex)->token_start), \
+                    (lex)->token_start);
+
    switch (error)
    {
        case JSON_SUCCESS:
            /* fall through to the error code after switch */
            break;
        case JSON_ESCAPING_INVALID:
-           return psprintf(_("Escape sequence \"\\%s\" is invalid."),
-                           extract_token(lex));
+           token_error(lex, "Escape sequence \"\\%.*s\" is invalid.");
+           break;
        case JSON_ESCAPING_REQUIRED:
-           return psprintf(_("Character with value 0x%02x must be escaped."),
-                           (unsigned char) *(lex->token_terminator));
+           appendStringInfo(lex->errormsg,
+                            _("Character with value 0x%02x must be escaped."),
+                            (unsigned char) *(lex->token_terminator));
+           break;
        case JSON_EXPECTED_END:
-           return psprintf(_("Expected end of input, but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected end of input, but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_ARRAY_FIRST:
-           return psprintf(_("Expected array element or \"]\", but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected array element or \"]\", but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_ARRAY_NEXT:
-           return psprintf(_("Expected \",\" or \"]\", but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected \",\" or \"]\", but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_COLON:
-           return psprintf(_("Expected \":\", but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected \":\", but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_JSON:
-           return psprintf(_("Expected JSON value, but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected JSON value, but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_MORE:
            return _("The input string ended unexpectedly.");
        case JSON_EXPECTED_OBJECT_FIRST:
-           return psprintf(_("Expected string or \"}\", but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected string or \"}\", but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_OBJECT_NEXT:
-           return psprintf(_("Expected \",\" or \"}\", but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected \",\" or \"}\", but found \"%.*s\".");
+           break;
        case JSON_EXPECTED_STRING:
-           return psprintf(_("Expected string, but found \"%s\"."),
-                           extract_token(lex));
+           token_error(lex, "Expected string, but found \"%.*s\".");
+           break;
        case JSON_INVALID_TOKEN:
-           return psprintf(_("Token \"%s\" is invalid."),
-                           extract_token(lex));
+           token_error(lex, "Token \"%.*s\" is invalid.");
+           break;
        case JSON_UNICODE_CODE_POINT_ZERO:
            return _("\\u0000 cannot be converted to text.");
        case JSON_UNICODE_ESCAPE_FORMAT:
@@ -1219,9 +1222,19 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
            /* note: this case is only reachable in frontend not backend */
            return _("Unicode escape values cannot be used for code point values above 007F when the encoding is not UTF8.");
        case JSON_UNICODE_UNTRANSLATABLE:
-           /* note: this case is only reachable in backend not frontend */
+
+           /*
+            * Note: this case is only reachable in backend and not frontend.
+            * #ifdef it away so the frontend doesn't try to link against
+            * backend functionality.
+            */
+#ifndef FRONTEND
            return psprintf(_("Unicode escape value could not be translated to the server's encoding %s."),
                            GetDatabaseEncodingName());
+#else
+           Assert(false);
+           break;
+#endif
        case JSON_UNICODE_HIGH_SURROGATE:
            return _("Unicode high surrogate must not follow a high surrogate.");
        case JSON_UNICODE_LOW_SURROGATE:
@@ -1230,13 +1243,17 @@ json_errdetail(JsonParseErrorType error, JsonLexContext *lex)
            /* fall through to the error code after switch */
            break;
    }
+#undef token_error
 
    /*
     * We don't use a default: case, so that the compiler will warn about
     * unhandled enum values.  But this needs to be here anyway to cover the
     * possibility of an incorrect input.
     */
-   elog(ERROR, "unexpected json parse error type: %d", (int) error);
-   return NULL;
+   if (lex->errormsg->len == 0)
+       appendStringInfo(lex->errormsg,
+                        _("unexpected json parse error type: %d"),
+                        (int) error);
+
+   return lex->errormsg->data;
 }
-#endif
index 3f6d1dfd3dec7476a52d6020a92545c4ff765411..40ec3b4f58c92be04c4ca8ed2da98555bbd469d2 100644 (file)
@@ -152,7 +152,7 @@ json_parse_manifest(JsonManifestParseContext *context, char *buffer,
    /* Run the actual JSON parser. */
    json_error = pg_parse_json(lex, &sem);
    if (json_error != JSON_SUCCESS)
-       json_manifest_parse_failure(context, "parsing failed");
+       json_manifest_parse_failure(context, json_errdetail(json_error, lex));
    if (parse.state != JM_EXPECT_EOF)
        json_manifest_parse_failure(context, "manifest ended unexpectedly");
 
index 02943cdad8f2954cdc44ec3b0401c1ee709eaa4b..86a0fc2d001a862653c9567e4a2a39ad3690cec2 100644 (file)
@@ -89,6 +89,7 @@ typedef struct JsonLexContext
    int         line_number;    /* line number, starting from 1 */
    char       *line_start;     /* where that line starts within input */
    StringInfo  strval;
+   StringInfo  errormsg;
 } JsonLexContext;
 
 typedef JsonParseErrorType (*json_struct_action) (void *state);