Bug 89285 - [8 Regression] ICE after casting the this pointer in the constructor in C++17 mode
Summary: [8 Regression] ICE after casting the this pointer in the constructor in C++17...
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 8.2.0
: P2 normal
Target Milestone: 9.0
Assignee: Jakub Jelinek
URL:
Keywords: accepts-invalid, ice-on-valid-code
Depends on:
Blocks: 86953
  Show dependency treegraph
 
Reported: 2019-02-11 13:15 UTC by Guus Sliepen
Modified: 2021-05-14 11:34 UTC (History)
4 users (show)

See Also:
Host:
Target:
Build:
Known to work: 9.0
Known to fail: 8.3.0
Last reconfirmed: 2019-02-11 00:00:00


Attachments
Minimal reproducer code (191 bytes, text/plain)
2019-02-11 13:15 UTC, Guus Sliepen
Details
gcc9-pr89285-wip.patch (1.82 KB, patch)
2019-02-12 11:31 UTC, Jakub Jelinek
Details | Diff
gcc9-pr89285-wip.patch (1.12 KB, patch)
2019-02-12 11:59 UTC, Jakub Jelinek
Details | Diff
gcc9-pr89285-wip.patch (2.57 KB, patch)
2019-02-12 14:06 UTC, Jakub Jelinek
Details | Diff
gcc9-pr89285-wip.patch (3.21 KB, patch)
2019-02-12 15:35 UTC, Jakub Jelinek
Details | Diff
gcc9-pr89285.patch (5.71 KB, patch)
2019-02-18 19:03 UTC, Jakub Jelinek
Details | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Guus Sliepen 2019-02-11 13:15:43 UTC
Created attachment 45656 [details]
Minimal reproducer code

While developing some code that links nested struct variables together in a linked list at compile time, I got an ICE when I modified my code to, instead of
storing pointers in the linked list, stored relative offsets. It seems that the ICE was triggered due to doing a reinterpret_cast<char *>(this) inside a constructor, although I might be wrong about that.

GCC version: gcc (Debian 8.2.0-20) 8.2.0
System type: Debian GNU/Linux unstable on an AMD64 machine

> g++ -std=c++17 -c reproducer.cc
during RTL pass: expand
reproducer.cc: In function ‘void __static_initialization_and_destruction_0(int, int)’:
reproducer.cc:11:43: internal compiler error: in expand_expr_real_1, at expr.c:10043
   *child = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(child);
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0x7f4478edc09a __libc_start_main
	../csu/libc-start.c:308
Comment 1 Guus Sliepen 2019-02-11 13:32:36 UTC
Comment on attachment 45656 [details]
Minimal reproducer code

The while-loop can be removed as well, and it will still give an ICE.
Comment 2 Jakub Jelinek 2019-02-11 14:59:35 UTC
This started to ICE with r259629 and stopped ICEing with r267272.
Not really sure if this is ice-on-invalid (and thus accepts-invalid) on the trunk or just ice-on-valid on the branch, given the reinterpret_casts in the constexpr body.
Comment 3 Jonathan Wakely 2019-02-11 15:28:49 UTC
I think it's valid (but the constructor can't be used in a constant expression).
Comment 4 Jakub Jelinek 2019-02-11 15:44:54 UTC
Ok, so:
struct A {
  int a {};
};
struct B {
  int b {};
  constexpr B (A *x) {
    int *c = &x->a;
    while (*c)
      c = reinterpret_cast<int *>((reinterpret_cast<char *>(c) + *c));
    *c = reinterpret_cast<char *>(this) - reinterpret_cast<char *>(c);
  }
};
struct C : A {
  B bar {this};
};

constexpr C foo {};

would then be accepts-invalid.

I've tried to fix that by doing:
--- gcc/cp/cp-gimplify.c	(revision 268765)
+++ gcc/cp/cp-gimplify.c	(working copy)
@@ -2307,6 +2307,22 @@ cp_fold (tree x)
 	  && TREE_OVERFLOW_P (x) && !TREE_OVERFLOW_P (op0))
 	TREE_OVERFLOW (x) = false;
 
+      /* Preserve REINTERPRET_CAST_P.  */
+      if (code == NOP_EXPR && REINTERPRET_CAST_P (org_x))
+	{
+	  if (TREE_CODE (x) == NOP_EXPR && REINTERPRET_CAST_P (x))
+	    break;
+	  if (TREE_CODE (x) == NOP_EXPR)
+	    {
+	      x = copy_node (x);
+	      REINTERPRET_CAST_P (x) = 1;
+	    }
+	  else
+	    {
+	      x = build1_loc (loc, code, TREE_TYPE (x), x);
+	      REINTERPRET_CAST_P (x) = 1;
+	    }
+	}
       break;
 
     case INDIRECT_REF:
