From 8365b41223945a3cfd9615fa33488d774f00e0f8 Mon Sep 17 00:00:00 2001
From: Nikita Glukhov <n.gluhov@postgrespro.ru>
Date: Mon, 29 Aug 2022 23:55:52 +0300
Subject: [PATCH v9 4/9] Add safe input and type conversion functions for
 numeric

---
 src/backend/utils/adt/numeric.c | 230 ++++++++++++++++++++++----------
 src/include/utils/numeric.h     |   6 +-
 2 files changed, 161 insertions(+), 75 deletions(-)

diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index 920a63b0081..951b2f43ab1 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -498,7 +498,7 @@ static void free_var(NumericVar *var);
 static void zero_var(NumericVar *var);
 
 static const char *set_var_from_str(const char *str, const char *cp,
-									NumericVar *dest);
+									NumericVar *dest, bool *have_error);
 static void set_var_from_num(Numeric value, NumericVar *dest);
 static void init_var_from_num(Numeric num, NumericVar *dest);
 static void set_var_from_var(const NumericVar *value, NumericVar *dest);
@@ -512,8 +512,8 @@ static Numeric duplicate_numeric(Numeric num);
 static Numeric make_result(const NumericVar *var);
 static Numeric make_result_opt_error(const NumericVar *var, bool *error);
 
-static void apply_typmod(NumericVar *var, int32 typmod);
-static void apply_typmod_special(Numeric num, int32 typmod);
+static bool apply_typmod(NumericVar *var, int32 typmod, bool *error);
+static void apply_typmod_special(Numeric num, int32 typmod, bool *error);
 
 static bool numericvar_to_int32(const NumericVar *var, int32 *result);
 static bool numericvar_to_int64(const NumericVar *var, int64 *result);
@@ -607,21 +607,25 @@ static void accum_sum_combine(NumericSumAccum *accum, NumericSumAccum *accum2);
  * ----------------------------------------------------------------------
  */
 
+/* Convenience macro: set *have_error flag (if provided) or throw error */
+#define RETURN_ERROR(throw_error, have_error) \
+do { \
+	if (have_error) { \
+		*(have_error) = true; \
+		return NULL; \
+	} else { \
+		throw_error; \
+	} \
+} while (0)
 
 /*
  * numeric_in() -
  *
  *	Input function for numeric data type
  */
-Datum
-numeric_in(PG_FUNCTION_ARGS)
+Numeric
+numeric_in_opt_error(char *str, int32 typmod, bool *have_error)
 {
-	char	   *str = PG_GETARG_CSTRING(0);
-
-#ifdef NOT_USED
-	Oid			typelem = PG_GETARG_OID(1);
-#endif
-	int32		typmod = PG_GETARG_INT32(2);
 	Numeric		res;
 	const char *cp;
 
@@ -682,7 +686,10 @@ numeric_in(PG_FUNCTION_ARGS)
 
 		init_var(&value);
 
-		cp = set_var_from_str(str, cp, &value);
+		cp = set_var_from_str(str, cp, &value, have_error);
+
+		if (!cp)
+			return NULL;	/* error */
 
 		/*
 		 * We duplicate a few lines of code here because we would like to
@@ -693,38 +700,59 @@ numeric_in(PG_FUNCTION_ARGS)
 		while (*cp)
 		{
 			if (!isspace((unsigned char) *cp))
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("invalid input syntax for type %s: \"%s\"",
-								"numeric", str)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+									  errmsg("invalid input syntax for type %s: \"%s\"",
+											 "numeric", str))),
+							 have_error);
 			cp++;
 		}
 
-		apply_typmod(&value, typmod);
+		if (!apply_typmod(&value, typmod, have_error))
+			return NULL; /* error */
 
-		res = make_result(&value);
+		res = make_result_opt_error(&value, have_error);
 		free_var(&value);
 
-		PG_RETURN_NUMERIC(res);
+		return res;
 	}
 
 	/* Should be nothing left but spaces */
 	while (*cp)
 	{
 		if (!isspace((unsigned char) *cp))
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("invalid input syntax for type %s: \"%s\"",
-							"numeric", str)));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+								  errmsg("invalid input syntax for type %s: \"%s\"",
+										 "numeric", str))),
+						 have_error);
+
 		cp++;
 	}
 
 	/* As above, throw any typmod error after finishing syntax check */
