From patchwork Wed Jun 1 18:44:07 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Martin Sebor X-Patchwork-Id: 628805 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3rKfQt6WWqz9sC4 for ; Thu, 2 Jun 2016 04:44:22 +1000 (AEST) Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b=gmlHuSEf; dkim-atps=neutral DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:to:references:from:message-id:date:mime-version :in-reply-to:content-type; q=dns; s=default; b=H68SvIB3vgfvqC1rR Mrmt9vCW7LZ5a4AN7KRz7NBTN2gdBC4fTZwOIim19owzCf5EBHJ4KlSPwvvRNZC6 0T5EwVUnY+mSH8WU8wkkwdXWnpWbW4oTRD8FWvC4xMXH/ugixZGs++gi1Uk4OodU fuVOxp7jFb36vNYQpq8MMuu9kA= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :subject:to:references:from:message-id:date:mime-version :in-reply-to:content-type; s=default; bh=+txC+OjWRv+zZJKhgEC+1PG GRP8=; b=gmlHuSEfWVQdtDxLczxaQucLM/LdM1IUqIf3oeioBrFWNcSey+LAJd1 ti9IepPtwkosou3A0R3Jf/azKP2LRGcb1iTin7f3xFFkLj96L/7yvF9iEett9cw8 H5BVVpm+1QREno2OL2s5brwgAEQKtSwRlH2NQTgCyDV93QK08XBM= Received: (qmail 4740 invoked by alias); 1 Jun 2016 18:44:16 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 4730 invoked by uid 89); 1 Jun 2016 18:44:15 -0000 Authentication-Results: sourceware.org; auth=none X-Virus-Found: No X-Spam-SWARE-Status: No, score=-2.6 required=5.0 tests=BAYES_00, FREEMAIL_FROM, RCVD_IN_DNSWL_LOW, SPF_PASS autolearn=ham version=3.3.2 spammy=psi, Reject X-HELO: mail-qg0-f42.google.com Received: from mail-qg0-f42.google.com (HELO mail-qg0-f42.google.com) (209.85.192.42) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with (AES128-GCM-SHA256 encrypted) ESMTPS; Wed, 01 Jun 2016 18:44:12 +0000 Received: by mail-qg0-f42.google.com with SMTP id 52so47470933qgy.0 for ; Wed, 01 Jun 2016 11:44:12 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:subject:to:references:from:message-id:date :user-agent:mime-version:in-reply-to; bh=DI7xCZKhwyprbT0FYzQTMrUE1C26nw17zPzrVSLUViI=; b=CDTTSpZbYdEvTjYhOFjBKu+LPObkcUF738RAv7lMmgrA6jhVtcHRgjDvdwv/7zxR9x klZPj4MIIq/GjTCssklbYMcytPghN/3Q3tNThUMLzw7giBC2TvPYWfkY9rMTpQxDpG0T FxPJu7HeLsyrxVAf+yqSwHtv+UYRjG//fVAkbRHNvN1RD8OaFyA5obVGUZDhC3NGVHEQ x5MzhhImT/P3UJOCZFW0sSGvavlte4iANYED5Lz4iAyWec0sg0ffT4sEM9FzbpyMj+H2 m1AaqtyLxU0aBMNNUr7460W1gUbB3LrbIbd6fk8eiGtoaVKnsi7phg/5r5F711A9k5zK VwYA== X-Gm-Message-State: ALyK8tKFG9dppCsz7CI4Pv3QufXQeSYsXyLlKDOXAj0pXfyFGMM9uDysKX2Vgvwbfo7epg== X-Received: by 10.140.33.130 with SMTP id j2mr36781693qgj.102.1464806650637; Wed, 01 Jun 2016 11:44:10 -0700 (PDT) Received: from [192.168.0.26] (75-166-216-131.hlrn.qwest.net. [75.166.216.131]) by smtp.gmail.com with ESMTPSA id h75sm10023821qge.30.2016.06.01.11.44.09 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 01 Jun 2016 11:44:09 -0700 (PDT) Subject: Re: [PATCH] c++/60760 - arithmetic on null pointers should not be allowed in constant expressions To: Jason Merrill , Gcc Patch List References: <573504DD.9040707@gmail.com> <574E0A41.9070204@gmail.com> From: Martin Sebor Message-ID: <574F2CF7.1000800@gmail.com> Date: Wed, 1 Jun 2016 12:44:07 -0600 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:38.0) Gecko/20100101 Thunderbird/38.1.0 MIME-Version: 1.0 In-Reply-To: X-IsSubscribed: yes >> The new code in cxx_eval_component_reference diagnoses the following >> problem that's not detected otherwise: >> >> struct S { const S *s; }; >> >> constexpr S s = { 0 }; >> >> constexpr const void *p = &s.s->s; > > Note that this falls under core issue 1530, which has not been resolved. I don't quite see the relevance of this issue. It's concerned with storage in which an object will exist at some point in the future when its lifetime begins or where it existed in the past before its lifetime ended. There is no object or storage at s.s above because s.s is null. It might perhaps be okay to accept the expression above as an extension with the rationale that the offset of the first member is zero and so its address must be a null pointer, but it surely wouldn't be okay in the modified test case below where the second member's offset is non-zero and there may not be a way to represent that offset as a pointer. This seems unquestionably undefined to me. Did I miss something? struct S { const S *s; int i; }; constexpr S s = { 0 }; constexpr const void *p = &s.s->i; > I believe that this is fine under the current wording; only "access" > to a non-static data member is undefined, and "access" is defined as > reading or writing. There has been discussion in the context of UBsan > about making this undefined, but that hasn't been decided yet. But I'm > OK with changing G++ to go in that direction. > > cxx_eval_component_reference could check whether 'whole' is a null (or > other invalid) lvalue; for that testcase it's an INDIRECT_REF of 0. Thanks for the hint. I've made that change and also adjusted the rest of the patch to avoid passing the nullptr_p around. It was surprisingly simpler to do than I remembered from my first attempt back in March. 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/testsuite/ChangeLog: 2016-05-12 Martin Sebor PR c++/60760 PR c++/71091 * g++.dg/cpp0x/constexpr-nullptr-2.C: New test. * gcc/testsuite/g++.dg/ubsan/pr63956.C: Adjust. gcc/cp/ChangeLog: 2016-05-12 Martin Sebor PR c++/60760 PR c++/71091 * constexpr.c (cxx_eval_binary_expression): Reject invalid arithmetic involving null pointers. (cxx_eval_constant_expression): Same. (cxx_eval_component_reference): Reject null pointer dereferences. diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c index 482f8af..d1fe276 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -1757,6 +1757,13 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t, || 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)) + { + 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); @@ -2097,6 +2104,11 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t, 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) @@ -3505,10 +3517,20 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, 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) @@ -3919,7 +3941,6 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, 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, @@ -3945,15 +3966,26 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, 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) + { + const char *msg = NULL; + if (integer_zerop (op)) + { + if (!same_type_ignoring_top_level_qualifiers_p (type, + TREE_TYPE (op))) + msg = "invalid conversion involving a null pointer"; + } + else + msg = "reinterpret_cast from integer to pointer"; + + if (msg) + { + if (!ctx->quiet) + error_at (EXPR_LOC_OR_LOC (t, input_location), msg); + + *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 diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C new file mode 100644 index 0000000..9ee1316 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-nullptr-2.C @@ -0,0 +1,191 @@ +// 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 x = 0; +constexpr int* ptr2 = ptr + x; // Adding zero is valid. +constexpr int* ptr3 = ptr - x; // As is subtracting zero. + +} + +// Test case from c++/71091. +namespace PR_71091 { + +constexpr int *p = 0; +constexpr int &r = *p; // { dg-error "null pointer" } + +} + +// Other testr 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" } + +} + +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(0); +constexpr int i2 = p0 - (int*)nullptr; +constexpr int i3 = p0 - static_cast(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(0) - p0; +constexpr int i9 = (int*)nullptr - p0; +constexpr int i10 = static_cast(nullptr) - p0; +constexpr int i11 = q0 - p0; +constexpr int i12 = r0 - p0; + +} + +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" } + +} diff --git a/gcc/testsuite/g++.dg/ubsan/pr63956.C b/gcc/testsuite/g++.dg/ubsan/pr63956.C index 25db8a4..ac01fa4 100644 --- a/gcc/testsuite/g++.dg/ubsan/pr63956.C +++ b/gcc/testsuite/g++.dg/ubsan/pr63956.C @@ -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)