and with that we reject the testcase again (like we've done in 8.x, so this part is [9 Regression] accepts-invalid).
But that also means we ICE again on the:
C bar {};

So, maybe we want to ignore that:
      if (REINTERPRET_CAST_P (t))
        {
          if (!ctx->quiet)
            error_at (cp_expr_loc_or_loc (t, input_location),
                      "a reinterpret_cast is not a constant expression");
          *non_constant_p = true;
          return t;
        }
for perhaps !ctx->manifestly_const_eval , as in when we really don't require constant expression.  But we don't really call constexpr with zero manifestly_const_eval anyway.
Comment 5 Jakub Jelinek 2019-02-11 15:48:35 UTC
Though, I wonder if something isn't wrong with the r267272 commit, I'd have thought that constexpr evaluation should be done on the pre-cp_folded bodies and cp_fold could be removing REINTERPRET_CAST_Ps.
Comment 6 Jakub Jelinek 2019-02-11 17:12:19 UTC
Bisected that to the finish_id_expression change, reverting the penultimate and antepenultimate hunk of https://gcc.gnu.org/viewcvs/gcc/trunk/gcc/cp/semantics.c?limit_changes=0&r1=267272&r2=267271&pathrev=267272
makes gcc trunk reject the #c4 testcase again and ICE again with C bar {} which should be valid.  Ah, and likely that is the reason why the #c4 patch makes a difference, previously it was just a REINTERPRET_CAST_P NOP_EXPR around PARM_DECL, but now it is REINTERPRET_CAST_P NOP_EXPR around the location wrapper nop and cp_fold optimizes that away.
Comment 7 Jakub Jelinek 2019-02-11 18:05:26 UTC
Ah, so one problem is that while we save the inline bodies of functions before cp_fold_function, cp_fold_function is destructive and clobbers the saved copy.
cp_fold itself is (hopefully) not destructive and creates new trees, but cp_fold_function is destructive and happily modifies the trees shared with something else (in this case with the constexpr funcdef table).

One way out of this is throwing away cp_fold_r/cp_fold_function and teaching cp_fold to handle all the trees that cp_walk_tree can walk (dunno if we want to cache everything or not).  That means handling e.g. STATEMENT_LIST, BIND_EXPR, *_STMT etc.
Another possibility is to use cp_fold_r the way it is for non-constexpr functions, and for constexpr functions use instead a hand written walker that will handle everything that can appear in constexpr functions with copy on write behavior, say if it is processing a STATEMENT_LIST and determines a recursive call wants to fold one of the statements, unshare the whole STATEMENT_LIST, modify it on the copy (and the parent would notice and unshare etc.).
Yet another possibility is to use the inliner's copy_tree_body_r for this in
register_constexpr_fundef, but in a way that we don't create new decls.  I'm afraid it would still create new VLA types, can those appear in constexpr functions?

Jason, any preferences here?

Note, the above would fix the [9 Regression] part of this PR, that we don't reject that #c4 testcase.  We'd still need to figure out a fix for the [8 Regression] part that once this [9 Regression] part is fixed would become [8/9 Regression].  I guess it could fix various other accepts-invalid cases where the cp_folding which modifies the saved tree makes us accept invalid constexpr code.
Comment 8 Jakub Jelinek 2019-02-11 20:55:08 UTC
I'll try to copy & adjust (mostly kill almost everything) copy_tree_body_r and use that in register_constexpr_fundef.  As we don't need to remap decls or types, 
I think we just need copy_statement_list for STATEMENT_LIST, remap_save_expr for SAVE_EXPR/TARGET_EXPR, copy_tree_r for the rest?
Comment 9 Jason Merrill 2019-02-11 21:19:37 UTC
(In reply to Jakub Jelinek from comment #4)
> +      /* Preserve REINTERPRET_CAST_P.  */
> +      if (code == NOP_EXPR && REINTERPRET_CAST_P (org_x))
> +	{
> +	  if (TREE_CODE (x) == NOP_EXPR && REINTERPRET_CAST_P (x))
> +	    break;
> +	  if (TREE_CODE (x) == NOP_EXPR)
> +	    {
> +	      x = copy_node (x);
> +	      REINTERPRET_CAST_P (x) = 1;
> +	    }
> +	  else
> +	    {
> +	      x = build1_loc (loc, code, TREE_TYPE (x), x);
> +	      REINTERPRET_CAST_P (x) = 1;
> +	    }
> +	}
>        break;

Looks good, though can't we assume that if x != org_x it's a new NOP_EXPR so we can just set REINTERPRET_CAST_P?

> and with that we reject the testcase again (like we've done in 8.x, so this
> part is [9 Regression] accepts-invalid).
> But that also means we ICE again on the:
> C bar {};

I'm not seeing this.

> So, maybe we want to ignore that:
>       if (REINTERPRET_CAST_P (t))
>         {
>           if (!ctx->quiet)
>             error_at (cp_expr_loc_or_loc (t, input_location),
>                       "a reinterpret_cast is not a constant expression");
>           *non_constant_p = true;
>           return t;
>         }
> for perhaps !ctx->manifestly_const_eval , as in when we really don't require
> constant expression.

That should depend on ctx->strict, not manifestly_const_eval.

(In reply to Jakub Jelinek from comment #7)
> Ah, so one problem is that while we save the inline bodies of functions
> before cp_fold_function, cp_fold_function is destructive and clobbers the
> saved copy.
> cp_fold itself is (hopefully) not destructive and creates new trees, but
> cp_fold_function is destructive and happily modifies the trees shared with
> something else (in this case with the constexpr funcdef table).
> 
> Jason, any preferences here?

My theory has been to switch to saving pre-gimplification bodies of constexpr functions and doing constant evaluation using them.
Comment 10 Jakub Jelinek 2019-02-12 11:31:21 UTC
Created attachment 45666 [details]
gcc9-pr89285-wip.patch

Non-working WIP.

I've tried this, thinking that we don't really need to duplicate decls etc. in the function and could just copy the statements.  Unfortunately, there are issues with that:
1) the genericization modifies in place the PARM_DECLs and RESULT_DECL, so mixing the pre-cp_fold_function/cp_genericize* statements (body) with the post cp_genericize* is not going to work flawlessly
2) apparently cxx_eval_constant_expression assumes post genericization IL:
      if (STATEMENT_CODE_P (TREE_CODE (t)))
        {
          /* This function doesn't know how to deal with pre-genericize
             statements; this can only happen with statement-expressions,
             so for now just fail.  */
          if (!ctx->quiet)
            error_at (EXPR_LOCATION (t),
                      "statement is not a constant expression");
        }
so it is upset e.g. about WHILE_STMT and others.

From the language conformance POV though, I think it is highly desirable to evaluate constexpr functions on the original, pre-folding/genericization, trees, because the middle-end (e.g. match.pd, fold-const.c) is not willing to guarantee not to fold away stuff that should be diagnosed as constexpr violations.

So, for 1), I'd think we could just use copy_fn and remember not just the body, but also parms and result in constexpr_fundef
For 2), I guess that would mean teaching constexpr.c about the pre-genericization *_STMT constructs.
Comment 11 Jakub Jelinek 2019-02-12 11:59:48 UTC
Created attachment 45667 [details]
gcc9-pr89285-wip.patch