-	apply_typmod_special(res, typmod);
+	apply_typmod_special(res, typmod, have_error);
 
-	PG_RETURN_NUMERIC(res);
+	return res;
 }
 
+/*
+ * numeric_in() -
+ *
+ *	Input function for numeric data type
+ */
+Datum
+numeric_in(PG_FUNCTION_ARGS)
+{
+	char	   *str = PG_GETARG_CSTRING(0);
+
+#ifdef NOT_USED
+	Oid			typelem = PG_GETARG_OID(1);
+#endif
+	int32		typmod = PG_GETARG_INT32(2);
+
+	PG_RETURN_NUMERIC(numeric_in_opt_error(str, typmod, NULL));
+}
 
 /*
  * numeric_out() -
@@ -1058,7 +1086,7 @@ numeric_recv(PG_FUNCTION_ARGS)
 	{
 		trunc_var(&value, value.dscale);
 
-		apply_typmod(&value, typmod);
+		apply_typmod(&value, typmod, NULL);
 
 		res = make_result(&value);
 	}
@@ -1067,7 +1095,7 @@ numeric_recv(PG_FUNCTION_ARGS)
 		/* apply_typmod_special wants us to make the Numeric first */
 		res = make_result(&value);
 
-		apply_typmod_special(res, typmod);
+		apply_typmod_special(res, typmod, NULL);
 	}
 
 	free_var(&value);
@@ -1180,7 +1208,7 @@ numeric		(PG_FUNCTION_ARGS)
 	 */
 	if (NUMERIC_IS_SPECIAL(num))
 	{
-		apply_typmod_special(num, typmod);
+		apply_typmod_special(num, typmod, NULL);
 		PG_RETURN_NUMERIC(duplicate_numeric(num));
 	}
 
@@ -1231,7 +1259,7 @@ numeric		(PG_FUNCTION_ARGS)
 	init_var(&var);
 
 	set_var_from_num(num, &var);
-	apply_typmod(&var, typmod);
+	apply_typmod(&var, typmod, NULL);
 	new = make_result(&var);
 
 	free_var(&var);
@@ -4308,15 +4336,20 @@ int8_numeric(PG_FUNCTION_ARGS)
 }
 
 
-Datum
-numeric_int8(PG_FUNCTION_ARGS)
+int64
+numeric_int8_opt_error(Numeric num, bool *error)
 {
-	Numeric		num = PG_GETARG_NUMERIC(0);
 	NumericVar	x;
 	int64		result;
 
 	if (NUMERIC_IS_SPECIAL(num))
 	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		if (NUMERIC_IS_NAN(num))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4331,13 +4364,26 @@ numeric_int8(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);
 
 	if (!numericvar_to_int64(&x, &result))
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("bigint out of range")));
+	}
 
-	PG_RETURN_INT64(result);
+	return result;
 }
 
+Datum
+numeric_int8(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT64(numeric_int8_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
 
 Datum
 int2_numeric(PG_FUNCTION_ARGS)
@@ -4347,17 +4393,20 @@ int2_numeric(PG_FUNCTION_ARGS)
 	PG_RETURN_NUMERIC(int64_to_numeric(val));
 }
 
-
-Datum
-numeric_int2(PG_FUNCTION_ARGS)
+int16
+numeric_int2_opt_error(Numeric num, bool *error)
 {
-	Numeric		num = PG_GETARG_NUMERIC(0);
 	NumericVar	x;
 	int64		val;
-	int16		result;
 
 	if (NUMERIC_IS_SPECIAL(num))
 	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		if (NUMERIC_IS_NAN(num))
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -4372,21 +4421,40 @@ numeric_int2(PG_FUNCTION_ARGS)
 	init_var_from_num(num, &x);
 
 	if (!numericvar_to_int64(&x, &val))
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
+	}
 
 	if (unlikely(val < PG_INT16_MIN) || unlikely(val > PG_INT16_MAX))
+	{
+		if (error)
+		{
+			*error = true;
+			return 0;
+		}
+
 		ereport(ERROR,
 				(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 				 errmsg("smallint out of range")));
+	}
 
 	/* Down-convert to int2 */
-	result = (int16) val;
-
-	PG_RETURN_INT16(result);
+	return (int16) val;
 }
 
