This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] c++/60760 - arithmetic on null pointers should not be allowed in constant expressions


+  if (TREE_CODE (whole) == INDIRECT_REF
+      && integer_zerop (TREE_OPERAND (whole, 0))
+      && !ctx->quiet)
+    error ("dereferencing a null pointer in %qE", orig_whole);

+      if (TREE_CODE (t) == INTEGER_CST
+          && TREE_CODE (TREE_TYPE (t)) == POINTER_TYPE
+          && !integer_zerop (t))
+        {
+          if (!ctx->quiet)
+            error ("arithmetic involving a null pointer in %qE", t);
+        }

These places should all set *non_constant_p, and the second should
return t after doing so.  OK with that change.

Some additional testing exposed a couple of bugs in the patch.
First, adding or subtracting a constant zero to or from a null
pointer is valid (it was rejected with the previous patch).
Second, the code in cxx_eval_constant_expression that tried
to detect invalid conversions didn't handle qualifiers
correctly in all cases, causing conversions that add constness
to be rejected.  Finally, while fixing these issues I decided
that dereferencing and indirecting through null pointers would
be more appropriately handled in the functions that deal with
those expressions rather than in cxx_eval_constant_expression.

Since the updates aren't completely trivial I post the new
version of the patch for another review before committing it.

Thanks
Martin

PR c++/60760 - arithmetic on null pointers should not be allowed in constant
  expressions
PR c++/71091 - constexpr reference bound to a null pointer dereference
   accepted

gcc/cp/ChangeLog:
2016-06-20  Martin Sebor  <msebor@redhat.com>

	PR c++/60760
	PR c++/71091
	* constexpr.c (cxx_eval_binary_expression): Reject invalid expressions
	involving null pointers.
	(cxx_eval_component_reference): Reject null pointer dereferences.
	(cxx_eval_indirect_ref): Reject indirecting through null pointers.
	(cxx_eval_constant_expression): Reject invalid expressions involving
	null pointers.

gcc/testsuite/ChangeLog:
2016-06-20  Martin Sebor  <msebor@redhat.com>

	PR c++/60760
	PR c++/71091
	* g++.dg/cpp0x/constexpr-nullptr-2.C: New test.
	* g++.dg/cpp1y/constexpr-sfinae.C: Correct.
	* g++.dg/ubsan/pr63956.C: Correct.

Index: gcc/cp/constexpr.c
===================================================================
--- gcc/cp/constexpr.c	(revision 237582)
+++ gcc/cp/constexpr.c	(working copy)
@@ -1811,6 +1811,14 @@ cxx_eval_binary_expression (const constexpr_ctx *c
 		   || null_member_pointer_value_p (rhs)))
 	r = constant_boolean_node (!is_code_eq, type);
     }
+  if (code == POINTER_PLUS_EXPR && !*non_constant_p
+      && tree_int_cst_equal (lhs, null_pointer_node)
+      && !tree_int_cst_equal (rhs, integer_zero_node))
+    {
+      if (!ctx->quiet)
+        error ("arithmetic involving a null pointer in %qE", lhs);
+      return t;
+    }
 
   if (r == NULL_TREE)
     r = fold_binary_loc (loc, code, type, lhs, rhs);
@@ -2151,6 +2159,11 @@ cxx_eval_component_reference (const constexpr_ctx
   tree whole = cxx_eval_constant_expression (ctx, orig_whole,
 					     lval,
 					     non_constant_p, overflow_p);
+  if (TREE_CODE (whole) == INDIRECT_REF
+      && integer_zerop (TREE_OPERAND (whole, 0))
+      && !ctx->quiet)
+    error ("dereferencing a null pointer in %qE", orig_whole);
+
   if (TREE_CODE (whole) == PTRMEM_CST)
     whole = cplus_expand_constant (whole);
   if (whole == orig_whole)
@@ -2911,6 +2924,14 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, t
       if (*non_constant_p)
 	return t;
 
+      if (integer_zerop (op0))
+	{
+	  if (!ctx->quiet)
+	    error ("dereferencing a null pointer");
+	  *non_constant_p = true;
+	  return t;
+	}
+
       r = cxx_fold_indirect_ref (EXPR_LOCATION (t), TREE_TYPE (t), op0,
 				 &empty_base);
       if (r == NULL_TREE)
@@ -3559,10 +3580,20 @@ cxx_eval_constant_expression (const constexpr_ctx
 	  if (!flag_permissive || ctx->quiet)
 	    *overflow_p = true;
 	}
+
+      if (TREE_CODE (t) == INTEGER_CST
+          && TREE_CODE (TREE_TYPE (t)) == POINTER_TYPE
+          && !integer_zerop (t))
+        {
+          if (!ctx->quiet)
+            error ("arithmetic involving a null pointer in %qE", t);
+        }
+
       return t;
     }
 