Updated patch to address 1).  For 2), I guess we need to handle e.g.
CLEANUP_STMT, IF_STMT, FOR_STMT, WHILE_STMT, DO_STMT, SWITCH_STMT, CONTINUE_STMT, BREAK_STMT in cxx_eval_constant_expression.
Comment 12 Jakub Jelinek 2019-02-12 14:06:44 UTC
Created attachment 45671 [details]
gcc9-pr89285-wip.patch

Updated version that handles the above mentioned C++ stmt trees.

That said, there are still various regressions:
/usr/src/gcc/obj/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/vector.tcc:636:48:   in 'constexpr' expansion of 'std::vector<int, std::allocator<int> >::_S_use_relocate()'
/usr/src/gcc/obj/x86_64-pc-linux-gnu/libstdc++-v3/include/bits/vector.tcc:636:8: error: modification of '#'result_decl' not supported by dump_expr#<expression error>' is not a constant expression
etc.
Comment 13 Jakub Jelinek 2019-02-12 15:35:41 UTC
Created attachment 45673 [details]
gcc9-pr89285-wip.patch

Updated WIP patch.  There are still various regressions, but it is getting better.  Before I continue further on this, I'd like to know if I'm not going completely in a wrong direction.
Comment 14 Jakub Jelinek 2019-02-18 19:03:13 UTC
Created attachment 45752 [details]
gcc9-pr89285.patch

Updated untested patch for the constexpr evaluation on pre-folding trees.
Comment 15 Jakub Jelinek 2019-02-21 21:21:56 UTC
Author: jakub
Date: Thu Feb 21 21:21:25 2019
New Revision: 269078

