summaryrefslogtreecommitdiff
path: root/gcc/cp/cp-ubsan.c
blob: 0aaf0af23cdbb600d8e066d5458d121f37b8a09c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/* UndefinedBehaviorSanitizer, undefined behavior detector.
   Copyright (C) 2014 Free Software Foundation, Inc.
   Contributed by Jakub Jelinek <jakub@redhat.com>

This file is part of GCC.

GCC is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free
Software Foundation; either version 3, or (at your option) any later
version.

GCC is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
for more details.

You should have received a copy of the GNU General Public License
along with GCC; see the file COPYING3.  If not see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include "system.h"
#include "coretypes.h"
#include "cp-tree.h"
#include "ubsan.h"

/* Test if we should instrument vptr access.  */

static bool
cp_ubsan_instrument_vptr_p (tree type)
{
  if (!flag_rtti || flag_sanitize_undefined_trap_on_error)
    return false;

  if (current_function_decl
      && lookup_attribute ("no_sanitize_undefined",
			   DECL_ATTRIBUTES (current_function_decl)))
    return false;

  if (type)
    {
      type = TYPE_MAIN_VARIANT (type);
      if (!CLASS_TYPE_P (type) || !CLASSTYPE_VTABLES (type))
	return false;
    }

  return true;
}

/* Helper function for
   cp_ubsan_maybe_instrument_{member_{call,access},downcast}.
   Instrument vptr access.  */

static tree
cp_ubsan_instrument_vptr (location_t loc, tree op, tree type, bool is_addr,
			  enum ubsan_null_ckind ckind)
{
  type = TYPE_MAIN_VARIANT (type);
  const char *mangled = mangle_type_string (type);
  hashval_t str_hash1 = htab_hash_string (mangled);
  hashval_t str_hash2 = iterative_hash (mangled, strlen (mangled), 0);
  tree str_hash = wide_int_to_tree (uint64_type_node,
				    wi::uhwi (((uint64_t) str_hash1 << 32)
					      | str_hash2, 64));
  if (!is_addr)
    op = build_fold_addr_expr_loc (loc, op);
  op = save_expr (op);
  tree vptr = fold_build3_loc (loc, COMPONENT_REF,
			       TREE_TYPE (TYPE_VFIELD (type)),
			       build_fold_indirect_ref_loc (loc, op),
			       TYPE_VFIELD (type), NULL_TREE);
  vptr = fold_convert_loc (loc, pointer_sized_int_node, vptr);
  vptr = fold_convert_loc (loc, uint64_type_node, vptr);
  if (ckind == UBSAN_DOWNCAST_POINTER)
    vptr = fold_build3 (COND_EXPR, uint64_type_node,
			fold_build2 (NE_EXPR, boolean_type_node, op,
				     build_zero_cst (TREE_TYPE (op))),
			vptr, build_int_cst (uint64_type_node, 0));
  tree ti_decl = get_tinfo_decl (type);
  mark_used (ti_decl);
  tree ptype = build_pointer_type (type);
  tree call
    = build_call_expr_internal_loc (loc, IFN_UBSAN_VPTR,
				    void_type_node, 5, op, vptr, str_hash,
				    build_address (ti_decl),
				    build_int_cst (ptype, ckind));
  TREE_SIDE_EFFECTS (call) = 1;
  return fold_build2 (COMPOUND_EXPR, TREE_TYPE (op), call, op);
}

/* Helper function for
   cp_ubsan_maybe_instrument_{member_{call,access},downcast}.
   Instrument vptr access if it should be instrumented, otherwise return
   NULL_TREE.  */

static tree
cp_ubsan_maybe_instrument_vptr (location_t loc, tree op, tree type,
				bool is_addr, enum ubsan_null_ckind ckind)
{
  if (!cp_ubsan_instrument_vptr_p (type))
    return NULL_TREE;
  return cp_ubsan_instrument_vptr (loc, op, type, is_addr, ckind);
}

/* Instrument a member call (but not constructor call) if needed.  */

void
cp_ubsan_maybe_instrument_member_call (tree stmt)
{
  if (call_expr_nargs (stmt) == 0)
    return;
  tree *opp = &CALL_EXPR_ARG (stmt, 0);
  tree op = *opp;
  if (op == error_mark_node
      || !POINTER_TYPE_P (TREE_TYPE (op)))
    return;
  while (TREE_CODE (op) == COMPOUND_EXPR)
    {
      opp = &TREE_OPERAND (op, 1);
      op = *opp;
    }
  op = cp_ubsan_maybe_instrument_vptr (EXPR_LOCATION (stmt), op,
				       TREE_TYPE (TREE_TYPE (op)),
				       true, UBSAN_MEMBER_CALL);
  if (op)
    *opp = op;
}

/* Data passed to cp_ubsan_check_member_access_r.  */

struct cp_ubsan_check_member_access_data
{
  hash_set<tree> *pset;
  bool is_addr;
};

static tree cp_ubsan_check_member_access_r (tree *, int *, void *);

/* Instrument a member access.  */

