since-v3.diff.txt

text/plain

Filename: since-v3.diff.txt
Type: text/plain
Part: 0
Message: Re: pg_parse_json() should not leak token copies on failure
1:  81b1d7cdb6 ! 1:  6a0d088cbe jsonapi: add lexer option to keep token ownership
    @@ Commit message
         of any tokens it wants to persist past the callback lifetime, but the
         lexer will handle necessary cleanup on failure.
     
    +    A -o option has been added to test_json_parser_incremental to exercise
    +    the new setJsonLexContextOwnsTokens() API, and the test_json_parser TAP
    +    tests make use of it. (The test program now cleans up allocated memory,
    +    so that tests can be usefully run under leak sanitizers.)
    +
      ## src/common/jsonapi.c ##
    +@@ src/common/jsonapi.c: struct JsonParserStack
    +  */
    + struct JsonIncrementalState
    + {
    ++	bool		started;
    + 	bool		is_last_chunk;
    + 	bool		partial_completed;
    + 	jsonapi_StrValType partial_token;
     @@ src/common/jsonapi.c: static JsonParseErrorType parse_array_element(JsonLexContext *lex, const JsonSem
      static JsonParseErrorType parse_array(JsonLexContext *lex, const JsonSemAction *sem);
      static JsonParseErrorType report_parse_error(JsonParseContext ctx, JsonLexContext *lex);
    @@ src/common/jsonapi.c: makeJsonLexContextIncremental(JsonLexContext *lex, int enc
     +void
     +setJsonLexContextOwnsTokens(JsonLexContext *lex, bool owned_by_context)
     +{
    ++	if (lex->incremental && lex->inc_state->started)
    ++	{
    ++		/*
    ++		 * Switching this flag after parsing has already started is a
    ++		 * programming error.
    ++		 */
    ++		Assert(false);
    ++		return;
    ++	}
    ++
     +	if (owned_by_context)
     +		lex->flags |= JSONLEX_CTX_OWNS_TOKENS;
     +	else
    @@ src/common/jsonapi.c: inc_lex_level(JsonLexContext *lex)
     +	if (lex->incremental)
     +	{
     +		/*
    -+		 * Ensure freeJsonLexContext() remains safe even if no fname is assigned
    -+		 * at this level.
    ++		 * Ensure freeJsonLexContext() remains safe even if no fname is
    ++		 * assigned at this level.
     +		 */
     +		lex->pstack->fnames[lex->lex_level] = NULL;
     +	}
    @@ src/common/jsonapi.c: inc_lex_level(JsonLexContext *lex)
      static inline void
      dec_lex_level(JsonLexContext *lex)
      {
    -+	set_fname(lex, NULL); /* free the current level's fname, if needed */
    ++	set_fname(lex, NULL);		/* free the current level's fname, if needed */
      	lex->lex_level -= 1;
      }
      
    @@ src/common/jsonapi.c: freeJsonLexContext(JsonLexContext *lex)
      		FREE(lex->pstack);
      	}
      
    +@@ src/common/jsonapi.c: pg_parse_json_incremental(JsonLexContext *lex,
    + 	lex->input = lex->token_terminator = lex->line_start = json;
    + 	lex->input_length = len;
    + 	lex->inc_state->is_last_chunk = is_last;
    ++	lex->inc_state->started = true;
    + 
    + 	/* get the initial token */
    + 	result = json_lex(lex);
     @@ src/common/jsonapi.c: pg_parse_json_incremental(JsonLexContext *lex,
      						if (sfunc != NULL)
      						{
    @@ src/common/jsonapi.c: pg_parse_json_incremental(JsonLexContext *lex,
     +
     +							/*
     +							 * Either ownership of the token passed to the
    -+							 * callback, or we need to free it now. Either way,
    -+							 * clear our pointer to it so it doesn't get freed
    -+							 * in the future.
    ++							 * callback, or we need to free it now. Either
    ++							 * way, clear our pointer to it so it doesn't get
    ++							 * freed in the future.
     +							 */
     +							if (lex->flags & JSONLEX_CTX_OWNS_TOKENS)
     +								FREE(pstack->scalar_val);
    @@ src/include/common/jsonapi.h: extern JsonLexContext *makeJsonLexContextIncrement
      extern void freeJsonLexContext(JsonLexContext *lex);
      
      /* lex one token */
    +
    + ## src/test/modules/test_json_parser/t/001_test_json_parser_incremental.pl ##
    +@@ src/test/modules/test_json_parser/t/001_test_json_parser_incremental.pl: use FindBin;
    + 
    + my $test_file = "$FindBin::RealBin/../tiny.json";
    + 
    +-my @exes =
    +-  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    ++my @exes = (
    ++	[ "test_json_parser_incremental", ],
    ++	[ "test_json_parser_incremental", "-o", ],
    ++	[ "test_json_parser_incremental_shlib", ],
    ++	[ "test_json_parser_incremental_shlib", "-o", ]);
    + 
    + foreach my $exe (@exes)
    + {
    +-	note "testing executable $exe";
    ++	note "testing executable @$exe";
    + 
    + 	# Test the  usage error
    +-	my ($stdout, $stderr) = run_command([ $exe, "-c", 10 ]);
    ++	my ($stdout, $stderr) = run_command([ @$exe, "-c", 10 ]);
    + 	like($stderr, qr/Usage:/, 'error message if not enough arguments');
    + 
    + 	# Test that we get success for small chunk sizes from 64 down to 1.
    + 	for (my $size = 64; $size > 0; $size--)
    + 	{
    +-		($stdout, $stderr) = run_command([ $exe, "-c", $size, $test_file ]);
    ++		($stdout, $stderr) = run_command([ @$exe, "-c", $size, $test_file ]);
    + 
    + 		like($stdout, qr/SUCCESS/, "chunk size $size: test succeeds");
    + 		is($stderr, "", "chunk size $size: no error output");
    +
    + ## src/test/modules/test_json_parser/t/002_inline.pl ##
    +@@ src/test/modules/test_json_parser/t/002_inline.pl: use Test::More;
    + use File::Temp qw(tempfile);
    + 
    + my $dir = PostgreSQL::Test::Utils::tempdir;
    +-my $exe;
    ++my @exe;
    + 
    + sub test
    + {
    +@@ src/test/modules/test_json_parser/t/002_inline.pl: sub test
    + 
    + 	foreach my $size (reverse(1 .. $chunk))
    + 	{
    +-		my ($stdout, $stderr) = run_command([ $exe, "-c", $size, $fname ]);
    ++		my ($stdout, $stderr) = run_command([ @exe, "-c", $size, $fname ]);
    + 
    + 		if (defined($params{error}))
    + 		{
    +@@ src/test/modules/test_json_parser/t/002_inline.pl: sub test
    + 	}
    + }
    + 
    +-my @exes =
    +-  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    ++my @exes = (
    ++	[ "test_json_parser_incremental", ],
    ++	[ "test_json_parser_incremental", "-o", ],
    ++	[ "test_json_parser_incremental_shlib", ],
    ++	[ "test_json_parser_incremental_shlib", "-o", ]);
    + 
    + foreach (@exes)
    + {
    +-	$exe = $_;
    +-	note "testing executable $exe";
    ++	@exe = @$_;
    ++	note "testing executable @exe";
    + 
    + 	test("number", "12345");
    + 	test("string", '"hello"');
    +
    + ## src/test/modules/test_json_parser/t/003_test_semantic.pl ##
    +@@ src/test/modules/test_json_parser/t/003_test_semantic.pl: use File::Temp qw(tempfile);
    + my $test_file = "$FindBin::RealBin/../tiny.json";
    + my $test_out = "$FindBin::RealBin/../tiny.out";
    + 
    +-my @exes =
    +-  ("test_json_parser_incremental", "test_json_parser_incremental_shlib");
    ++my @exes = (
    ++	[ "test_json_parser_incremental", ],
    ++	[ "test_json_parser_incremental", "-o", ],
    ++	[ "test_json_parser_incremental_shlib", ],
    ++	[ "test_json_parser_incremental_shlib", "-o", ]);
    + 
    + foreach my $exe (@exes)
    + {
    +-	note "testing executable $exe";
    ++	note "testing executable @$exe";
    + 
    +-	my ($stdout, $stderr) = run_command([ $exe, "-s", $test_file ]);
    ++	my ($stdout, $stderr) = run_command([ @$exe, "-s", $test_file ]);
    + 
    + 	is($stderr, "", "no error output");
    + 
    +
    + ## src/test/modules/test_json_parser/test_json_parser_incremental.c ##
    +@@
    +  * If the -s flag is given, the program does semantic processing. This should
    +  * just mirror back the json, albeit with white space changes.
    +  *
    ++ * If the -o flag is given, the JSONLEX_CTX_OWNS_TOKENS flag is set. (This can
    ++ * be used in combination with a leak sanitizer; without the option, the parser
    ++ * may leak memory with invalid JSON.)
    ++ *
    +  * The argument specifies the file containing the JSON input.
    +  *
    +  *-------------------------------------------------------------------------
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: static JsonSemAction sem = {
    + 	.scalar = do_scalar
    + };
    + 
    ++static bool lex_owns_tokens = false;
    ++
    + int
    + main(int argc, char **argv)
    + {
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 	char	   *testfile;
    + 	int			c;
    + 	bool		need_strings = false;
    ++	int			ret = 0;
    + 
    + 	pg_logging_init(argv[0]);
    + 
    +-	while ((c = getopt(argc, argv, "c:s")) != -1)
    ++	while ((c = getopt(argc, argv, "c:os")) != -1)
    + 	{
    + 		switch (c)
    + 		{
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 				if (chunk_size > BUFSIZE)
    + 					pg_fatal("chunk size cannot exceed %d", BUFSIZE);
    + 				break;
    ++			case 'o':			/* switch token ownership */
    ++				lex_owns_tokens = true;
    ++				break;
    + 			case 's':			/* do semantic processing */
    + 				testsem = &sem;
    + 				sem.semstate = palloc(sizeof(struct DoState));
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 
    + 	if (optind < argc)
    + 	{
    +-		testfile = pg_strdup(argv[optind]);
    ++		testfile = argv[optind];
    + 		optind++;
    + 	}
    + 	else
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 	}
    + 
    + 	makeJsonLexContextIncremental(&lex, PG_UTF8, need_strings);
    ++	setJsonLexContextOwnsTokens(&lex, lex_owns_tokens);
    + 	initStringInfo(&json);
    + 
    + 	if ((json_file = fopen(testfile, PG_BINARY_R)) == NULL)
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 			if (result != JSON_INCOMPLETE)
    + 			{
    + 				fprintf(stderr, "%s\n", json_errdetail(result, &lex));
    +-				exit(1);
    ++				ret = 1;
    ++				goto cleanup;
    + 			}
    + 			resetStringInfo(&json);
    + 		}
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: main(int argc, char **argv)
    + 			if (result != JSON_SUCCESS)
    + 			{
    + 				fprintf(stderr, "%s\n", json_errdetail(result, &lex));
    +-				exit(1);
    ++				ret = 1;
    ++				goto cleanup;
    + 			}
    + 			if (!need_strings)
    + 				printf("SUCCESS!\n");
    + 			break;
    + 		}
    + 	}
    ++
    ++cleanup:
    + 	fclose(json_file);
    +-	exit(0);
    ++	freeJsonLexContext(&lex);
    ++	free(json.data);
    ++
    ++	return ret;
    + }
    + 
    + /*
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: do_object_field_start(void *state, char *fname, bool isnull)
    + static JsonParseErrorType
    + do_object_field_end(void *state, char *fname, bool isnull)
    + {
    +-	/* nothing to do really */
    ++	if (!lex_owns_tokens)
    ++		free(fname);
    + 
    + 	return JSON_SUCCESS;
    + }
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: do_scalar(void *state, char *token, JsonTokenType tokentype)
    + 	else
    + 		printf("%s", token);
    + 
    ++	if (!lex_owns_tokens)
    ++		free(token);
    ++
    + 	return JSON_SUCCESS;
    + }
    + 
    +@@ src/test/modules/test_json_parser/test_json_parser_incremental.c: usage(const char *progname)
    + {
    + 	fprintf(stderr, "Usage: %s [OPTION ...] testfile\n", progname);
    + 	fprintf(stderr, "Options:\n");
    +-	fprintf(stderr, "  -c chunksize      size of piece fed to parser (default 64)n");
    ++	fprintf(stderr, "  -c chunksize      size of piece fed to parser (default 64)\n");
    ++	fprintf(stderr, "  -o                set JSONLEX_CTX_OWNS_TOKENS for leak checking\n");
    + 	fprintf(stderr, "  -s                do semantic processing\n");
    + 
    + }
-:  ---------- > 2:  d3e639ba2b jsonapi: fully initialize dummy lexer