diff --git a/src/pg_query_scan.c b/src/pg_query_scan.c index 1d0e052e..50df4296 100644 --- a/src/pg_query_scan.c +++ b/src/pg_query_scan.c @@ -23,6 +23,15 @@ struct yyguts_t size_t yyleng_r; }; +static void free_scan_tokens(PgQuery__ScanToken **tokens, size_t n_tokens) +{ + size_t i; + + for (i = 0; i < n_tokens; i++) + free(tokens[i]); + free(tokens); +} + PgQueryScanResult pg_query_scan(const char* input) { MemoryContext ctx = NULL; @@ -77,6 +86,8 @@ PgQueryScanResult pg_query_scan(const char* input) scanner_finish(yyscanner); output_tokens = malloc(sizeof(PgQuery__ScanToken *) * token_count); + if (output_tokens == NULL && token_count > 0) + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); /* initialize the flex scanner --- should match raw_parser() */ yyscanner = scanner_init(input, &yyextra, &ScanKeywords, ScanKeywordTokens); @@ -91,6 +102,11 @@ PgQueryScanResult pg_query_scan(const char* input) if (tok == 0) break; output_tokens[i] = malloc(sizeof(PgQuery__ScanToken)); + if (output_tokens[i] == NULL) + { + free_scan_tokens(output_tokens, i); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); + } pg_query__scan_token__init(output_tokens[i]); output_tokens[i]->start = yylloc; if (tok == SCONST || tok == USCONST || tok == BCONST || tok == XCONST || tok == IDENT || tok == UIDENT || tok == C_COMMENT) { @@ -115,12 +131,15 @@ PgQueryScanResult pg_query_scan(const char* input) scan_result.tokens = output_tokens; result.pbuf.len = pg_query__scan_result__get_packed_size(&scan_result); result.pbuf.data = malloc(result.pbuf.len); + if (result.pbuf.data == NULL) + { + result.pbuf.len = 0; + free_scan_tokens(output_tokens, token_count); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); + } pg_query__scan_result__pack(&scan_result, (void*) result.pbuf.data); - for (i = 0; i < token_count; i++) { - free(output_tokens[i]); - } - free(output_tokens); + free_scan_tokens(output_tokens, token_count); #ifndef DEBUG // Save stderr for result diff --git a/test/scan.c b/test/scan.c index 991ee3ab..25e0ab07 100644 --- a/test/scan.c +++ b/test/scan.c @@ -9,6 +9,58 @@ #include "protobuf/pg_query.pb-c.h" +#if defined(__linux__) && !defined(USE_VALGRIND) +#include +#include + +/* Scanning under a tight address-space limit must report "out of memory" + * instead of writing tokens through a NULL pointer. */ +static bool test_scan_out_of_memory(void) +{ + const char *stmt = "SELECT 1;\n"; + const size_t repeat = 2000000; + size_t len = strlen(stmt); + char *input = malloc(len * repeat + 1); + long pages = 0; + FILE *statm; + struct rlimit old_limit; + struct rlimit limit; + PgQueryScanResult result; + bool ok; + size_t i; + + for (i = 0; i < repeat; i++) + memcpy(input + i * len, stmt, len); + input[len * repeat] = '\0'; + + statm = fopen("/proc/self/statm", "r"); + if (statm == NULL || fscanf(statm, "%ld", &pages) != 1) { + printf("INVALID result for out of memory scan: cannot read /proc/self/statm\n"); + free(input); + return false; + } + fclose(statm); + + getrlimit(RLIMIT_AS, &old_limit); + limit = old_limit; + limit.rlim_cur = (rlim_t) pages * sysconf(_SC_PAGESIZE) + 128 * 1024 * 1024; + setrlimit(RLIMIT_AS, &limit); + result = pg_query_scan(input); + setrlimit(RLIMIT_AS, &old_limit); + + ok = result.error != NULL && strcmp(result.error->message, "out of memory") == 0; + if (ok) { + printf("."); + } else { + printf("INVALID result for out of memory scan: %s\n", result.error ? result.error->message : "no error"); + } + + pg_query_free_scan_result(result); + free(input); + return ok; +} +#endif + int main() { size_t i; size_t j; @@ -61,6 +113,11 @@ int main() { pg_query_free_scan_result(result); } +#if defined(__linux__) && !defined(USE_VALGRIND) + if (!test_scan_out_of_memory()) + ret_code = -1; +#endif + printf("\n"); pg_query_exit();