ESBMC
Efficient SMT-based Context-Bounded Model Checker
Loading...
Searching...
No Matches
annotation_symbolic.inl
Go to the documentation of this file.
1// Out-of-class member definitions for `python_annotation<Json>` that
2// implement class-inheritance reasoning and per-class function
3// specialisation:
4//
5// - are_all_user_classes
6// - are_specializable_types
7// - is_specialization_pending
8// - build_specialized_function_name
9// - resolve_name_assigned_class
10// - rewrite_specialized_calls
11// - retype_builtin_copy
12// - apply_pending_specializations
13// - get_class_ancestors
14// - find_common_ancestor
15//
16// This file is `#include`d from python_annotation.h after the class
17// definition. Bodies are verbatim from the previous in-class
18// definitions; only the `template <class Json>` line and the
19// `python_annotation<Json>::` qualifier are new. No semantic change.
20
21#pragma once
22
23template <class Json>
24bool python_annotation<Json>::are_all_user_classes(
25 const std::vector<std::string> &types) const
26{
27 if (types.empty())
28 return false;
29
30 for (const auto &type_name : types)
31 {
32 if (type_name.empty() || !json_utils::is_class(type_name, ast_))
33 return false;
34 }
35
36 return true;
37}
38
39// Whether call sites passing `types` get a copy of the function per type.
40// Besides user classes, a parameter given an int at one call and a float at
41// another gets one: widening it to float gives the int call a float result,
42// which then fails isinstance(..., int) (#8627).
43template <class Json>
44bool python_annotation<Json>::are_specializable_types(
45 const std::vector<std::string> &types) const
46{
47 if (are_all_user_classes(types))
48 return true;
49 bool has_int = false, has_float = false;
50 for (const auto &t : types)
51 {
52 if (t != "int" && t != "float" && t != "str" && t != "bool")
53 return false;
54 has_int |= t == "int";
55 has_float |= t == "float";
56 }
57 return has_int && has_float;
58}
59
60// Whether `func_name` already has a specialization pending that covers
61// parameter `param_index`. Copies made for builtin types annotate every
62// parameter from their own calls, so one such specialization per function
63// suffices.
64template <class Json>
65bool python_annotation<Json>::is_specialization_pending(
66 const std::string &func_name,
67 size_t param_index,
68 const std::vector<std::string> &types) const
69{
70 for (const auto &spec : pending_specializations_)
71 if (
72 spec.function_name == func_name &&
73 (spec.param_index == param_index || !are_all_user_classes(types)))
74 return true;
75 return false;
76}
77
78template <class Json>
79std::string python_annotation<Json>::build_specialized_function_name(
80 const std::string &base_name,
81 const std::string &class_name) const
82{
83 return base_name + "__esbmc_poly_" + class_name;
84}
85
86template <class Json>
87std::string python_annotation<Json>::resolve_name_assigned_class(
88 const std::string &name,
89 const Json &node)
90{
91 if (name.empty())
92 return "";
93
94 if (node.is_object())
95 {
96 if (
97 node.contains("_type") && node["_type"] == "Assign" &&
98 node.contains("targets") && node["targets"].is_array() &&
99 !node["targets"].empty() && node["targets"][0].is_object() &&
100 node["targets"][0].contains("_type") &&
101 node["targets"][0]["_type"] == "Name" &&
102 node["targets"][0].contains("id") && node["targets"][0]["id"] == name &&
103 node.contains("value") && node["value"].is_object() &&
104 node["value"].contains("_type") && node["value"]["_type"] == "Call" &&
105 node["value"].contains("func") && node["value"]["func"].is_object() &&
106 node["value"]["func"].contains("_type") &&
107 node["value"]["func"]["_type"] == "Name" &&
108 node["value"]["func"].contains("id"))
109 {
110 std::string class_name =
111 node["value"]["func"]["id"].template get<std::string>();
112 if (json_utils::is_class(class_name, ast_))
113 return class_name;
114 }
115
116 for (auto it = node.begin(); it != node.end(); ++it)
117 {
118 std::string found = resolve_name_assigned_class(name, it.value());
119 if (!found.empty())
120 return found;
121 }
122 }
123 else if (node.is_array())
124 {
125 for (const auto &element : node)
126 {
127 std::string found = resolve_name_assigned_class(name, element);
128 if (!found.empty())
129 return found;
130 }
131 }
132
133 return "";
134}
135
136template <class Json>
137void python_annotation<Json>::rewrite_specialized_calls(
138 const std::string &original_name,
139 size_t param_index,
140 const std::unordered_map<std::string, std::string> &specialized_names,
141 Json &node)
142{
143 if (node.is_object())
144 {
145 if (
146 node.contains("_type") && node["_type"] == "Call" &&
147 node.contains("func") && node["func"].is_object() &&
148 node["func"].contains("_type") && node["func"]["_type"] == "Name" &&
149 node["func"].contains("id") && node["func"]["id"] == original_name &&
150 node.contains("args") && node["args"].is_array() &&
151 param_index < node["args"].size())
152 {
153 std::string arg_type = get_argument_type(node["args"][param_index]);
154 if (
155 arg_type.empty() && node["args"][param_index].contains("_type") &&
156 node["args"][param_index]["_type"] == "Name" &&
157 node["args"][param_index].contains("id"))
158 {
159 const std::string arg_name =
160 node["args"][param_index]["id"].template get<std::string>();
161 arg_type = resolve_name_assigned_class(arg_name, ast_["body"]);
162 }
163
164 auto it = specialized_names.find(arg_type);
165 if (it != specialized_names.end())
166 node["func"]["id"] = it->second;
167 }
168
169 for (auto it = node.begin(); it != node.end(); ++it)
170 rewrite_specialized_calls(
171 original_name, param_index, specialized_names, it.value());
172 }
173 else if (node.is_array())
174 {
175 for (auto &element : node)
176 rewrite_specialized_calls(
177 original_name, param_index, specialized_names, element);
178 }
179}
180
181// Retype `fn`, specialized for the builtin `type` of one parameter, from the
182// calls that pass a `type` there (#8627). Each other parameter left
183// unannotated, because its calls disagree on its type, takes the type those
184// calls agree on. The return type inferred for the original came from all
185// calls, so it is dropped and the converter infers it from the copy's body.
186template <class Json>
187void python_annotation<Json>::retype_builtin_copy(
188 Json &fn,
189 const pending_specializationt &spec,
190 const std::string &type,
191 const std::vector<Json> &calls)
192{
193 if (are_all_user_classes(spec.class_types))
194 return;
195 const size_t param_index = spec.param_index;
196 std::vector<Json> reaching;
197 for (const Json &call : calls)
198 if (
199 param_index < call["args"].size() &&
200 get_argument_type(call["args"][param_index]) == type)
201 reaching.push_back(call);
202
203 Json &params = fn["args"]["args"];
204 for (size_t j = 0; j < params.size(); ++j)
205 {
206 if (
207 j == param_index ||
208 (params[j].contains("annotation") && !params[j]["annotation"].is_null()))
209 continue;
210 const std::vector<std::string> types =
211 collect_parameter_types_from_calls(j, reaching);
212 if (types.size() > 1)
213 throw std::runtime_error(
214 "unsupported: function '" + fn["name"].template get<std::string>() +
215 "' is called with differing types in more than one parameter");
216 if (types.size() == 1)
217 add_parameter_annotation(params[j], types.front());
218 }
219 if (
220 fn["returns"].is_object() &&
221 fn["returns"].value("_inferred_annotation", false))
222 fn["returns"] = nullptr;
223}
224
225template <class Json>
226void python_annotation<Json>::apply_pending_specializations()
227{
228 for (const auto &spec : pending_specializations_)
229 {
230 const std::string &func_name = spec.function_name;
231 size_t param_index = spec.param_index;
232
233 Json original_function;
234 bool found = false;
235 size_t original_index = 0;
236 size_t idx = 0;
237 for (const auto &node : ast_["body"])
238 {
239 if (
240 node.contains("_type") && node["_type"] == "FunctionDef" &&
241 node.contains("name") && node["name"] == func_name)
242 {
243 original_function = node;
244 found = true;
245 original_index = idx;
246 break;
247 }
248 ++idx;
249 }
250
251 if (!found)
252 continue;
253
254 const std::vector<Json> calls =
255 find_function_calls(func_name, ast_["body"]);
256 if (!spec.class_types.empty())
257 {
258 for (auto &node : ast_["body"])
259 {
260 if (
261 node.contains("_type") && node["_type"] == "FunctionDef" &&
262 node.contains("name") && node["name"] == func_name &&
263 node.contains("args") && node["args"].is_object() &&
264 node["args"].contains("args") && node["args"]["args"].is_array() &&
265 param_index < node["args"]["args"].size())
266 {
267 Json &param = node["args"]["args"][param_index];
268 add_parameter_annotation(param, spec.class_types.front());
269 retype_builtin_copy(node, spec, spec.class_types.front(), calls);
270 break;
271 }
272 }
273 }
274
275 std::unordered_map<std::string, std::string> specialized_names;
276 Json specialized_nodes = Json::array();
277 for (const auto &class_name : spec.class_types)
278 {
279 Json specialized = original_function;
280 const std::string specialized_name =
281 build_specialized_function_name(func_name, class_name);
282 specialized["name"] = specialized_name;
283
284 if (
285 specialized.contains("args") && specialized["args"].is_object() &&
286 specialized["args"].contains("args") &&
287 specialized["args"]["args"].is_array() &&
288 param_index < specialized["args"]["args"].size())
289 {
290 Json &param = specialized["args"]["args"][param_index];
291 add_parameter_annotation(param, class_name);
292 retype_builtin_copy(specialized, spec, class_name, calls);
293 }
294
295 specialized_names[class_name] = specialized_name;
296 specialized_nodes.push_back(std::move(specialized));
297 }
298
299 auto insert_it =
300 ast_["body"].begin() + static_cast<ptrdiff_t>(original_index + 1);
301 ast_["body"].insert(
302 insert_it, specialized_nodes.begin(), specialized_nodes.end());
303
304 rewrite_specialized_calls(
305 func_name, param_index, specialized_names, ast_);
306 }
307
308 pending_specializations_.clear();
309}
310
311// Returns the ancestors of a class (including itself) in BFS order via
312// "bases". Handles multiple inheritance and cycle-safe (visited guard).
313// "object" is intentionally excluded — it has no ESBMC IR representation.
314template <class Json>
315std::vector<std::string>
316python_annotation<Json>::get_class_ancestors(const std::string &class_name)
317{
318 std::vector<std::string> ancestors;
319 std::unordered_set<std::string> visited;
320 std::vector<std::string> worklist = {class_name};
321 size_t idx = 0;
322 while (idx < worklist.size())
323 {
324 std::string cur = worklist[idx++];
325 if (cur.empty() || cur == "object" || visited.count(cur))
326 continue;
327 visited.insert(cur);
328 ancestors.push_back(cur);
329 Json cls = json_utils::find_class(ast_["body"], cur);
330 if (cls.empty() || !cls.contains("bases"))
331 continue;
332 for (const auto &base : cls["bases"])
333 if (base.contains("id"))
334 worklist.push_back(base["id"].template get<std::string>());
335 }
336 return ancestors;
337}
338
339// Returns the lowest common ancestor of two user-defined class types, or "".
340template <class Json>
341std::string python_annotation<Json>::find_common_ancestor(
342 const std::string &type_a,
343 const std::string &type_b)
344{
345 std::vector<std::string> ancestors_a = get_class_ancestors(type_a);
346 std::unordered_set<std::string> ancestors_b_set;
347 for (const auto &anc : get_class_ancestors(type_b))
348 ancestors_b_set.insert(anc);
349 for (const auto &anc : ancestors_a)
350 if (ancestors_b_set.count(anc))
351 return anc;
352 return "";
353}
static const twodig_t base
Definition bigint.cpp:28
JsonType find_class(const JsonType &ast_json, const std::string &class_name)
Definition json_utils.h:159
bool is_class(const std::string &name, const JsonType &ast_json)
Definition json_utils.h:441
name
Definition transform.py:21
void * value
Definition solidity_mapping.c:2
long int ptrdiff_t
Definition stddef.h:30