Skip to content

Commit e37fe1d

Browse files
committed
Convert jsonpath's input function to report errors softly
Reviewed by Tom Lane Discussion: https://postgr.es/m/a8dc5700-c341-3ba8-0507-cc09881e6200@dunslane.net
1 parent 780ec9f commit e37fe1d

File tree

8 files changed

+322
-125
lines changed

8 files changed

+322
-125
lines changed

src/backend/utils/adt/jsonpath.c

Lines changed: 74 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,19 @@
6666
#include "funcapi.h"
6767
#include "lib/stringinfo.h"
6868
#include "libpq/pqformat.h"
69+
#include "nodes/miscnodes.h"
6970
#include "miscadmin.h"
7071
#include "utils/builtins.h"
7172
#include "utils/json.h"
7273
#include "utils/jsonpath.h"
7374

7475

75-
static Datum jsonPathFromCstring(char *in, int len);
76+
static Datum jsonPathFromCstring(char *in, int len, struct Node *escontext);
7677
static char *jsonPathToCstring(StringInfo out, JsonPath *in,
7778
int estimated_len);
78-
static int flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
79+
static bool flattenJsonPathParseItem(StringInfo buf, int *result,
80+
struct Node *escontext,
81+
JsonPathParseItem *item,
7982
int nestingLevel, bool insideArraySubscript);
8083
static void alignStringInfoInt(StringInfo buf);
8184
static int32 reserveSpaceForItemPointer(StringInfo buf);
@@ -95,7 +98,7 @@ jsonpath_in(PG_FUNCTION_ARGS)
9598
char *in = PG_GETARG_CSTRING(0);
9699
int len = strlen(in);
97100

98-
return jsonPathFromCstring(in, len);
101+
return jsonPathFromCstring(in, len, fcinfo->context);
99102
}
100103

101104
/*
@@ -119,7 +122,7 @@ jsonpath_recv(PG_FUNCTION_ARGS)
119122
else
120123
elog(ERROR, "unsupported jsonpath version number: %d", version);
121124

122-
return jsonPathFromCstring(str, nbytes);
125+
return jsonPathFromCstring(str, nbytes, NULL);
123126
}
124127

125128
/*
@@ -165,24 +168,29 @@ jsonpath_send(PG_FUNCTION_ARGS)
165168
* representation of jsonpath.
166169
*/
167170
static Datum
168-
jsonPathFromCstring(char *in, int len)
171+
jsonPathFromCstring(char *in, int len, struct Node *escontext)
169172
{
170-
JsonPathParseResult *jsonpath = parsejsonpath(in, len);
173+
JsonPathParseResult *jsonpath = parsejsonpath(in, len, escontext);
171174
JsonPath *res;
172175
StringInfoData buf;
173176

174-
initStringInfo(&buf);
175-
enlargeStringInfo(&buf, 4 * len /* estimation */ );
176-
177-
appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
177+
if (SOFT_ERROR_OCCURRED(escontext))
178+
return (Datum) 0;
178179

179180
if (!jsonpath)
180-
ereport(ERROR,
181+
ereturn(escontext, (Datum) 0,
181182
(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
182183
errmsg("invalid input syntax for type %s: \"%s\"", "jsonpath",
183184
in)));
184185

185-
flattenJsonPathParseItem(&buf, jsonpath->expr, 0, false);
186+
initStringInfo(&buf);
187+
enlargeStringInfo(&buf, 4 * len /* estimation */ );
188+
189+
appendStringInfoSpaces(&buf, JSONPATH_HDRSZ);
190+
191+
if (!flattenJsonPathParseItem(&buf, NULL, escontext,
192+
jsonpath->expr, 0, false))
193+
return (Datum) 0;
186194

187195
res = (JsonPath *) buf.data;
188196
SET_VARSIZE(res, buf.len);
@@ -225,9 +233,10 @@ jsonPathToCstring(StringInfo out, JsonPath *in, int estimated_len)
225233
* Recursive function converting given jsonpath parse item and all its
226234
* children into a binary representation.
227235
*/
228-
static int
229-
flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
230-
int nestingLevel, bool insideArraySubscript)
236+
static bool
237+
flattenJsonPathParseItem(StringInfo buf, int *result, struct Node *escontext,
238+
JsonPathParseItem *item, int nestingLevel,
239+
bool insideArraySubscript)
231240
{
232241
/* position from beginning of jsonpath data */
233242
int32 pos = buf->len - JSONPATH_HDRSZ;
@@ -295,16 +304,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
295304
int32 left = reserveSpaceForItemPointer(buf);
296305
int32 right = reserveSpaceForItemPointer(buf);
297306

298-
chld = !item->value.args.left ? pos :
299-
flattenJsonPathParseItem(buf, item->value.args.left,
300-
nestingLevel + argNestingLevel,
301-
insideArraySubscript);
307+
if (!item->value.args.left)
308+
chld = pos;
309+
else if (! flattenJsonPathParseItem(buf, &chld, escontext,
310+
item->value.args.left,
311+
nestingLevel + argNestingLevel,
312+
insideArraySubscript))
313+
return false;
302314
*(int32 *) (buf->data + left) = chld - pos;
303315

304-
chld = !item->value.args.right ? pos :
305-
flattenJsonPathParseItem(buf, item->value.args.right,
306-
nestingLevel + argNestingLevel,
307-
insideArraySubscript);
316+
if (!item->value.args.right)
317+
chld = pos;
318+
else if (! flattenJsonPathParseItem(buf, &chld, escontext,
319+
item->value.args.right,
320+
nestingLevel + argNestingLevel,
321+
insideArraySubscript))
322+
return false;
308323
*(int32 *) (buf->data + right) = chld - pos;
309324
}
310325
break;
@@ -323,9 +338,11 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
323338
item->value.like_regex.patternlen);
324339
appendStringInfoChar(buf, '\0');
325340

