https://gcc.gnu.org/bugzilla/show_bug.cgi?id=112727
--- Comment #3 from Jakub Jelinek <jakub at gcc dot gnu.org> --- Actually no, ubsan_handle_shift is called here with SAVE_EXPR <!(bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[VIEW_CONVERT_EXPR<unsigned int>(j)] ? NON_LVALUE_EXPR <1> : NON_LVALUE_EXPR <0>> as op0. And what goes out of it + the whole LSHIFT_EXPR with instrumentation looks reasonable as well: if (SAVE_EXPR <16 + VIEW_CONVERT_EXPR<unsigned int>(j)>;, SAVE_EXPR <!(bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[VIEW_CONVERT_EXPR<unsigned int>(j)] ? NON_LVALUE_EXPR <1> : NON_LVALUE_EXPR <0>>;, SAVE_EXPR <16 + VIEW_CONVERT_EXPR<unsigned int>(j)> > 31;) { __builtin___ubsan_handle_shift_out_of_bounds (&*.Lubsan_data0, (unsigned long) (SAVE_EXPR <!(bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[VIEW_CONVERT_EXPR<unsigned int>(j)] ? NON_LVALUE_EXPR <1> : NON_LVALUE_EXPR <0>>), (unsigned long) (SAVE_EXPR <16 + VIEW_CONVERT_EXPR<unsigned int>(j)>)); } else { <<< Unknown tree: void_cst >>> }, (SAVE_EXPR <!(bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[VIEW_CONVERT_EXPR<unsigned int>(j)] ? NON_LVALUE_EXPR <1> : NON_LVALUE_EXPR <0>>) << SAVE_EXPR <16 + VIEW_CONVERT_EXPR<unsigned int>(j)>;; i.e. the x->s[j] ? 0 : 1 is evaluated in SAVE_EXPR first, then 16 + j > 31 comparison, etc. Seems the problem happens during cp_fold, which when called on the COMPOUND_EXPR with SAVE_EXPR <!(bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[VIEW_CONVERT_EXPR<unsigned int>(j)] ? NON_LVALUE_EXPR <1> : NON_LVALUE_EXPR <0>> as first argument and SAVE_EXPR <16 + VIEW_CONVERT_EXPR<unsigned int>(j)> > 31 second recurses using 3113 op0 = cp_fold_maybe_rvalue (TREE_OPERAND (x, 0), rval_ops, flags); on the first one and returns (bool) VIEW_CONVERT_EXPR<const struct S *>(x)->s[j] ? 0 : 1 (i.e. extracts it out of the SAVE_EXPR). case SAVE_EXPR: /* A SAVE_EXPR might contain e.g. (0 * i) + (0 * j), which, after folding, evaluates to an invariant. In that case no need to wrap this folded tree with a SAVE_EXPR. */ r = cp_fold (TREE_OPERAND (x, 0), flags); if (tree_invariant_p (r)) x = r; break; And then because it thinks it is invariant (the COND_EXPR has for some reason TREE_READONLY set on it), it also drops it from the COMPOUND_EXPR. Now, guess there is some tree sharing (while c-ubsan.cc calls unshare_expr when it uses the expressions multiple times, the unsharing stops at SAVE_EXPR) and so when the bounds-strict instrumentation modifies it and adds important side-effect to it, it modifies both remaining copies of the SAVE_EXPR's argument.