On Sun, Jan 26, 2014 at 02:13:03PM +0100, Florian Pflug wrote:
> On Jan26, 2014, at 03:50 , Bruce Momjian <br...@momjian.us> wrote:
> > Patch attached.
> 
> > +   if ((float)tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX)
> > +           return -1;
> 
> Is this bullet-proof? If float and int are both 32-bit, the float's mantissa
> will be less than 32-bit (24 or so, I think), and thus won't be able to
> represent INT_MAX accurately. This means there's a value of
> tm_year * MONTHS_PER_YEAR which is less than INT_MAX, yet for which
> tm_year * MONTHS_PER_YEAR + tm_mon will return tm_year * MONTHS_PER_YEAR
> unmodified if tm_mon is small enough (e.g, 1). But if tm_year * 
> MONTHS_PER_YEAR
> was close enough to INT_MAX, the actual value of
> tm_year * MONTHS_PER_YEAR + tm_mon might still be larger than INT_MAX,
> the floating-point computation just won't detect it. In that case, the
> check would succeed, yet the actual integer computation would still overflow.
> 
> It should be safe with "double" instead of "float".

You are correct.  I have adjusted the code to use "double".

> > +   if (SAMESIGN(span1->month, span2->month) &&
> > +           !SAMESIGN(result->month, span1->month))
> 
> This assumes wrapping semantics for signed integral types, which isn't
> guaranteed by the C standard - it says overflows of signed integral types
> produce undefined results. We currently depend on wrapping semantics for
> these types in more places, and therefore need GCC's "-fwrapv" anway, but
> I still wonder if adding more of these kinds of checks is a good idea.

Well, I copied this from int.c, so what I did was to mention the other
function I got this code from.  If we ever change int.c we would need to
change this too.

The updated attached patch has overflow detection for interval
subtraction, multiply, and negate.  There are also some comparison
cleanups.

-- 
  Bruce Momjian  <br...@momjian.us>        http://momjian.us
  EnterpriseDB                             http://enterprisedb.com

  + Everyone has their own god. +
diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml
new file mode 100644
index 6bf4cf6..b7d7d80
*** a/doc/src/sgml/datatype.sgml
--- b/doc/src/sgml/datatype.sgml
*************** SELECT E'\\xDEADBEEF';
*** 1587,1593 ****
         </row>
         <row>
          <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
!         <entry>12 bytes</entry>
          <entry>time interval</entry>
          <entry>-178000000 years</entry>
          <entry>178000000 years</entry>
--- 1587,1593 ----
         </row>
         <row>
          <entry><type>interval [ <replaceable>fields</replaceable> ] [ (<replaceable>p</replaceable>) ]</type></entry>
!         <entry>16 bytes</entry>
          <entry>time interval</entry>
          <entry>-178000000 years</entry>
          <entry>178000000 years</entry>
diff --git a/src/backend/utils/adt/datetime.c b/src/backend/utils/adt/datetime.c
new file mode 100644
index a61b40e..70284e9
*** a/src/backend/utils/adt/datetime.c
--- b/src/backend/utils/adt/datetime.c
*************** DecodeInterval(char **field, int *ftype,
*** 2976,2981 ****
--- 2976,2983 ----
  					type = DTK_MONTH;
  					if (*field[i] == '-')
  						val2 = -val2;
+ 					if (((double)val * MONTHS_PER_YEAR + val2) > INT_MAX)
+ 						return DTERR_FIELD_OVERFLOW;
  					val = val * MONTHS_PER_YEAR + val2;
  					fval = 0;
  				}
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
new file mode 100644
index 4581862..3b8e8e8
*** a/src/backend/utils/adt/timestamp.c
--- b/src/backend/utils/adt/timestamp.c
***************
*** 41,46 ****
--- 41,47 ----
  #error -ffast-math is known to break this code
  #endif
  
+ #define SAMESIGN(a,b)   (((a) < 0) == ((b) < 0))
  
  /* Set at postmaster start */
  TimestampTz PgStartTime;
*************** interval2tm(Interval span, struct pg_tm
*** 1694,1700 ****
  #ifdef HAVE_INT64_TIMESTAMP
  	tfrac = time / USECS_PER_HOUR;
  	time -= tfrac * USECS_PER_HOUR;
! 	tm->tm_hour = tfrac;		/* could overflow ... */
  	tfrac = time / USECS_PER_MINUTE;
  	time -= tfrac * USECS_PER_MINUTE;
  	tm->tm_min = tfrac;
--- 1695,1705 ----
  #ifdef HAVE_INT64_TIMESTAMP
  	tfrac = time / USECS_PER_HOUR;
  	time -= tfrac * USECS_PER_HOUR;
! 	tm->tm_hour = tfrac;
! 	if (!SAMESIGN(tm->tm_hour, tfrac))
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
! 				 errmsg("interval out of range")));
  	tfrac = time / USECS_PER_MINUTE;
  	time -= tfrac * USECS_PER_MINUTE;
  	tm->tm_min = tfrac;