326-
chld = flattenJsonPathParseItem(buf, item->value.like_regex.expr,
327-
nestingLevel,
328-
insideArraySubscript);
341+
if (! flattenJsonPathParseItem(buf, &chld, escontext,
342+
item->value.like_regex.expr,
343+
nestingLevel,
344+
insideArraySubscript))
345+
return false;
329346
*(int32 *) (buf->data + offs) = chld - pos;
330347
}
331348
break;
@@ -341,10 +358,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
341358
{
342359
int32 arg = reserveSpaceForItemPointer(buf);
343360

344-
chld = !item->value.arg ? pos :
345-
flattenJsonPathParseItem(buf, item->value.arg,
346-
nestingLevel + argNestingLevel,
347-
insideArraySubscript);
361+
if (!item->value.arg)
362+
chld = pos;
363+
else if (! flattenJsonPathParseItem(buf, &chld, escontext,
364+
item->value.arg,
365+
nestingLevel + argNestingLevel,
366+
insideArraySubscript))
367+
return false;
348368
*(int32 *) (buf->data + arg) = chld - pos;
349369
}
350370
break;
@@ -357,13 +377,13 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
357377
break;
358378
case jpiCurrent:
359379
if (nestingLevel <= 0)
360-
ereport(ERROR,
380+
ereturn(escontext, false,
361381
(errcode(ERRCODE_SYNTAX_ERROR),
362382
errmsg("@ is not allowed in root expressions")));
363383
break;
364384
case jpiLast:
365385
if (!insideArraySubscript)
366-
ereport(ERROR,
386+
ereturn(escontext, false,
367387
(errcode(ERRCODE_SYNTAX_ERROR),
368388
errmsg("LAST is allowed only in array subscripts")));
369389
break;
@@ -383,15 +403,22 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
383403
{
384404
int32 *ppos;
385405
int32 topos;
386-
int32 frompos =
387-
flattenJsonPathParseItem(buf,
388-
item->value.array.elems[i].from,
389-
nestingLevel, true) - pos;
406+
int32 frompos;
407+
408+
if (! flattenJsonPathParseItem(buf, &frompos, escontext,
409+
item->value.array.elems[i].from,
410+
nestingLevel, true))
411+
return false;
412+
frompos -= pos;
390413

391414
if (item->value.array.elems[i].to)
392-
topos = flattenJsonPathParseItem(buf,
393-
item->value.array.elems[i].to,
394-
nestingLevel, true) - pos;
415+
{
416+
if (! flattenJsonPathParseItem(buf, &topos, escontext,
417+
item->value.array.elems[i].to,
418+
nestingLevel, true))
419+
return false;
420+
topos -= pos;
421+
}
395422
else
396423
topos = 0;
397424

@@ -424,12 +451,17 @@ flattenJsonPathParseItem(StringInfo buf, JsonPathParseItem *item,
424451

425452
if (item->next)
426453
{
427-
chld = flattenJsonPathParseItem(buf, item->next, nestingLevel,
428-
insideArraySubscript) - pos;
454+
if (! flattenJsonPathParseItem(buf, &chld, escontext,
455+
item->next, nestingLevel,
456+
insideArraySubscript))
457+
return false;
458+
chld -= pos;
429459
*(int32 *) (buf->data + next) = chld;
430460
}
431461

432-
return pos;
462+
if (result)
463+
*result = pos;
464+
return true;
433465
}
434466

435467
/*

src/backend/utils/adt/jsonpath_exec.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1721,7 +1721,8 @@ executeLikeRegex(JsonPathItem *jsp, JsonbValue *str, JsonbValue *rarg,
17211721
cxt->regex =
17221722
cstring_to_text_with_len(jsp->content.like_regex.pattern,
17231723
jsp->content.like_regex.patternlen);
1724-
cxt->cflags = jspConvertRegexFlags(jsp->content.like_regex.flags);
1724+
(void) jspConvertRegexFlags(jsp->content.like_regex.flags,
1725+
&(cxt->cflags), NULL);
17251726
}
17261727

17271728
if (RE_compile_and_execute(cxt->regex, str->val.string.val,

src/backend/utils/adt/jsonpath_gram.y

Lines changed: 62 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ static JsonPathParseItem *makeItemUnary(JsonPathItemType type,
3838
static JsonPathParseItem *makeItemList(List *list);
3939
static JsonPathParseItem *makeIndexArray(List *list);
4040
static JsonPathParseItem *makeAny(int first, int last);
41-
static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
42-
JsonPathString *pattern,
43-
JsonPathString *flags);
41+
static bool makeItemLikeRegex(JsonPathParseItem *expr,
42+
JsonPathString *pattern,
43+
JsonPathString *flags,
44+
JsonPathParseItem ** result,
45+
struct Node *escontext);
4446

4547
/*
4648
* Bison doesn't allocate anything that needs to live across parser calls,
@@ -57,6 +59,9 @@ static JsonPathParseItem *makeItemLikeRegex(JsonPathParseItem *expr,
5759
%expect 0
5860
%name-prefix="jsonpath_yy"
5961
%parse-param {JsonPathParseResult **result}
62+
%parse-param {struct Node *escontext}
63+
%lex-param {JsonPathParseResult **result}
64+
%lex-param {struct Node *escontext}
6065

6166
%union
6267
{
@@ -163,9 +168,20 @@ predicate:
163168
{ $$ = makeItemUnary(jpiIsUnknown, $2); }
164169
| expr STARTS_P WITH_P starts_with_initial
165170
{ $$ = makeItemBinary(jpiStartsWith, $1, $4); }
166-
| expr LIKE_REGEX_P STRING_P { $$ = makeItemLikeRegex($1, &$3, NULL); }
171+
| expr LIKE_REGEX_P STRING_P
172+
{
173+
JsonPathParseItem *jppitem;
174+
if (! makeItemLikeRegex($1, &$3, NULL, &jppitem, escontext))
175+
YYABORT;
176+
$$ = jppitem;
177+
}
167178
| expr LIKE_REGEX_P STRING_P FLAG_P STRING_P
168-
{ $$ = makeItemLikeRegex($1, &$3, &$5); }
179+
{
180+
JsonPathParseItem *jppitem;
181+
if (! makeItemLikeRegex($1, &$3, &$5, &jppitem, escontext))
182+
YYABORT;
183+
$$ = jppitem;
184+
}
169185
;
170186

171187
starts_with_initial:
@@ -472,9 +488,10 @@ makeAny(int first, int last)
472488
return v;
473489
}
474490

475-
static JsonPathParseItem *
491+
static bool
476492
makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
477-
JsonPathString *flags)
493+
JsonPathString *flags, JsonPathParseItem ** result,
494+
struct Node *escontext)
478495
{
479496
JsonPathParseItem *v = makeItemType(jpiLikeRegex);
480497
int i;
@@ -506,7 +523,7 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
506523
v->value.like_regex.flags |= JSP_REGEX_QUOTE;
507524
break;
508525
default:
509-
ereport(ERROR,
526+
ereturn(escontext, false,
510527
(errcode(ERRCODE_SYNTAX_ERROR),
511528
errmsg("invalid input syntax for type %s", "jsonpath"),
512529
errdetail("Unrecognized flag character \"%.*s\" in LIKE_REGEX predicate.",
@@ -515,22 +532,48 @@ makeItemLikeRegex(JsonPathParseItem *expr, JsonPathString *pattern,
515532
}
516533
}
517534

518-
/* Convert flags to what RE_compile_and_cache needs */
519-
cflags = jspConvertRegexFlags(v->value.like_regex.flags);
535+
/* Convert flags to what pg_regcomp needs */
536+
if ( !jspConvertRegexFlags(v->value.like_regex.flags, &cflags, escontext))
537+
return false;
520538

521539
/* check regex validity */
522-
(void) RE_compile_and_cache(cstring_to_text_with_len(pattern->val,
523-
pattern->len),
524-
cflags, DEFAULT_COLLATION_OID);
540+
{
541+
regex_t re_tmp;
542+
pg_wchar *wpattern;
543+
int wpattern_len;
544+
int re_result;
545+
546+
wpattern = (pg_wchar *) palloc((pattern->len + 1) * sizeof(pg_wchar));
547+
wpattern_len = pg_mb2wchar_with_len(pattern->val,
548+
wpattern,
549+
pattern->len);
550+
551+
if ((re_result = pg_regcomp(&re_tmp, wpattern, wpattern_len, cflags,
552+
DEFAULT_COLLATION_OID)) != REG_OKAY)
553+
{
554+
char errMsg[100];
555+
556+
/* See regexp.c for explanation */
557+
CHECK_FOR_INTERRUPTS();
558+
pg_regerror(re_result, &re_tmp, errMsg, sizeof(errMsg));
559+
ereturn(escontext, false,
560+
(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
561+
errmsg("invalid regular expression: %s", errMsg)));
562+
}
525563

526-
return v;
564+
pg_regfree(&re_tmp);
565+
}
566+
567+
*result = v;
568+
569+
return true;
527570
}
528571

529572
/*
530573
* Convert from XQuery regex flags to those recognized by our regex library.
531574
*/
532-
int
533-
jspConvertRegexFlags(uint32 xflags)
575+
bool
576+
jspConvertRegexFlags(uint32 xflags, int *result, struct Node *escontext)
534577
{
535578
/* By default, XQuery is very nearly the same as Spencer's AREs */
536579
int cflags = REG_ADVANCED;
@@ -561,18 +604,12 @@ jspConvertRegexFlags(uint32 xflags)
561604
* XQuery-style ignore-whitespace mode.
562605
*/
563606
if (xflags & JSP_REGEX_WSPACE)
564-
ereport(ERROR,
607+
ereturn(escontext, false,
565608
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
566609
errmsg("XQuery \"x\" flag (expanded regular expressions) is not implemented")));
567610
}
568611

569-
/*
570-
* We'll never need sub-match details at execution. While
571-
* RE_compile_and_execute would set this flag anyway, force it on here to
572-
* ensure that the regex cache entries created by makeItemLikeRegex are
573-
* useful.
574-
*/
575-
cflags |= REG_NOSUB;
612+
*result = cflags;
576613

577-
return cflags;
614+
return true;
578615
}

src/backend/utils/adt/jsonpath_internal.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,14 @@ typedef struct JsonPathString
2525
#include "utils/jsonpath.h"
2626
#include "jsonpath_gram.h"
2727

28-
extern int jsonpath_yylex(YYSTYPE *yylval_param);
29-
extern int jsonpath_yyparse(JsonPathParseResult **result);
30-
extern void jsonpath_yyerror(JsonPathParseResult **result, const char *message);
28+
#define YY_DECL extern int jsonpath_yylex(YYSTYPE *yylval_param, \
29+
JsonPathParseResult **result, \
30+
struct Node *escontext)
31+
YY_DECL;
32+
extern int jsonpath_yyparse(JsonPathParseResult **result,
33+
struct Node *escontext);
34+
extern void jsonpath_yyerror(JsonPathParseResult **result,
35+
struct Node *escontext,
36+
const char *message);
3137

3238
#endif /* JSONPATH_INTERNAL_H */

0 commit comments

Comments
 (0)