Daily bump.
[official-gcc.git] / gcc / analyzer / region-model-asm.cc
blob2a074b647755fda7554762be1032e7e640560bfb
1 /* Handling inline asm in the analyzer.
2 Copyright (C) 2021-2024 Free Software Foundation, Inc.
3 Contributed by David Malcolm <dmalcolm@redhat.com>.
5 This file is part of GCC.
7 GCC is free software; you can redistribute it and/or modify it
8 under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3, or (at your option)
10 any later version.
12 GCC is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING3. If not see
19 <http://www.gnu.org/licenses/>. */
21 #include "config.h"
22 #define INCLUDE_MEMORY
23 #define INCLUDE_VECTOR
24 #include "system.h"
25 #include "coretypes.h"
26 #include "tree.h"
27 #include "function.h"
28 #include "basic-block.h"
29 #include "gimple.h"
30 #include "gimple-iterator.h"
31 #include "diagnostic-core.h"
32 #include "pretty-print.h"
33 #include "analyzer/analyzer.h"
34 #include "analyzer/analyzer-logging.h"
35 #include "options.h"
36 #include "analyzer/call-string.h"
37 #include "analyzer/program-point.h"
38 #include "analyzer/store.h"
39 #include "analyzer/region-model.h"
40 #include "analyzer/region-model-reachability.h"
41 #include "stmt.h"
43 #if ENABLE_ANALYZER
45 namespace ana {
47 /* Minimal asm support for the analyzer.
49 The objective of this code is to:
50 - minimize false positives from the analyzer on the Linux kernel
51 (which makes heavy use of inline asm), whilst
52 - avoiding having to "teach" the compiler anything about specific strings
53 in asm statements.
55 Specifically, we want to:
57 (a) mark asm outputs and certain other regions as having been written to,
58 to avoid false postives from -Wanalyzer-use-of-uninitialized-value.
60 (b) identify some of these stmts as "deterministic" so that we can
61 write consistent outputs given consistent inputs, so that we can
62 avoid false positives for paths in which an asm is invoked twice
63 with the same inputs and is expected to emit the same output.
65 This file implements heuristics for achieving the above. */
67 /* Determine if ASM_STMT is deterministic, in the sense of (b) above.
69 Consider this x86 function taken from the Linux kernel
70 (arch/x86/include/asm/barrier.h):
72 static inline unsigned long array_index_mask_nospec(unsigned long index,
73 unsigned long size)
75 unsigned long mask;
77 asm volatile ("cmp %1,%2; sbb %0,%0;"
78 :"=r" (mask)
79 :"g"(size),"r" (index)
80 :"cc");
81 return mask;
84 The above is a mitigation for Spectre-variant-1 attacks, for clamping
85 an array access to within the range of [0, size] if the CPU speculates
86 past the array bounds.
88 However, it is ultimately used to implement wdev_to_wvif:
90 static inline struct wfx_vif *
91 wdev_to_wvif(struct wfx_dev *wdev, int vif_id)
93 vif_id = array_index_nospec(vif_id, ARRAY_SIZE(wdev->vif));
94 if (!wdev->vif[vif_id]) {
95 return NULL;
97 return (struct wfx_vif *)wdev->vif[vif_id]->drv_priv;
100 which is used by:
102 if (wdev_to_wvif(wvif->wdev, 1))
103 return wdev_to_wvif(wvif->wdev, 1)->vif;
105 The code has been written to assume that wdev_to_wvif is deterministic,
106 and won't change from returning non-NULL at the "if" clause to
107 returning NULL at the "->vif" dereference.
109 By treating the above specific "asm volatile" as deterministic we avoid
110 a false positive from -Wanalyzer-null-dereference. */
112 static bool
113 deterministic_p (const gasm *asm_stmt)
115 /* Assume something volatile with no inputs is querying
116 changeable state e.g. rdtsc. */
117 if (gimple_asm_ninputs (asm_stmt) == 0
118 && gimple_asm_volatile_p (asm_stmt))
119 return false;
121 /* Otherwise assume it's purely a function of its inputs. */
122 return true;
125 /* Update this model for the asm STMT, using CTXT to report any
126 diagnostics.
128 Compare with cfgexpand.cc: expand_asm_stmt. */
130 void
131 region_model::on_asm_stmt (const gasm *stmt, region_model_context *ctxt)
133 logger *logger = ctxt ? ctxt->get_logger () : NULL;
134 LOG_SCOPE (logger);
136 const unsigned noutputs = gimple_asm_noutputs (stmt);
137 const unsigned ninputs = gimple_asm_ninputs (stmt);
139 auto_vec<tree> output_tvec;
140 auto_vec<tree> input_tvec;
141 auto_vec<const char *> constraints;
143 /* Copy the gimple vectors into new vectors that we can manipulate. */
144 output_tvec.safe_grow (noutputs, true);
145 input_tvec.safe_grow (ninputs, true);
146 constraints.safe_grow (noutputs + ninputs, true);
148 for (unsigned i = 0; i < noutputs; ++i)
150 tree t = gimple_asm_output_op (stmt, i);
151 output_tvec[i] = TREE_VALUE (t);
152 constraints[i] = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
154 for (unsigned i = 0; i < ninputs; i++)
156 tree t = gimple_asm_input_op (stmt, i);
157 input_tvec[i] = TREE_VALUE (t);
158 constraints[i + noutputs]
159 = TREE_STRING_POINTER (TREE_VALUE (TREE_PURPOSE (t)));
162 /* Determine which regions are reachable from the inputs
163 to this stmt. */
164 reachable_regions reachable_regs (this);
166 int num_errors = 0;
168 auto_vec<const region *> output_regions (noutputs);
169 for (unsigned i = 0; i < noutputs; ++i)
171 tree val = output_tvec[i];
172 const char *constraint;
173 bool is_inout;
174 bool allows_reg;
175 bool allows_mem;
177 const region *dst_reg = get_lvalue (val, ctxt);
178 output_regions.quick_push (dst_reg);
179 reachable_regs.add (dst_reg, true);
181 /* Try to parse the output constraint. If that fails, there's
182 no point in going further. */
183 constraint = constraints[i];
184 if (!parse_output_constraint (&constraint, i, ninputs, noutputs,
185 &allows_mem, &allows_reg, &is_inout))
187 if (logger)
188 logger->log ("error parsing constraint for output %i: %qs",
189 i, constraint);
190 num_errors++;
191 continue;
194 if (logger)
196 logger->log ("output %i: %qs %qE"
197 " is_inout: %i allows_reg: %i allows_mem: %i",
198 i, constraint, val,
199 (int)is_inout, (int)allows_reg, (int)allows_mem);
200 logger->start_log_line ();
201 logger->log_partial (" region: ");
202 dst_reg->dump_to_pp (logger->get_printer (), true);
203 logger->end_log_line ();
208 /* Ideally should combine with inout_svals to determine the
209 "effective inputs" and use this for the asm_output_svalue. */
211 auto_vec<const svalue *> input_svals (ninputs);
212 for (unsigned i = 0; i < ninputs; i++)
214 tree val = input_tvec[i];
215 const char *constraint = constraints[i + noutputs];
216 bool allows_reg, allows_mem;
217 if (! parse_input_constraint (&constraint, i, ninputs, noutputs, 0,
218 constraints.address (),
219 &allows_mem, &allows_reg))
221 if (logger)
222 logger->log ("error parsing constraint for input %i: %qs",
223 i, constraint);
224 num_errors++;
225 continue;
228 tree src_expr = input_tvec[i];
229 const svalue *src_sval = get_rvalue (src_expr, ctxt);
230 check_for_poison (src_sval, src_expr, NULL, ctxt);
231 input_svals.quick_push (src_sval);
232 reachable_regs.handle_sval (src_sval);
234 if (logger)
236 logger->log ("input %i: %qs %qE"
237 " allows_reg: %i allows_mem: %i",
238 i, constraint, val,
239 (int)allows_reg, (int)allows_mem);
240 logger->start_log_line ();
241 logger->log_partial (" sval: ");
242 src_sval->dump_to_pp (logger->get_printer (), true);
243 logger->end_log_line ();
247 if (num_errors > 0)
248 gcc_unreachable ();
250 if (logger)
252 logger->log ("reachability: ");
253 reachable_regs.dump_to_pp (logger->get_printer ());
254 logger->end_log_line ();
257 /* Given the regions that were reachable from the inputs we
258 want to clobber them.
259 This is similar to region_model::handle_unrecognized_call,
260 but the unknown call policies seems too aggressive (e.g. purging state
261 from anything that's ever escaped). Instead, clobber any clusters
262 that were reachable in *this* asm stmt, rather than those that
263 escaped, and we don't treat the values as having escaped.
264 We also assume that asm stmts don't affect sm-state. */
265 for (auto iter = reachable_regs.begin_mutable_base_regs ();
266 iter != reachable_regs.end_mutable_base_regs (); ++iter)
268 const region *base_reg = *iter;
269 if (base_reg->symbolic_for_unknown_ptr_p ()
270 || !base_reg->tracked_p ())
271 continue;
273 binding_cluster *cluster = m_store.get_or_create_cluster (base_reg);
274 cluster->on_asm (stmt, m_mgr->get_store_manager (),
275 conjured_purge (this, ctxt));
278 /* Update the outputs. */
279 for (unsigned output_idx = 0; output_idx < noutputs; output_idx++)
281 tree dst_expr = output_tvec[output_idx];
282 const region *dst_reg = output_regions[output_idx];
284 const svalue *sval;
285 if (deterministic_p (stmt)
286 && input_svals.length () <= asm_output_svalue::MAX_INPUTS)
287 sval = m_mgr->get_or_create_asm_output_svalue (TREE_TYPE (dst_expr),
288 stmt,
289 output_idx,
290 input_svals);
291 else
293 sval = m_mgr->get_or_create_conjured_svalue (TREE_TYPE (dst_expr),
294 stmt,
295 dst_reg,
296 conjured_purge (this,
297 ctxt));
299 set_value (dst_reg, sval, ctxt);
303 } // namespace ana
305 #endif /* #if ENABLE_ANALYZER */