*************** recalc:
*** 1725,1731 ****
  int
  tm2interval(struct pg_tm * tm, fsec_t fsec, Interval *span)
  {
! 	span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
  	span->day = tm->tm_mday;
  #ifdef HAVE_INT64_TIMESTAMP
  	span->time = (((((tm->tm_hour * INT64CONST(60)) +
--- 1730,1740 ----
  int
  tm2interval(struct pg_tm * tm, fsec_t fsec, Interval *span)
  {
! 	double total_months = (double)tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
! 
! 	if (total_months > INT_MAX || total_months < INT_MIN)
! 		return -1;
! 	span->month = total_months;
  	span->day = tm->tm_mday;
  #ifdef HAVE_INT64_TIMESTAMP
  	span->time = (((((tm->tm_hour * INT64CONST(60)) +
*************** interval_um(PG_FUNCTION_ARGS)
*** 2826,2833 ****
--- 2835,2855 ----
  	result = (Interval *) palloc(sizeof(Interval));
  
  	result->time = -interval->time;
+ 	/* overflow check copied from int4um */
+ 	if (interval->time != 0 && SAMESIGN(result->time, interval->time))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
  	result->day = -interval->day;
+ 	if (interval->day != 0 && SAMESIGN(result->day, interval->day))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
  	result->month = -interval->month;
+ 	if (interval->month != 0 && SAMESIGN(result->month, interval->month))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
  
  	PG_RETURN_INTERVAL_P(result);
  }
*************** interval_pl(PG_FUNCTION_ARGS)
*** 2872,2879 ****
--- 2894,2919 ----
  	result = (Interval *) palloc(sizeof(Interval));
  
  	result->month = span1->month + span2->month;
+ 	/* overflow check copied from int4pl */
+ 	if (SAMESIGN(span1->month, span2->month) &&
+ 		!SAMESIGN(result->month, span1->month))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
+ 
  	result->day = span1->day + span2->day;
+ 	if (SAMESIGN(span1->day, span2->day) &&
+ 		!SAMESIGN(result->day, span1->day))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
+ 
  	result->time = span1->time + span2->time;
+ 	if (SAMESIGN(span1->time, span2->time) &&
+ 		!SAMESIGN(result->time, span1->time))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
  
  	PG_RETURN_INTERVAL_P(result);
  }
*************** interval_mi(PG_FUNCTION_ARGS)
*** 2888,2895 ****
--- 2928,2954 ----
  	result = (Interval *) palloc(sizeof(Interval));
  
  	result->month = span1->month - span2->month;
+ 	/* overflow check copied from int4mi */
+ 	if (!SAMESIGN(span1->month, span2->month) &&
+ 		!SAMESIGN(result->month, span1->month))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
+ 
  	result->day = span1->day - span2->day;
+ 	if (!SAMESIGN(span1->day, span2->day) &&
+ 		!SAMESIGN(result->day, span1->day))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
+ 
  	result->time = span1->time - span2->time;
+ 	if (!SAMESIGN(span1->time, span2->time) &&
+ 		!SAMESIGN(result->time, span1->time))
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
+ 				 errmsg("interval out of range")));
+ 
  
  	PG_RETURN_INTERVAL_P(result);
  }
*************** interval_mul(PG_FUNCTION_ARGS)
*** 2906,2920 ****
  	Interval   *span = PG_GETARG_INTERVAL_P(0);
  	float8		factor = PG_GETARG_FLOAT8(1);
  	double		month_remainder_days,
! 				sec_remainder;
  	int32		orig_month = span->month,
  				orig_day = span->day;
  	Interval   *result;
  
  	result = (Interval *) palloc(sizeof(Interval));
  
! 	result->month = (int32) (span->month * factor);
! 	result->day = (int32) (span->day * factor);
  
  	/*
  	 * The above correctly handles the whole-number part of the month and day
--- 2965,2991 ----
  	Interval   *span = PG_GETARG_INTERVAL_P(0);
  	float8		factor = PG_GETARG_FLOAT8(1);
  	double		month_remainder_days,
! 				sec_remainder,
! 				result_double;
  	int32		orig_month = span->month,
  				orig_day = span->day;
  	Interval   *result;
  
  	result = (Interval *) palloc(sizeof(Interval));
  
! 	result_double = span->month * factor;
! 	if (result_double > INT_MAX || result_double < INT_MIN)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
! 				 errmsg("interval out of range")));
! 	result->month = (int32) result_double;
! 
! 	result_double = span->day * factor;
! 	if (result_double > INT_MAX || result_double < INT_MIN)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
! 				 errmsg("interval out of range")));
! 	result->day = (int32) result_double;
  
  	/*
  	 * The above correctly handles the whole-number part of the month and day
*************** interval_mul(PG_FUNCTION_ARGS)
*** 2954,2960 ****
  	/* cascade units down */
  	result->day += (int32) month_remainder_days;
  #ifdef HAVE_INT64_TIMESTAMP
! 	result->time = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
  #else
  	result->time = span->time * factor + sec_remainder;
  #endif
--- 3025,3036 ----
  	/* cascade units down */
  	result->day += (int32) month_remainder_days;
  #ifdef HAVE_INT64_TIMESTAMP
! 	result_double = rint(span->time * factor + sec_remainder * USECS_PER_SEC);
! 	if (result_double > INT64_MAX || result_double < INT64_MIN)
! 		ereport(ERROR,
! 				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
! 				 errmsg("interval out of range")));
! 	result->time = (int64) result_double;
  #else
  	result->time = span->time * factor + sec_remainder;
  #endif
diff --git a/src/interfaces/ecpg/pgtypeslib/interval.c b/src/interfaces/ecpg/pgtypeslib/interval.c
new file mode 100644
index efa775d..d603fd4
*** a/src/interfaces/ecpg/pgtypeslib/interval.c
--- b/src/interfaces/ecpg/pgtypeslib/interval.c
*************** recalc:
*** 1036,1041 ****
--- 1036,1043 ----
  static int
  tm2interval(struct tm * tm, fsec_t fsec, interval * span)
  {
+ 	if ((double)tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon > INT_MAX)
+ 		return -1;
  	span->month = tm->tm_year * MONTHS_PER_YEAR + tm->tm_mon;
  #ifdef HAVE_INT64_TIMESTAMP
  	span->time = (((((((tm->tm_mday * INT64CONST(24)) +
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to