ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
literal_divergence.h
Go to the documentation of this file.
1#pragma once
2
3#include <nlohmann/json.hpp>
4#include <string>
5#include <unordered_map>
6#include <unordered_set>
7#include <utility>
8#include <vector>
9
10// Pure-AST literal-kind divergence detection, shared by dynamic_type_handler
11// and the annotation pre-pass (which has no symbol table to check against).
13{
14// The name a call's `func` spells, `m.f` for an attribute of a name, and
15// its last component; both "" for any other shape.
16inline std::pair<std::string, std::string>
17callee_names(const nlohmann::json &func)
18{
19 if (
20 func.value("_type", "") != "Attribute" || !func.contains("value") ||
21 func["value"].value("_type", "") != "Name")
22 return {func.value("id", ""), func.value("id", "")};
23 const std::string attr = func.value("attr", "");
24 return {func["value"].value("id", "") + "." + attr, attr};
25}
26
27// The kind of a scalar type named `type`, "" for any other name.
28inline std::string scalar_name_kind(const std::string &type)
29{
30 if (type == "str")
31 return "str";
32 if (type == "int" || type == "bool")
33 return "num";
34 return type == "float" ? "float" : "";
35}
36
37// The kind a nondet_<type> function named `leaf` returns, "" for any other.
38inline std::string nondet_leaf_kind(const std::string &leaf)
39{
40 return leaf.rfind("nondet_", 0) == 0 ? scalar_name_kind(leaf.substr(7)) : "";
41}
42
43// The kind of a literal constant's value.
44inline std::string constant_kind(const nlohmann::json &lit)
45{
46 if (lit.is_string())
47 return "str";
48 if (lit.is_number_integer() || lit.is_boolean())
49 return "num";
50 return lit.is_number_float() ? "float" : "";
51}
52
53// Classifies a literal constant, a nondet_<type> call, plain or qualified
54// by a module as in `_sv_verifier.nondet_str()`, or a call to a function
55// with a scalar return annotation (e.g. `mk()` where `def mk() -> str`);
56// "" otherwise. `module_body` holds the FunctionDefs a call may name, a
57// qualified call `m.mk()` naming one called `m.mk`. It may be left empty
58// where a caller has no access to the full module (the annotation
59// pre-pass).
60inline std::string classify_literal_value_kind(
61 const nlohmann::json &value,
62 const nlohmann::json &module_body = nlohmann::json())
63{
64 if (value.value("_type", "") == "Constant" && value.contains("value"))
65 {
66 return constant_kind(value["value"]);
67 }
68
69 if (value.value("_type", "") != "Call" || !value.contains("func"))
70 return "";
71
72 const auto [callee, leaf] = callee_names(value["func"]);
73 const std::string nondet_kind = nondet_leaf_kind(leaf);
74 if (!nondet_kind.empty())
75 return nondet_kind;
76
77 if (module_body.is_array())
78 {
79 for (const auto &stmt : module_body)
80 {
81 if (
82 !stmt.is_object() || stmt.value("_type", "") != "FunctionDef" ||
83 stmt.value("name", "") != callee || !stmt.contains("returns") ||
84 stmt["returns"].is_null())
85 continue;
86
87 const std::string ret_kind =
88 scalar_name_kind(stmt["returns"].value("id", ""));
89 if (!ret_kind.empty())
90 return ret_kind;
91 }
92 }
93
94 return "";
95}
96
97// Whether a name joining these literal kinds has a type that depends on the
98// path. An int and a float alone are a number of path dependent type, which
99// is refused where it is read (#8973).
100// Whether any name was found to join a float with another kind, so that a
101// tag may hold a float, which the tagged arithmetic and comparison helpers
102// do not model.
104{
105 static bool possible = false;
106 return possible;
107}
108
109inline bool kinds_diverge(const std::unordered_set<std::string> &kinds)
110{
111 const bool int_float_only =
112 kinds.size() == 2 && kinds.count("num") && kinds.count("float");
113 const bool diverge = kinds.size() >= 2 && !int_float_only;
114 if (diverge && kinds.count("float"))
115 float_tags_possible() = true;
116 return diverge;
117}
118
119// Resolves `x = n` via name's own assignment in scope_body.
120inline std::string classify_name_kind(
121 const std::string &name,
122 const nlohmann::json &scope_body,
123 const nlohmann::json &module_body = nlohmann::json())
124{
125 if (!scope_body.is_array())
126 return "";
127
128 std::string kind;
129 for (const auto &stmt : scope_body)
130 {
131 if (!stmt.is_object())
132 continue;
133
134 const std::string stmt_type = stmt.value("_type", "");
135 nlohmann::json target;
136 if (stmt_type == "Assign")
137 {
138 if (!stmt.contains("targets") || stmt["targets"].size() != 1)
139 continue;
140 target = stmt["targets"][0];
141 }
142 else if (stmt_type == "AnnAssign")
143 {
144 if (!stmt.contains("target"))
145 continue;
146 target = stmt["target"];
147 }
148 else
149 continue;
150
151 if (target.value("_type", "") != "Name" || target.value("id", "") != name)
152 continue;
153
154 if (!stmt.contains("value") || stmt["value"].is_null())
155 kind.clear();
156 else
157 kind = classify_literal_value_kind(stmt["value"], module_body);
158 }
159 return kind;
160}
161
162// `x` for a Name target, `C.v` for an attribute of a name, else "".
163inline std::string assigned_name(const nlohmann::json &target)
164{
165 if (target.value("_type", "") == "Name")
166 return target.value("id", "");
167 if (
168 target.value("_type", "") == "Attribute" && target.contains("value") &&
169 target["value"].value("_type", "") == "Name")
170 return target["value"].value("id", "") + "." + target.value("attr", "");
171 return "";
172}
173
174// Classifies a branch's top-level assignments by literal type; a Name
175// value (e.g. `x = n`) is resolved via scope_body.
176inline std::unordered_map<std::string, std::string>
178 const nlohmann::json &block,
179 const nlohmann::json &scope_body,
180 const nlohmann::json &module_body = nlohmann::json())
181{
182 std::unordered_map<std::string, std::string> types;
183 if (!block.is_array())
184 return types;
185
186 for (const auto &stmt : block)
187 {
188 if (!stmt.is_object())
189 continue;
190
191 const std::string stmt_type = stmt.value("_type", "");
192 nlohmann::json target;
193 if (stmt_type == "Assign")
194 {
195 if (!stmt.contains("targets") || stmt["targets"].size() != 1)
196 continue;
197 target = stmt["targets"][0];
198 }
199 else if (stmt_type == "AnnAssign")
200 {
201 if (!stmt.contains("target"))
202 continue;
203 target = stmt["target"];
204 }
205 else
206 continue;
207
208 const std::string name = assigned_name(target);
209 if (name.empty())
210 continue;
211
212 // A later reassignment invalidates an earlier recorded kind for `name`.
213 if (!stmt.contains("value") || stmt["value"].is_null())
214 {
215 types.erase(name);
216 continue;
217 }
218 const auto &value = stmt["value"];
219 std::string kind = classify_literal_value_kind(value, module_body);
220 if (
221 kind.empty() && value.value("_type", "") == "Name" &&
222 value.contains("id"))
223 kind = classify_name_kind(
224 value["id"].get<std::string>(), scope_body, module_body);
225
226 if (kind.empty())
227 types.erase(name);
228 else
229 types[name] = kind;
230 }
231
232 return types;
233}
234
235// Collects, per name, the literal kinds assigned to it across every leaf
236// reachable from `block`, following elif/nested if-else chains. Returns
237// false if any such chain is dangling (no final else).
239 const nlohmann::json &block,
240 std::unordered_map<std::string, std::unordered_set<std::string>> &kinds,
241 std::unordered_map<std::string, int> &leaf_count,
242 int &leaf_total,
243 const nlohmann::json &scope_body,
244 const nlohmann::json &module_body = nlohmann::json())
245{
246 if (
247 block.is_array() && block.size() == 1 && block[0].is_object() &&
248 block[0].value("_type", "") == "If")
249 {
250 const auto &nested = block[0];
251 if (
252 !nested.contains("body") || !nested.contains("orelse") ||
253 nested["orelse"].empty())
254 return false;
255
257 nested["body"],
258 kinds,
259 leaf_count,
260 leaf_total,
261 scope_body,
262 module_body) &&
264 nested["orelse"],
265 kinds,
266 leaf_count,
267 leaf_total,
268 scope_body,
269 module_body);
270 }
271
272 leaf_total++;
273 for (const auto &[name, kind] :
274 classify_branch_literal_assigns(block, scope_body, module_body))
275 {
276 kinds[name].insert(kind);
277 leaf_count[name]++;
278 }
279 return true;
280}
281
282// Same as above, but starting from an `If` node's body/orelse directly.
284 const nlohmann::json &if_node,
285 std::unordered_map<std::string, std::unordered_set<std::string>> &kinds,
286 std::unordered_map<std::string, int> &leaf_count,
287 int &leaf_total,
288 const nlohmann::json &scope_body,
289 const nlohmann::json &module_body = nlohmann::json())
290{
291 if (!if_node.contains("body"))
292 return false;
293 if (!if_node.contains("orelse") || if_node["orelse"].empty())
294 return false;
295
297 if_node["body"],
298 kinds,
299 leaf_count,
300 leaf_total,
301 scope_body,
302 module_body) &&
304 if_node["orelse"],
305 kinds,
306 leaf_count,
307 leaf_total,
308 scope_body,
309 module_body);
310}
311
312// Whether `stmt` assigns to `name`.
313inline bool assigns_name(const nlohmann::json &stmt, const std::string &name)
314{
315 const std::string kind = stmt.value("_type", "");
316 const nlohmann::json *target = nullptr;
317 if (kind == "AnnAssign")
318 target = &stmt["target"];
319 else if (kind == "Assign" && stmt["targets"].size() == 1)
320 target = &stmt["targets"][0];
321 return target && target->is_object() &&
322 target->value("_type", "") == "Name" &&
323 target->value("id", "") == name;
324}
325
326// Whether `value` is a conditional expression whose arms have incompatible
327// literal kinds.
329 const nlohmann::json &value,
330 const nlohmann::json &module_body)
331{
332 if (
333 !value.is_object() || value.value("_type", "") != "IfExp" ||
334 !value.contains("body") || !value.contains("orelse"))
335 return false;
336 const std::string then_kind =
337 classify_literal_value_kind(value["body"], module_body);
338 const std::string else_kind =
339 classify_literal_value_kind(value["orelse"], module_body);
340 return !then_kind.empty() && !else_kind.empty() &&
341 kinds_diverge({then_kind, else_kind});
342}
343
344// True if some top-level `If` in `scope_body`, or a conditional expression
345// bound to `name`, assigns `name` genuinely incompatible literal kinds across
346// every branch.
348 const std::string &name,
349 const nlohmann::json &scope_body,
350 const nlohmann::json &module_body = nlohmann::json())
351{
352 if (!scope_body.is_array())
353 return false;
354
355 for (const auto &stmt : scope_body)
356 {
357 if (!stmt.is_object())
358 continue;
359 if (
360 assigns_name(stmt, name) && stmt.contains("value") &&
361 ifexp_arms_diverge(stmt["value"], module_body))
362 return true;
363 if (stmt.value("_type", "") != "If")
364 continue;
365
366 std::unordered_map<std::string, std::unordered_set<std::string>> kinds;
367 std::unordered_map<std::string, int> leaf_count;
368 int leaf_total = 0;
370 stmt, kinds, leaf_count, leaf_total, scope_body, module_body))
371 continue;
372
373 auto it = kinds.find(name);
374 if (
375 it != kinds.end() && leaf_count[name] == leaf_total &&
376 kinds_diverge(it->second))
377 return true;
378 }
379 return false;
380}
381
382// A binding of a name to a number: its kind, "int" (bool included) or
383// "float", the value bound, and whether it sits inside a block.
385{
386 std::string kind;
387 nlohmann::json value;
389};
390
391// The kind of number a binding of `name` stores: "int" (bool included),
392// "float", or "" for anything else or another name.
393inline std::string
394numeric_binding_kind(const nlohmann::json &stmt, const std::string &name)
395{
396 if (!assigns_name(stmt, name))
397 return "";
398 const nlohmann::json &annotation = stmt.value("annotation", nlohmann::json());
399 const std::string type =
400 annotation.is_object() ? annotation.value("id", "") : "";
401 if (type == "int" || type == "bool")
402 return "int";
403 return type == "float" ? "float" : "";
404}
405
406// A line field of `node`, or -1 when the preprocessor left it unset.
407inline int line_field(const nlohmann::json &node, const char *key)
408{
409 const auto it = node.find(key);
410 return it != node.end() && it->is_number_integer() ? it->get<int>() : -1;
411}
412
413// Whether `stmt` starts after line `before`, so that, outside a loop that
414// spans `before`, nothing it binds reaches that line.
415inline bool starts_after(const nlohmann::json &stmt, int before)
416{
417 return line_field(stmt, "lineno") >= before;
418}
419
420// Whether `stmt` is a loop whose body runs again after line `before`. A loop
421// the preprocessor built without an end line is taken to span it.
422inline bool loop_spans(const nlohmann::json &stmt, int before)
423{
424 const std::string kind = stmt.value("_type", "");
425 const int end = line_field(stmt, "end_lineno");
426 return (kind == "While" || kind == "For" || kind == "AsyncFor") &&
427 line_field(stmt, "lineno") <= before && (end < 0 || end >= before);
428}
429
430// Records the number `stmt` binds to `name`. A binding on the straight-line
431// spine before line `before` replaces every binding ahead of it on all paths,
432// so those are dropped.
434 const nlohmann::json &stmt,
435 const std::string &name,
436 bool nested,
437 int before,
438 std::vector<numeric_bindingt> &bindings)
439{
440 const int line = line_field(stmt, "lineno");
441 if (!nested && line >= 0 && line < before && assigns_name(stmt, name))
442 bindings.clear();
443 const std::string number = numeric_binding_kind(stmt, name);
444 if (!number.empty() && stmt.contains("value") && !stmt["value"].is_null())
445 bindings.push_back({number, stmt["value"], nested});
446}
447
448// The number bindings of `name` in `body`, outside nested scopes, that are
449// live at line `before`. With `include_later`, those starting after it count
450// too, as they do inside a loop that spans it.
452 const nlohmann::json &body,
453 const std::string &name,
454 bool nested,
455 int before,
456 bool include_later,
457 std::vector<numeric_bindingt> &bindings)
458{
459 for (const auto &stmt : body)
460 {
461 if (!stmt.is_object() || (!include_later && starts_after(stmt, before)))
462 continue;
463 const std::string kind = stmt.value("_type", "");
464 if (kind == "FunctionDef" || kind == "ClassDef")
465 continue;
466 record_numeric_binding(stmt, name, nested, before, bindings);
467 const bool later = include_later || loop_spans(stmt, before);
468 for (const char *key : {"body", "orelse", "finalbody", "handlers", "cases"})
469 if (stmt.contains(key) && stmt[key].is_array())
471 stmt[key], name, true, before, later, bindings);
472 }
473}
474
475// Whether the bindings store an int on one path and a float on another: an
476// int and a float, at least one of them inside a block. A name rebound on
477// the straight-line spine has the same type on every path.
478inline bool
479is_path_dependent_number(const std::vector<numeric_bindingt> &bindings)
480{
481 bool has_int = false, has_float = false, in_block = false;
482 for (const auto &binding : bindings)
483 {
484 has_int |= binding.kind == "int";
485 has_float |= binding.kind == "float";
486 in_block |= binding.in_block;
487 }
488 return has_int && has_float && in_block;
489}
490} // namespace dynamic_type_detail
Definition toml.hpp:1531
Definition literal_divergence.h:13
bool ifexp_arms_diverge(const nlohmann::json &value, const nlohmann::json &module_body)
Definition literal_divergence.h:328
std::string nondet_leaf_kind(const std::string &leaf)
Definition literal_divergence.h:38
bool collect_branch_literal_kinds(const nlohmann::json &block, std::unordered_map< std::string, std::unordered_set< std::string > > &kinds, std::unordered_map< std::string, int > &leaf_count, int &leaf_total, const nlohmann::json &scope_body, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:238
std::string scalar_name_kind(const std::string &type)
Definition literal_divergence.h:28
bool collect_if_node_literal_kinds(const nlohmann::json &if_node, std::unordered_map< std::string, std::unordered_set< std::string > > &kinds, std::unordered_map< std::string, int > &leaf_count, int &leaf_total, const nlohmann::json &scope_body, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:283
std::string constant_kind(const nlohmann::json &lit)
Definition literal_divergence.h:44
bool & float_tags_possible()
Definition literal_divergence.h:103
std::string classify_literal_value_kind(const nlohmann::json &value, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:60
bool kinds_diverge(const std::unordered_set< std::string > &kinds)
Definition literal_divergence.h:109
bool scope_assigns_divergent_literal_types(const std::string &name, const nlohmann::json &scope_body, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:347
void record_numeric_binding(const nlohmann::json &stmt, const std::string &name, bool nested, int before, std::vector< numeric_bindingt > &bindings)
Definition literal_divergence.h:433
std::string classify_name_kind(const std::string &name, const nlohmann::json &scope_body, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:120
bool is_path_dependent_number(const std::vector< numeric_bindingt > &bindings)
Definition literal_divergence.h:479
bool loop_spans(const nlohmann::json &stmt, int before)
Definition literal_divergence.h:422
bool assigns_name(const nlohmann::json &stmt, const std::string &name)
Definition literal_divergence.h:313
std::pair< std::string, std::string > callee_names(const nlohmann::json &func)
Definition literal_divergence.h:17
std::string assigned_name(const nlohmann::json &target)
Definition literal_divergence.h:163
void collect_numeric_bindings(const nlohmann::json &body, const std::string &name, bool nested, int before, bool include_later, std::vector< numeric_bindingt > &bindings)
Definition literal_divergence.h:451
std::string numeric_binding_kind(const nlohmann::json &stmt, const std::string &name)
Definition literal_divergence.h:394
std::unordered_map< std::string, std::string > classify_branch_literal_assigns(const nlohmann::json &block, const nlohmann::json &scope_body, const nlohmann::json &module_body=nlohmann::json())
Definition literal_divergence.h:177
bool starts_after(const nlohmann::json &stmt, int before)
Definition literal_divergence.h:415
int line_field(const nlohmann::json &node, const char *key)
Definition literal_divergence.h:407
@ number
Definition nested_func_transform.cpp:64
void * value
Definition solidity_mapping.c:2
uint256_t key
Definition solidity_mapping.c:1
Definition literal_divergence.h:385
nlohmann::json value
Definition literal_divergence.h:387
std::string kind
Definition literal_divergence.h:386
bool in_block
Definition literal_divergence.h:388