-  switch (TREE_CODE (t))
+  tree_code tcode = TREE_CODE (t);
+  switch (tcode)
     {
     case RESULT_DECL:
       if (lval)
@@ -3973,7 +4004,6 @@ cxx_eval_constant_expression (const constexpr_ctx
     case NOP_EXPR:
     case UNARY_PLUS_EXPR:
       {
-	enum tree_code tcode = TREE_CODE (t);
 	tree oldop = TREE_OPERAND (t, 0);
 
 	tree op = cxx_eval_constant_expression (ctx, oldop,
@@ -3999,15 +4029,43 @@ cxx_eval_constant_expression (const constexpr_ctx
 		return t;
 	      }
 	  }
-	if (POINTER_TYPE_P (type)
-	    && TREE_CODE (op) == INTEGER_CST
-	    && !integer_zerop (op))
-	  {
-	    if (!ctx->quiet)
-	      error_at (EXPR_LOC_OR_LOC (t, input_location),
-			"reinterpret_cast from integer to pointer");
-	    *non_constant_p = true;
-	    return t;
+	if (POINTER_TYPE_P (type) && TREE_CODE (op) == INTEGER_CST)
+          {
+	    if (integer_zerop (op))
+	      {
+		if (TREE_CODE (type) == REFERENCE_TYPE)
+		  {
+		    if (!ctx->quiet)
+		      error_at (EXPR_LOC_OR_LOC (t, input_location),
+				"dereferencing a null pointer");
+		    *non_constant_p = true;
+		    return t;
+		  }
+		else if (TREE_CODE (TREE_TYPE (op)) == POINTER_TYPE)
+		  {
+		    tree t1 = TREE_TYPE (type);
+		    tree t2 = TREE_TYPE (TREE_TYPE (op));
+		
+		    if (!same_type_ignoring_top_level_qualifiers_p (t1, t2))
+		      {
+			if (!ctx->quiet)
+			  error_at (EXPR_LOC_OR_LOC (t, input_location),
+			    "invalid conversion involving a null pointer");
+			
+			*non_constant_p = true;
+			return t;
+		      }
+		  }
+	      }
+            else if (!integer_zerop (op))
+	      {
+		if (!ctx->quiet)
+		  error_at (EXPR_LOC_OR_LOC (t, input_location),
+			    "invalid conversion from %qT to %qT",
+			    TREE_TYPE (op), type);
+		*non_constant_p = true;
+		return t;
+	      }
 	  }
 	if (op == oldop && tcode != UNARY_PLUS_EXPR)
 	  /* We didn't fold at the top so we could check for ptr-int
Index: gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C
===================================================================
--- gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C	(nonexistent)
+++ gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C	(working copy)
@@ -0,0 +1,238 @@
+// PR c++/60760 - arithmetic on null pointers should not be allowed
+//     in constant expressions
+// PR c++/71091 - constexpr reference bound to a null pointer dereference
+//     accepted
+// { dg-do compile { target c++11 } }
+// { dg-additional-options "-Wno-pointer-arith" }
+
+// Generate a null poiinter.
+constexpr int* null () { return 0; }
+
+// Test case from comment #0 in c++/60760.
+namespace PR_60760_comment_0 {
+
+constexpr int* ptr = nullptr;
+constexpr int* ptr2 = ptr + 1;   // { dg-error "null pointer|not a constant" }
+
+}
+
+// Test case from comment #1 in c++/60760.
+namespace PR_60760_comment_1 {
+
+constexpr int* ptr = nullptr;
+
+constexpr int zero = 0;
+constexpr int* ptr2 = ptr + zero;   // Adding zero is valid.
+constexpr int* ptr3 = ptr - zero;   // As is subtracting zero.
+
+}
+
+// Test case from c++/71091.
+namespace PR_71091 {
+
+constexpr int *p = 0;
+constexpr const int &r = *p;   // { dg-error "dereferencing a null pointer" }
+
+}
+
+// Other test cases.
+namespace C {
+
+struct S { int a, b[1]; } s;
+
+constexpr S *p0 = &s;
+constexpr S *p1 = nullptr;
+constexpr int *r0 = p1->b;   // { dg-error "null pointer|constant expression" }
+
+// Adding and subtracting zero from and to a null pointer is valid.
+constexpr S* r1 = p1 + 0;
+constexpr S* r2 = r1 - 0;
+
+constexpr int zero = 0;
+
+constexpr S* r3 = r2 + zero;
+constexpr S* r4 = r3 - zero;
+
+static_assert (r4 == nullptr, "r4 == nullptr");
+
+constexpr const S *pcs = p0;
+constexpr int d1 = pcs - p0;
+constexpr int d2 = p0 - pcs;
+
+constexpr bool b = !p1 && !pcs;
+}
+
+namespace D {
+
+struct A { int i; const A *pa1; const A *pa0; };
+
+constexpr A a1 = { 0, 0, 0  };
+constexpr A a2 = { 1, &a1, 0 };
+
+constexpr const A *pa2 = &a2;
+constexpr int i0 = pa2->i;
+constexpr int i1 = pa2->pa1->i;
+constexpr int i2 = pa2->pa1->pa0->i;            // { dg-error "null pointer|not a constant" }
+
+constexpr const A *pa3 = &*pa2->pa1->pa0;
+constexpr const A *pa4 = pa2->pa1->pa0 + 1;     // { dg-error "null pointer|not a constant" }
+
+constexpr const int *pi0 = &pa2->pa1->pa0->i;   // { dg-error "null pointer|not a constant" }
+
+constexpr const A *pa5 = 0;
+constexpr const int *pi1 = &pa5->i;             // { dg-error "null pointer|not a constant" }
+
+}
+
+
+namespace SimpleTests {
+
+constexpr int* p0 = nullptr;
+constexpr int* q0 = p0;
+constexpr int* r0 = null ();
+
+// Adding or subtracting zero from a null pointer is valid in C++.
+constexpr int* p1 = p0 + 0;
+constexpr int* p2 = p0 - 0;
+constexpr int* p3 = 0 + p0;
+
+// While the text of the C++ standard still doesn't allow it, CWG
+// issue 232 implies that dererencing a null pointer is intended
+// to be permitted in contexts where the result isn't evaluated.
+// For compatibility with C that should at a minimum include
+// expressions like &*p that are valid there.
+constexpr int* p4 = &*p0;
+constexpr int* p5 = p0 + 1;       // { dg-error "null pointer|not a constant" }
+constexpr int* p6 = 1 + p0;       // { dg-error "null pointer|not a constant" }
+constexpr int* p7 = p0 - 1;       // { dg-error "null pointer|not a constant" }
+constexpr int* p8 = &p0 [0];
+constexpr int* p9 = &0 [p0];
+
+constexpr int* p10 = null () + 2; // { dg-error "null pointer|not a constant" }
+constexpr int* p11 = 3 + null (); // { dg-error "null pointer|not a constant" }
+constexpr int* p12 = null () - 4; // { dg-error "null pointer|not a constant" }
+constexpr int* p13 = &null ()[4]; // { dg-error "null pointer|not a constant" }
+constexpr int* p14 = &3[null ()]; // { dg-error "null pointer|not a constant" }
+
+constexpr int* q1 = q0 + 0;
+constexpr int* q2 = q0 - 0;
+constexpr int* q3 = q0 + 1;       // { dg-error "null pointer|not a constant" }
+constexpr int* q4 = q0 + 2;       // { dg-error "null pointer|not a constant" }
+constexpr int* q5 = &q0 [0];
+
+// Subtracting null pointers from one another is valid.
+constexpr int i0 = p0 - (int*)0;
+constexpr int i1 = p0 - static_cast<int*>(0);
+constexpr int i2 = p0 - (int*)nullptr;
+constexpr int i3 = p0 - static_cast<int*>(nullptr);
+constexpr int i4 = p0 - p0;
+constexpr int i5 = p0 - q0;
+constexpr int i6 = p0 - r0;
+constexpr int i7 = (int*)0 - p0;
+constexpr int i8 = static_cast<int*>(0) - p0;
+constexpr int i9 = (int*)nullptr - p0;
+constexpr int i10 = static_cast<int*>(nullptr) - p0;
+constexpr int i11 = q0 - p0;
+constexpr int i12 = r0 - p0;
+
+}
+
+namespace IncompleteTypeTests {
+
+// The type must be complete.
+struct X;
+constexpr X *px0 = nullptr;
+constexpr X *px1 = px0 + 0;     // { dg-error "invalid use of incomplete type"  }
+constexpr X *px2 = px0 - 0;     // { dg-error "invalid use of incomplete type"  }
+constexpr X *px3 = px0 - px0;   // { dg-error "invalid use of incomplete type"  }
+
+}
+
+namespace IndirectTests {
+
+struct S { int i, j; struct SA { struct SB { int *pi; } sb; } sa; };
+
+constexpr S* ps = (S*)0;
+
+// Comparing null pointers is valid.
+constexpr bool b0 = ps == ps;
+constexpr bool b1 = ps != ps;
+constexpr bool b2 = ps <  ps;
+constexpr bool b3 = ps <= ps;
+constexpr bool b4 = ps >  ps;
+constexpr bool b5 = ps >= ps;
+
+constexpr bool b6 = ps == (S*)0;
+constexpr bool b7 = ps != (S*)0;
+constexpr bool b8 = ps <  (S*)0;
+constexpr bool b9 = ps <= (S*)0;
+constexpr bool b10 = ps >  (S*)0;
+constexpr bool b11 = ps >= (S*)0;
+
+constexpr S* ps1 = ps;
+constexpr S* ps2 = ps1;
+
+// The following aren't diagnosed due to a bug.
+// constexpr int* pi0 = &((S*)0)->i;
+// constexpr int* pi1 = &((S*)nullptr)->i;
+
+constexpr int* pj0 = &((S*)0)->j;        // { dg-error "null pointer|not a constant" }
+constexpr int* pj1 = &((S*)nullptr)->j;  // { dg-error "null pointer|not a constant" }
+
+constexpr int* psi = &ps->i;            // { dg-error "null pointer|not a constant" }
+constexpr int* psj = &ps->j;            // { dg-error "null pointer|not a constant" }
+
+constexpr int* ps1i = &ps1->i;          // { dg-error "null pointer|not a constant" }
+constexpr int* ps2i = &ps1->i;          // { dg-error "null pointer|not a constant" }
+
+constexpr int* ps1j = &ps1->j;          // { dg-error "null pointer|not a constant" }
+constexpr int* ps2j = &ps1->j;          // { dg-error "null pointer|not a constant" }
+
+}
+
+namespace FunctionTests {
+
+typedef void Func ();
+
+// Arithmetic on member function pointers is diagnosed with -Wpointer-arith.
+// With constexpr, only zero may be added or subtracted.
+constexpr Func *pf0 = 0;
+constexpr Func *pf1 = pf0 + 0;  // triggers -Wpointer-arith
+constexpr Func *pf2 = pf0 - 0;  // triggers -Wpointer-arith
+constexpr Func *pf3 = 0 + pf0;  // triggers -Wpointer-arith
+constexpr Func *pf4 = pf0 + 1;  // { dg-error "null pointer|not a constant" }
+constexpr Func *pf5 = 2 + pf0;  // { dg-error "null pointer|not a constant" }
+constexpr Func *pf6 = pf0 - 3;  // { dg-error "null pointer|not a constant" }
+
+struct S;
+typedef void (S::*MemFuncPtr)();
+
+// Arithmetic on member function pointers is rejected with a hard error.
+constexpr MemFuncPtr pmf0 = nullptr;
+constexpr MemFuncPtr pmf1 = pmf0 + 0;   // { dg-error "invalid operands" }
+constexpr MemFuncPtr pmf2 = 0 + pmf0;   // { dg-error "invalid operands" }
+constexpr MemFuncPtr pmf3 = pmf0 + 1;   // { dg-error "invalid operands" }
+constexpr MemFuncPtr pmf4 = 1 + pmf0;   // { dg-error "invalid operands" }
+constexpr MemFuncPtr pmf5 = pmf0 - 1;   // { dg-error "invalid operands" }
+
+}
+
+namespace ConversionTest {
+
+struct A {
+  int *p;
+};
+
+constexpr const int* f (const int *p) { return p; }
+
+void f ()
+{
+  static_assert (!f (0), "f (a.p)");
+  static_assert (!f (nullptr), "f (a.p)");
+
+  constexpr A a = A ();
+
+  static_assert (!f (a.p), "f (a.p)");
+}
+
+}
Index: gcc/testsuite/g++.dg/cpp1y/constexpr-sfinae.C
===================================================================
--- gcc/testsuite/g++.dg/cpp1y/constexpr-sfinae.C	(revision 237582)
+++ gcc/testsuite/g++.dg/cpp1y/constexpr-sfinae.C	(working copy)
@@ -90,22 +90,28 @@ namespace NullPointerArithmetic {
 constexpr int i = 0;
 constexpr const int* a[] = { 0, &i };
 
-// Well-defined core constant expressoons involving null pointers.
+// Well-defined core constant expressions involving null pointers.
 constexpr __PTRDIFF_TYPE__ d00 = a [0] - a [0];
 constexpr __PTRDIFF_TYPE__ d11 = a [1] - a [1];
 
-// Undefined core constant expressoons involving null pointers.
+// Undefined core constant expressions involving null pointers.
 // constexpr __PTRDIFF_TYPE__ d01 = a [0] - a [1];
 // constexpr __PTRDIFF_TYPE__ d10 = a [1] - a [0];
 
-constexpr bool nullptr_sub_0 (int i, int j) { return 1 + a [i != 0] - a [j]; }
+// Valid when i == j.
+constexpr bool
+nullptr_sub_0 (bool i, bool j) { return 1 + a [!i] - a [!j]; }
 
-constexpr bool nullptr_sub_1 (int i, int j) { return 1 + a [i == 0] - a [j]; }
+// Valid when i != j.
+constexpr bool
+nullptr_sub_1 (bool i, bool j) { return 1 + a [i] - a [!j]; }
 
-template <int I>
+// Selected when I == 0.
+template <bool I>
 constexpr int f (int (*)[nullptr_sub_0 (I, 0)] = 0) { return 0; }
 
-template <int I>
+// Selected when I != 0.
+template <bool I>
 constexpr int f (int (*)[nullptr_sub_1 (I, 0)] = 0) { return 1; }
 
 constexpr int n0 = f<0>();
Index: gcc/testsuite/g++.dg/ubsan/pr63956.C
===================================================================
--- gcc/testsuite/g++.dg/ubsan/pr63956.C	(revision 237582)
+++ gcc/testsuite/g++.dg/ubsan/pr63956.C	(working copy)
@@ -92,7 +92,7 @@ constexpr int
 fn6 (const int &a, int b)
 {
   if (b != 2)
-    b = a;  // { dg-error "is not a constant expression" }
+    b = a;
   return b;
 }
 
@@ -106,7 +106,7 @@ fn7 (const int *a, int b)
 
 constexpr int n1 = 7;
 constexpr int n2 = fn7 (&n1, 5);
-constexpr int n3 = fn7 ((const int *) 0, 8);
+constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-error "null pointer" }
 
 constexpr int
 fn8 (int i)

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]