URL: https://gcc.gnu.org/viewcvs?rev=269078&root=gcc&view=rev
Log:
	PR c++/89285
	* builtins.c (fold_builtin_arith_overflow): If first two args are
	INTEGER_CSTs, set intres and ovfres to constants rather than calls
	to ifn.

	* constexpr.c (struct constexpr_fundef): Add parms and result members.
	(retrieve_constexpr_fundef): Adjust for the above change.
	(register_constexpr_fundef): Save constexpr body with copy_fn,
	temporarily set DECL_CONTEXT on DECL_RESULT before that.
	(get_fundef_copy): Change FUN argument to FUNDEF with
	constexpr_fundef * type, grab body and parms/result out of
	constexpr_fundef struct and temporarily change it for copy_fn calls
	too.
	(cxx_eval_builtin_function_call): For __builtin_FUNCTION temporarily
	adjust current_function_decl from ctx->call context.  Test
	!potential_constant_expression instead of !is_constant_expression.
	(cxx_bind_parameters_in_call): Grab parameters from new_call.  Undo
	convert_for_arg_passing changes for TREE_ADDRESSABLE type passing.
	(cxx_eval_call_expression): Adjust get_fundef_copy caller.
	(cxx_eval_conditional_expression): For IF_STMT, allow then or else
	operands to be NULL.
	(label_matches): Handle BREAK_STMT and CONTINUE_STMT.
	(cxx_eval_loop_expr): Add support for FOR_STMT, WHILE_STMT and DO_STMT.
	(cxx_eval_switch_expr): Add support for SWITCH_STMT.
	(cxx_eval_constant_expression): Handle IF_STMT, FOR_STMT, WHILE_STMT,
	DO_STMT, CONTINUE_STMT, SWITCH_STMT, BREAK_STMT and CONTINUE_STMT.
	For SIZEOF_EXPR, recurse on the result of fold_sizeof_expr.  Ignore
	DECL_EXPR with USING_DECL operand.
	* lambda.c (maybe_add_lambda_conv_op): Build thisarg using
	build_int_cst to make it a valid constant expression.

	* g++.dg/ubsan/vptr-4.C: Expect reinterpret_cast errors.
	* g++.dg/cpp1y/constexpr-84192.C (f2): Adjust expected diagnostics.
	* g++.dg/cpp1y/constexpr-70265-2.C (foo): Adjust expected line of
	diagnostics.
	* g++.dg/cpp1y/constexpr-89285.C: New test.
	* g++.dg/cpp0x/constexpr-arith-overflow.C (add, sub, mul): Ifdef out
	for C++11.
	(TEST_ADD, TEST_SUB, TEST_MUL): Define to Assert (true) for C++11.
	* g++.dg/cpp0x/constexpr-arith-overflow2.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp0x/constexpr-arith-overflow2.C
    trunk/gcc/testsuite/g++.dg/cpp1y/constexpr-89285.C
Modified:
    trunk/gcc/ChangeLog
    trunk/gcc/builtins.c
    trunk/gcc/cp/ChangeLog
    trunk/gcc/cp/constexpr.c
    trunk/gcc/cp/lambda.c
    trunk/gcc/testsuite/ChangeLog
    trunk/gcc/testsuite/g++.dg/cpp0x/constexpr-arith-overflow.C
    trunk/gcc/testsuite/g++.dg/cpp1y/constexpr-70265-2.C
    trunk/gcc/testsuite/g++.dg/cpp1y/constexpr-84192.C
    trunk/gcc/testsuite/g++.dg/ubsan/vptr-4.C
Comment 16 Jakub Jelinek 2019-02-22 00:07:09 UTC
The above commit just brings 9.x into the similar state as 8.x on this testcase, thus it is ice-on-valid.
Comment 17 Jakub Jelinek 2019-02-22 15:20:24 UTC
GCC 8.3 has been released.
Comment 18 Jakub Jelinek 2019-02-25 15:01:34 UTC
Author: jakub
Date: Mon Feb 25 15:01:01 2019
New Revision: 269188

URL: https://gcc.gnu.org/viewcvs?rev=269188&root=gcc&view=rev
Log:
	PR c++/89285
	* g++.dg/cpp1y/constexpr-89285-2.C: New test.

Added:
    trunk/gcc/testsuite/g++.dg/cpp1y/constexpr-89285-2.C
Modified:
    trunk/gcc/testsuite/ChangeLog
Comment 19 Jakub Jelinek 2019-02-25 15:06:57 UTC
Actually, #c16 comment is incorrect, that was the case of the earlier approach (#c4) but not of what has been actually committed.
Keeping this open for 8.x, though the patch is unlikely to be backportable.
Comment 20 Jakub Jelinek 2020-03-04 09:40:55 UTC
GCC 8.4.0 has been released, adjusting target milestone.
Comment 21 Jakub Jelinek 2021-05-14 11:34:41 UTC
The GCC 8 branch is being closed, fixed in GCC 9.1.