+Datum
+numeric_int2(PG_FUNCTION_ARGS)
+{
+	PG_RETURN_INT16(numeric_int2_opt_error(PG_GETARG_NUMERIC(0), NULL));
+}
 
 Datum
 float8_numeric(PG_FUNCTION_ARGS)
@@ -4412,7 +4480,7 @@ float8_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);
 
 	/* Assume we need not worry about leading/trailing spaces */
-	(void) set_var_from_str(buf, buf, &result);
+	(void) set_var_from_str(buf, buf, &result, NULL);
 
 	res = make_result(&result);
 
@@ -4505,7 +4573,7 @@ float4_numeric(PG_FUNCTION_ARGS)
 	init_var(&result);
 
 	/* Assume we need not worry about leading/trailing spaces */
-	(void) set_var_from_str(buf, buf, &result);
+	(void) set_var_from_str(buf, buf, &result, NULL);
 
 	res = make_result(&result);
 
@@ -6811,7 +6879,8 @@ zero_var(NumericVar *var)
  * reports.  (Typically cp would be the same except advanced over spaces.)
  */
 static const char *
-set_var_from_str(const char *str, const char *cp, NumericVar *dest)
+set_var_from_str(const char *str, const char *cp, NumericVar *dest,
+				 bool *have_error)
 {
 	bool		have_dp = false;
 	int			i;
@@ -6849,10 +6918,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
 	}
 
 	if (!isdigit((unsigned char) *cp))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-				 errmsg("invalid input syntax for type %s: \"%s\"",
-						"numeric", str)));
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+							  errmsg("invalid input syntax for type %s: \"%s\"",
+									 "numeric", str))),
+					 have_error);
 
 	decdigits = (unsigned char *) palloc(strlen(cp) + DEC_DIGITS * 2);
 
@@ -6873,10 +6943,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
 		else if (*cp == '.')
 		{
 			if (have_dp)
-				ereport(ERROR,
-						(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-						 errmsg("invalid input syntax for type %s: \"%s\"",
-								"numeric", str)));
+				RETURN_ERROR(ereport(ERROR,
+									 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+									  errmsg("invalid input syntax for type %s: \"%s\"",
+											 "numeric", str))),
+							 have_error);
 			have_dp = true;
 			cp++;
 		}
@@ -6897,10 +6968,11 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
 		cp++;
 		exponent = strtol(cp, &endptr, 10);
 		if (endptr == cp)
-			ereport(ERROR,
-					(errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
-					 errmsg("invalid input syntax for type %s: \"%s\"",
-							"numeric", str)));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+								  errmsg("invalid input syntax for type %s: \"%s\"",
+										 "numeric", str))),
+						 have_error);
 		cp = endptr;
 
 		/*
@@ -6912,9 +6984,10 @@ set_var_from_str(const char *str, const char *cp, NumericVar *dest)
 		 * for consistency use the same ereport errcode/text as make_result().
 		 */
 		if (exponent >= INT_MAX / 2 || exponent <= -(INT_MAX / 2))
-			ereport(ERROR,
-					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-					 errmsg("value overflows numeric format")));
+			RETURN_ERROR(ereport(ERROR,
+								 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+								  errmsg("value overflows numeric format"))),
+						 have_error);
 		dweight += (int) exponent;
 		dscale -= (int) exponent;
 		if (dscale < 0)