static bool
cp_ubsan_maybe_instrument_member_access
     (tree stmt, cp_ubsan_check_member_access_data *ucmd)
{
  if (DECL_ARTIFICIAL (TREE_OPERAND (stmt, 1)))
    return false;

  tree base = TREE_OPERAND (stmt, 0);
  if (!cp_ubsan_instrument_vptr_p (TREE_TYPE (base)))
    return false;

  cp_walk_tree (&base, cp_ubsan_check_member_access_r, ucmd, ucmd->pset);

  base = cp_ubsan_instrument_vptr (EXPR_LOCATION (stmt), base,
				   TREE_TYPE (base), false,
				   UBSAN_MEMBER_ACCESS);
  TREE_OPERAND (stmt, 0)
    = build_fold_indirect_ref_loc (EXPR_LOCATION (stmt), base);
  return true;
}

/* Attempt to instrument member accesses inside of the function.
   cp_ubsan_maybe_instrument_member_access should be called on COMPONENT_REFs
   in the GENERIC IL, but only when the field is actually accessed, not
   merely when it's address is taken.  Therefore we track in is_addr field
   whether in the current context we are processing address taken
   handled components or not.  E.g. for &x->y[w->z] we want to call
   cp_ubsan_maybe_instrument_member_access on *w.z COMPONENT_REF, but
   not on *x.y.  */

static tree
cp_ubsan_check_member_access_r (tree *stmt_p, int *walk_subtrees, void *data)
{
  tree stmt = *stmt_p, t;
  cp_ubsan_check_member_access_data *ucmd
    = (cp_ubsan_check_member_access_data *) data;
  switch (TREE_CODE (stmt))
    {
    case ADDR_EXPR:
      t = TREE_OPERAND (stmt, 0);
      while ((TREE_CODE (t) == MEM_REF || INDIRECT_REF_P (t))
	     && TREE_CODE (TREE_OPERAND (t, 0)) == ADDR_EXPR)
	t = TREE_OPERAND (TREE_OPERAND (t, 0), 0);
      if (handled_component_p (t))
	{
	  *walk_subtrees = 0;
	  ucmd->is_addr = true;
	  cp_walk_tree (&t, cp_ubsan_check_member_access_r,
			data, ucmd->pset);
	  ucmd->is_addr = false;
	}
      break;
    case MEM_REF:
    case INDIRECT_REF:
      t = TREE_OPERAND (stmt, 0);
      if (TREE_CODE (t) == ADDR_EXPR)
	{
	  *walk_subtrees = 0;
	  t = TREE_OPERAND (stmt, 0);
	  cp_walk_tree (&t, cp_ubsan_check_member_access_r, data, ucmd->pset);
	}
      break;
    case COMPONENT_REF:
      if (!ucmd->is_addr && cp_ubsan_maybe_instrument_member_access (stmt, ucmd))
	{
	  *walk_subtrees = 0;
	  break;
	}
      /* FALLTHRU */
    default:
      if (ucmd->is_addr && handled_component_p (stmt))
	{
	  int i, len = TREE_OPERAND_LENGTH (stmt);
	  *walk_subtrees = 0;
	  if (!handled_component_p (TREE_OPERAND (stmt, 0)))
	    ucmd->is_addr = false;
	  for (i = 0; i < len; i++)
	    {
	      cp_walk_tree (&TREE_OPERAND (stmt, i),
			    cp_ubsan_check_member_access_r, data, ucmd->pset);
	      ucmd->is_addr = false;
	    }
	  ucmd->is_addr = true;
	}
      break;
    }
  return NULL_TREE;
}

/* Instrument all member accesses inside GENERIC *T_P.  */

void
cp_ubsan_instrument_member_accesses (tree *t_p)
{
  if (cp_ubsan_instrument_vptr_p (NULL_TREE))
    {
      hash_set<tree> pset;
      cp_ubsan_check_member_access_data ucmd;
      ucmd.pset = &pset;
      ucmd.is_addr = false;
      cp_walk_tree (t_p, cp_ubsan_check_member_access_r, &ucmd, &pset);
    }
}

/* Instrument downcast.  */

tree
cp_ubsan_maybe_instrument_downcast (location_t loc, tree type, tree op)
{
  if (!POINTER_TYPE_P (type)
      || !POINTER_TYPE_P (TREE_TYPE (op))
      || !CLASS_TYPE_P (TREE_TYPE (type))
      || !CLASS_TYPE_P (TREE_TYPE (TREE_TYPE (op)))
      || !DERIVED_FROM_P (TREE_TYPE (TREE_TYPE (op)), TREE_TYPE (type)))
    return NULL_TREE;

  return cp_ubsan_maybe_instrument_vptr (loc, op, TREE_TYPE (type), true,
					 TREE_CODE (type) == POINTER_TYPE
					 ? UBSAN_DOWNCAST_POINTER
					 : UBSAN_DOWNCAST_REFERENCE);
}

/* Instrument cast to virtual base.  */

tree
cp_ubsan_maybe_instrument_cast_to_vbase (location_t loc, tree type, tree op)
{
  return cp_ubsan_maybe_instrument_vptr (loc, op, type, true,
					 UBSAN_CAST_TO_VBASE);
}