@@ -7420,17 +7493,10 @@ make_result_opt_error(const NumericVar *var, bool *have_error)
 	if (NUMERIC_WEIGHT(result) != weight ||
 		NUMERIC_DSCALE(result) != var->dscale)
 	{
-		if (have_error)
-		{
-			*have_error = true;
-			return NULL;
-		}
-		else
-		{
-			ereport(ERROR,
-					(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
-					 errmsg("value overflows numeric format")));
-		}
+		RETURN_ERROR(ereport(ERROR,
+							 (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
+							  errmsg("value overflows numeric format"))),
+					 have_error);
 	}
 
 	dump_numeric("make_result()", result);
@@ -7456,8 +7522,8 @@ make_result(const NumericVar *var)
  *	Do bounds checking and rounding according to the specified typmod.
  *	Note that this is only applied to normal finite values.
  */
-static void
-apply_typmod(NumericVar *var, int32 typmod)
+static bool
+apply_typmod(NumericVar *var, int32 typmod, bool *have_error)
 {
 	int			precision;
 	int			scale;
@@ -7467,7 +7533,7 @@ apply_typmod(NumericVar *var, int32 typmod)
 
 	/* Do nothing if we have an invalid typmod */
 	if (!is_valid_numeric_typmod(typmod))
-		return;
+		return true;
 
 	precision = numeric_typmod_precision(typmod);
 	scale = numeric_typmod_scale(typmod);
@@ -7514,6 +7580,13 @@ apply_typmod(NumericVar *var, int32 typmod)
 #error unsupported NBASE
 #endif
 				if (ddigits > maxdigits)
+				{
+					if (have_error)
+					{
+						*have_error = true;
+						return false;
+					}
+
 					ereport(ERROR,
 							(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 							 errmsg("numeric field overflow"),
@@ -7523,11 +7596,14 @@ apply_typmod(NumericVar *var, int32 typmod)
 									   maxdigits ? "10^" : "",
 									   maxdigits ? maxdigits : 1
 									   )));
+				}
 				break;
 			}
 			ddigits -= DEC_DIGITS;
 		}
 	}
+
+	return true;
 }
 
 /*
@@ -7537,7 +7613,7 @@ apply_typmod(NumericVar *var, int32 typmod)
  *	For convenience of most callers, the value is presented in packed form.
  */
 static void
-apply_typmod_special(Numeric num, int32 typmod)
+apply_typmod_special(Numeric num, int32 typmod, bool *have_error)
 {
 	int			precision;
 	int			scale;
@@ -7560,6 +7636,12 @@ apply_typmod_special(Numeric num, int32 typmod)
 	precision = numeric_typmod_precision(typmod);
 	scale = numeric_typmod_scale(typmod);
 
+	if (have_error)
+	{
+		*have_error = true;
+		return;
+	}
+
 	ereport(ERROR,
 			(errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE),
 			 errmsg("numeric field overflow"),
diff --git a/src/include/utils/numeric.h b/src/include/utils/numeric.h
index 3caa74dfe7a..f32807cfdaa 100644
--- a/src/include/utils/numeric.h
+++ b/src/include/utils/numeric.h
@@ -85,6 +85,10 @@ extern Numeric numeric_div_opt_error(Numeric num1, Numeric num2,
 									 bool *have_error);
 extern Numeric numeric_mod_opt_error(Numeric num1, Numeric num2,
 									 bool *have_error);
-extern int32 numeric_int4_opt_error(Numeric num, bool *error);
+extern int16 numeric_int2_opt_error(Numeric num, bool *have_error);
+extern int32 numeric_int4_opt_error(Numeric num, bool *have_error);
+extern int64 numeric_int8_opt_error(Numeric num, bool *have_error);
+extern Numeric numeric_in_opt_error(char *str, int32 typmod,
+									bool *have_error);
 
 #endif							/* _PG_NUMERIC_H_ */
-- 
2.17.1

