forked from diffblue/cbmc
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathassignments_from_json.cpp
951 lines (893 loc) · 37.5 KB
/
assignments_from_json.cpp
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
/*******************************************************************\
Module: Assignments to values specified in JSON files
Author: Diffblue Ltd.
\*******************************************************************/
#include "assignments_from_json.h"
#include <util/arith_tools.h>
#include <util/array_element_from_pointer.h>
#include <util/expr_initializer.h>
#include <util/ieee_float.h>
#include <util/json.h>
#include <util/symbol_table_base.h>
#include <util/unicode.h>
#include <goto-programs/allocate_objects.h>
#include <goto-programs/class_identifier.h>
#include <goto-programs/goto_instruction_code.h>
#include "ci_lazy_methods_needed.h"
#include "code_with_references.h"
#include "java_static_initializers.h"
#include "java_string_literals.h"
#include "java_types.h"
#include "java_utils.h"
/// Values passed around between most functions of the recursive deterministic
/// assignment algorithm entered from \ref assign_from_json.
/// The values in a given `object_creation_infot` are never reassigned, but the
/// ones that are not marked `const` may be mutated.
struct object_creation_infot
{
/// Handles allocation of new symbols, adds them to its symbol table (which
/// will usually be the same as the `symbol_table` of this struct) and keeps
/// track of them so declarations for them can be added by the caller before
/// `block`.
allocate_objectst &allocate_objects;
/// Used for looking up symbols corresponding to Java classes and methods.
symbol_table_baset &symbol_table;
/// Where runtime types differ from compile-time types, we need to mark the
/// runtime types as needed by lazy methods.
std::optional<ci_lazy_methods_neededt> &needed_lazy_methods;
/// Map to keep track of reference-equal objects. Each entry has an ID (such
/// that any two reference-equal objects have the same ID) and the expression
/// for the symbol that all these references point to.
std::unordered_map<std::string, object_creation_referencet> &references;
/// Source location associated with the newly added codet.
const source_locationt &loc;
/// Maximum value allowed for any (constant or variable length) arrays in user
/// code.
size_t max_user_array_length;
/// Used for the workaround for enums only.
/// See assign_enum_from_json.
const java_class_typet &declaring_class_type;
};
static java_class_typet
followed_class_type(const exprt &expr, const symbol_table_baset &symbol_table)
{
const pointer_typet &pointer_type = to_pointer_type(expr.type());
const java_class_typet &java_class_type = to_java_class_type(
namespacet{symbol_table}.follow(pointer_type.base_type()));
return java_class_type;
}
static bool
has_enum_type(const exprt &expr, const symbol_table_baset &symbol_table)
{
return followed_class_type(expr, symbol_table).get_is_enumeration();
}
/// This function is used as a workaround until reference-equal objects defined
/// across several classes are tracked correctly. Once reference-equality works
/// in all cases, this function can be removed.
/// Until then, in the case of an enum expression that needs to be assigned a
/// value, we distinguish between two cases:
/// 1) the declaring class of the enum expression is the same as the type of the
/// enum expression. For example, for an enum Pet {DOG, CAT}, the declaring
/// class of the expression Pet.DOG is Pet, and the type of the expression is
/// also Pet. The same is true for the expressions that represent the
/// elements of the $VALUES array of Pet, and for any user-defined Pet-typed
/// fields in Pet.java. In this case, initialize the expression just as a
/// regular object that has known reference-equal objects. (Corresponds to
/// creating the enum constant in Java or referencing it directly.)
/// See assign_reference_from_json.
/// 2) otherwise, initialize it by indexing the $VALUES array with the given
/// ordinal. An example of this case would be the field `pet` in
/// `class MyClass { Pet pet; }` (its declaring class is `MyClass` and its
/// own type is `Pet`).
/// See assign_enum_from_json.
/// \param expr: an expression representing a Java object.
/// \param symbol_table: used for looking up the type of \p expr.
/// \param declaring_class_type: type of the class where \p expr is declared.
/// \return `true` if \p expr has an enum type and is declared within the
/// definition of that same type, `false` otherwise.
static bool is_enum_with_type_equal_to_declaring_type(
const exprt &expr,
const symbol_table_baset &symbol_table,
const java_class_typet &declaring_class_type)
{
PRECONDITION(can_cast_type<pointer_typet>(expr.type()));
return followed_class_type(expr, symbol_table) == declaring_class_type &&
declaring_class_type.get_is_enumeration();
}
/// If the argument has a "@type" key, return the corresponding value, else
/// return an empty optional.
/// A runtime type that is different from the objects compile-time type should
/// be specified in `json` in this way.
/// Type values are of the format "my.package.name.ClassName".
static std::optional<std::string> get_type(const jsont &json)
{
if(!json.is_object())
return {};
const auto &json_object = to_json_object(json);
if(json_object.find("@type") == json_object.end())
return {};
return json_object["@type"].value;
}
/// Return true iff the argument has a "@id" key.
/// The presence of such a key means that there exist objects that are
/// reference-equal to this object.
/// The corresponding value is the unique ID of all objects that are reference-
/// equal to this one.
/// All other key-value pairs of `json` should be as usual.
static bool has_id(const jsont &json)
{
if(!json.is_object())
return false;
const auto &json_object = to_json_object(json);
return json_object.find("@id") != json_object.end();
}
/// Return true iff the argument has a "@ref" key.
/// The corresponding value is the unique ID of all objects that are reference-
/// equal to this one.
/// Any other key-value pairs of `json` will be ignored.
static bool is_reference(const jsont &json)
{
if(!json.is_object())
return false;
const auto &json_object = to_json_object(json);
return json_object.find("@ref") != json_object.end();
}
/// Return the unique ID of all objects that are reference-equal to this one.
/// This is the value corresponding to a "@id" or "@ref" key.
/// See \ref has_id and \ref is_reference.
static std::string get_id_or_reference_value(const jsont &json)
{
PRECONDITION(has_id(json) || is_reference(json));
if(has_id(json))
return json["@id"].value;
return json["@ref"].value;
}
/// Return a unique ID for an enum, based on its type and `name` field.
/// This is needed for the enum workaround until reference-equality across
/// different classes is supported.
/// See \ref is_enum_with_type_equal_to_declaring_type.
static std::string get_enum_id(
const exprt &expr,
const jsont &json,
const symbol_table_baset &symbol_table)
{
PRECONDITION(json.is_object());
const auto &json_object = to_json_object(json);
INVARIANT(
json_object.find("name") != json_object.end(),
"JSON representation of enums should include name field");
return id2string(followed_class_type(expr, symbol_table).get_tag()) + '.' +
(json["name"].value);
}
/// Return true iff the argument has a \c "@nondetLength"<tt>: true</tt> entry.
/// If such an entry is present on a JSON representation of an array, it means
/// that the array should be assigned a nondeterministic length, constrained to
/// be at least the number of elements specified for this array.
static bool has_nondet_length(const jsont &json)
{
if(!json.is_object())
return false;
const auto &json_object = to_json_object(json);
auto nondet_it = json_object.find("@nondetLength");
return nondet_it != json_object.end() && nondet_it->second.is_true();
}
/// For typed versions of primitive, string or array types, looks up their
/// untyped contents with the key specific to their type.
/// See the examples on \ref assign_from_json for the terminology used here
/// (typed vs. untyped).
static jsont get_untyped(const jsont &json, const std::string &object_key)
{
if(!json.is_object())
return json;
const auto &json_object = to_json_object(json);
PRECONDITION(
get_type(json) || json_object.find("@nondetLength") != json_object.end());
return json[object_key];
}
/// \ref get_untyped for primitive types.
static jsont get_untyped_primitive(const jsont &json)
{
return get_untyped(json, "value");
}
/// \ref get_untyped for array types.
/// char arrays are treated as a special case as they are represented as an
/// array of a single String element by json-io, rather than an array of one or
/// more char elements.
static json_arrayt
get_untyped_array(const jsont &json, const typet &element_type)
{
const jsont untyped = get_untyped(json, "@items");
PRECONDITION(untyped.is_array());
const auto &json_array = to_json_array(untyped);
if(element_type == java_char_type())
{
PRECONDITION(json_array.size() == 1);
const auto &first = *json_array.begin();
PRECONDITION(first.is_string());
const auto &json_string = to_json_string(first);
const auto wide_string = utf8_to_utf16_native_endian(json_string.value);
auto string_range = make_range(wide_string.begin(), wide_string.end());
const auto json_range = string_range.map([](const wchar_t &c) {
const std::u16string u16(1, c);
return json_stringt{utf16_native_endian_to_utf8(u16)};
});
return json_arrayt{json_range.begin(), json_range.end()};
}
return json_array;
}
/// \ref get_untyped for string types.
/// Note that this differs from the standard serialization of java.lang.String
/// in json-io, but is consistent with the serialization of StringBuilder and
/// StringBuffer.
static jsont get_untyped_string(const jsont &json)
{
return get_untyped(json, "value");
}
/// Given a JSON representation of a (non-array) reference-typed object and a
/// type inferred from the type of a containing array, get the runtime type of
/// the corresponding pointer expression.
/// \param json: JSON representation of a non-array object. If it contains a
/// `@type` field, this takes priority over \p type_from_array. Types for non-
/// array objects are stored in the JSON in the format
/// "my.package.name.ClassName".
/// \param type_from_array: may contain an element type name given by a
/// containing array. Such types are stored in the form
/// "Lmy.package.name.ClassName;".
/// \param symbol_table: used to look up the type given its name.
/// \return runtime type of the object, if specified by at least one of the
/// parameters.
static std::optional<java_class_typet> runtime_type(
const jsont &json,
const std::optional<std::string> &type_from_array,
const symbol_table_baset &symbol_table)
{
const auto type_from_json = get_type(json);
if(!type_from_json && !type_from_array)
return {}; // no runtime type specified, use compile-time type
const auto runtime_type = [&]() -> std::string {
if(type_from_json)
return "java::" + *type_from_json;
INVARIANT(
type_from_array->find('L') == 0 &&
type_from_array->rfind(';') == type_from_array->length() - 1,
"Types inferred from the type of a containing array should be of the "
"form Lmy.package.name.ClassName;");
return "java::" + type_from_array->substr(1, type_from_array->length() - 2);
}();
if(!symbol_table.has_symbol(runtime_type))
return {}; // Fall back to compile-time type if runtime type was not found
const auto &replacement_class_type =
to_java_class_type(symbol_table.lookup_ref(runtime_type).type);
return replacement_class_type;
}
/// Given a JSON representation of an array and a type inferred from the type of
/// a containing array, get the element type by removing the leading '['.
/// Types for arrays are stored in the format "[Lmy.package.name.ClassName;".
/// In this case, the returned value would be "Lmy.package.name.ClassName;".
/// \p type_from_array would only have a value if this array is stored within
/// another array, i.e.\ within a ClassName[][].
/// Keeping track of array types in this way is necessary to assign generic
/// arrays with no compile-time types.
/// \param json: JSON representation of an array. If it contains a `@type`
/// field, this takes priority over \p type_from_array.
/// \param type_from_array: may contain a type name from a containing array.
/// \return if the type of an array was given, the type of its elements.
static std::optional<std::string> element_type_from_array_type(
const jsont &json,
const std::optional<std::string> &type_from_array)
{
if(const auto json_array_type = get_type(json))
{
INVARIANT(
json_array_type->find('[') == 0,
"Array types in the JSON input should be of the form "
"[[...[Lmy.package.name.ClassName; (with n occurrences of [ for an "
"n-dimensional array)");
return json_array_type->substr(1);
}
else if(type_from_array)
{
INVARIANT(
type_from_array->find('[') == 0,
"For arrays that are themselves contained by an array from which a type "
"is inferred, such a type should be of the form "
"[[...[Lmy.package.name.ClassName;");
return type_from_array->substr(1);
}
return {};
}
code_with_references_listt assign_from_json_rec(
const exprt &expr,
const jsont &json,
const std::optional<std::string> &type_from_array,
object_creation_infot &info);
/// One of the base cases (primitive case) of the recursion.
/// For characters, the encoding in \p json is assumed to be UTF-8.
/// See \ref assign_from_json_rec.
static code_with_references_listt
assign_primitive_from_json(const exprt &expr, const jsont &json)
{
code_with_references_listt result;
if(json.is_null()) // field is not mentioned in json, leave as default value
return result;
if(expr.type() == java_boolean_type())
{
result.add(code_frontend_assignt{
expr, json.is_true() ? (exprt)true_exprt{} : (exprt)false_exprt{}});
}
else if(
expr.type() == java_int_type() || expr.type() == java_byte_type() ||
expr.type() == java_short_type() || expr.type() == java_long_type())
{
result.add(code_frontend_assignt{
expr, from_integer(std::stoll(json.value), expr.type())});
}
else if(expr.type() == java_double_type())
{
ieee_floatt ieee_float(to_floatbv_type(expr.type()));
ieee_float.from_double(std::stod(json.value));
result.add(code_frontend_assignt{expr, ieee_float.to_expr()});
}
else if(expr.type() == java_float_type())
{
ieee_floatt ieee_float(to_floatbv_type(expr.type()));
ieee_float.from_float(std::stof(json.value));
result.add(code_frontend_assignt{expr, ieee_float.to_expr()});
}
else if(expr.type() == java_char_type())
{
const std::wstring wide_value = utf8_to_utf16_native_endian(json.value);
PRECONDITION(wide_value.length() == 1);
result.add(code_frontend_assignt{
expr, from_integer(wide_value.front(), expr.type())});
}
return result;
}
/// One of the base cases of the recursive algorithm. See
/// \ref assign_from_json_rec.
static code_frontend_assignt assign_null(const exprt &expr)
{
return code_frontend_assignt{
expr, null_pointer_exprt{to_pointer_type(expr.type())}};
}
/// In the case of an assignment of an array given a JSON representation, this
/// function assigns the data component of the array, which contains the array
/// elements. \p expr is a pointer to the array containing the component.
static code_with_references_listt assign_array_data_component_from_json(
const exprt &expr,
const jsont &json,
const std::optional<std::string> &type_from_array,
object_creation_infot &info)
{
const auto &java_class_type = followed_class_type(expr, info.symbol_table);
const auto &data_component = java_class_type.components()[2];
const auto &element_type = java_array_element_type(
to_struct_tag_type(to_pointer_type(expr.type()).base_type()));
const exprt data_member_expr = typecast_exprt::conditional_cast(
member_exprt{dereference_exprt{expr}, "data", data_component.type()},
pointer_type(element_type));
const symbol_exprt &array_init_data =
info.allocate_objects.allocate_automatic_local_object(
data_member_expr.type(), "user_specified_array_data_init");
code_with_references_listt result;
result.add(
code_frontend_assignt{array_init_data, data_member_expr, info.loc});
size_t index = 0;
const std::optional<std::string> inferred_element_type =
element_type_from_array_type(json, type_from_array);
const json_arrayt json_array = get_untyped_array(json, element_type);
for(auto it = json_array.begin(); it < json_array.end(); it++, index++)
{
const dereference_exprt element_at_index = array_element_from_pointer(
array_init_data, from_integer(index, java_int_type()));
result.append(
assign_from_json_rec(element_at_index, *it, inferred_element_type, info));
}
return result;
}
/// Declare a non-deterministic length expression
/// \param[out] allocate: allocation functor
/// \param loc: location for the created code
/// \return the length expression that has been non-deterministically created
/// and the code declaring the expression
static std::pair<symbol_exprt, code_with_references_listt>
nondet_length(allocate_objectst &allocate, source_locationt loc)
{
symbol_exprt length_expr = allocate.allocate_automatic_local_object(
java_int_type(), "user_specified_array_length");
code_with_references_listt code;
code.add(code_frontend_assignt{
length_expr, side_effect_expr_nondett{java_int_type(), loc}});
code.add(code_assumet{binary_predicate_exprt{
length_expr, ID_ge, from_integer(0, java_int_type())}});
return std::make_pair(length_expr, std::move(code));
}
/// One of the cases in the recursive algorithm: the case where \p expr
/// represents an array which is not flagged with \c \@nondetLength.
/// The length of the array is given by symbol \p given_length_expr.
/// We assume that an array with this symbol as its length has already been
/// allocated and that \p expr has been assigned to it.
/// We constraint the length of the array to be the number of elements, which
/// is known and constant.
/// For the assignment of the array elements, see
/// \ref assign_array_data_component_from_json.
/// For the overall algorithm, see \ref assign_from_json_rec.
/// \return code for the assignment and length of the created array.
static std::pair<code_with_references_listt, exprt>
assign_det_length_array_from_json(
const exprt &expr,
const jsont &json,
const std::optional<std::string> &type_from_array,
object_creation_infot &info)
{
PRECONDITION(is_java_array_type(expr.type()));
const auto &element_type = java_array_element_type(
to_struct_tag_type(to_java_reference_type(expr.type()).base_type()));
const json_arrayt json_array = get_untyped_array(json, element_type);
const auto number_of_elements =
from_integer(json_array.size(), java_int_type());
return {
assign_array_data_component_from_json(expr, json, type_from_array, info),
number_of_elements};
}
/// One of the cases in the recursive algorithm: the case where \p expr
/// represents an array which is flagged with \c \@nondetLength.
/// The length of the array is given by symbol \p given_length_expr.
/// We assume that an array with this symbol as its length has already been
/// allocated and that \p expr has been assigned to it.
/// We constrain the length of the array to be greater or equal to the number
/// of elements specified in \p json.
/// For the assignment of the array elements, see
/// \ref assign_array_data_component_from_json.
/// For the overall algorithm, see \ref assign_from_json_rec.
/// \return code for the assignment and length of the created array.
static code_with_references_listt assign_nondet_length_array_from_json(
const exprt &array,
const jsont &json,
const exprt &given_length_expr,
const std::optional<std::string> &type_from_array,
object_creation_infot &info)
{
PRECONDITION(is_java_array_type(array.type()));
const auto &element_type = java_array_element_type(
to_struct_tag_type(to_java_reference_type(array.type()).base_type()));
const json_arrayt json_array = get_untyped_array(json, element_type);
code_with_references_listt result;
exprt number_of_elements = from_integer(json_array.size(), java_int_type());
result.add(code_assumet{and_exprt{
binary_predicate_exprt{given_length_expr, ID_ge, number_of_elements},
binary_predicate_exprt{
given_length_expr,
ID_le,
from_integer(info.max_user_array_length, java_int_type())}}});
result.append(
assign_array_data_component_from_json(array, json, type_from_array, info));
return result;
}
/// One of the cases in the recursive algorithm: the case where \p expr
/// represents a string.
/// See \ref assign_from_json_rec.
static code_frontend_assignt assign_string_from_json(
const jsont &json,
const exprt &expr,
object_creation_infot &info)
{
const auto json_string = get_untyped_string(json);
PRECONDITION(json_string.is_string());
return code_frontend_assignt{
expr,
get_or_create_string_literal_symbol(
json_string.value, info.symbol_table, true)};
}
/// Helper function for \ref assign_struct_from_json which recursively assigns
/// values to all of the fields of the Java object represented by \p expr (the
/// components of its type and all of its parent types).
static code_with_references_listt assign_struct_components_from_json(
const exprt &expr,
const jsont &json,
object_creation_infot &info)
{
const java_class_typet &java_class_type =
to_java_class_type(namespacet{info.symbol_table}.follow(expr.type()));
code_with_references_listt result;
for(const auto &component : java_class_type.components())
{
const irep_idt &component_name = component.get_name();
if(
component_name == JAVA_CLASS_IDENTIFIER_FIELD_NAME ||
component_name == "cproverMonitorCount")
{
continue;
}
member_exprt member_expr{expr, component_name, component.type()};
if(component_name[0] == '@') // component is parent struct type
{
result.append(
assign_struct_components_from_json(member_expr, json, info));
}
else // component is class field (pointer to struct)
{
const auto member_json = [&]() -> jsont {
if(
is_primitive_wrapper_type_id(java_class_type.get_name()) &&
id2string(component_name) == "value")
{
return get_untyped_primitive(json);
}
return json[id2string(component_name)];
}();
result.append(assign_from_json_rec(member_expr, member_json, {}, info));
}
}
return result;
}
/// One of the cases in the recursive algorithm: the case where \p expr is a
/// struct, which is the result of dereferencing a pointer that corresponds to
/// the Java object described in \p json.
/// See \ref assign_from_json_rec.
static code_with_references_listt assign_struct_from_json(
const exprt &expr,
const jsont &json,
object_creation_infot &info)
{
const namespacet ns{info.symbol_table};
const java_class_typet &java_class_type =
to_java_class_type(ns.follow(expr.type()));
code_with_references_listt result;
if(is_java_string_type(java_class_type))
{
result.add(assign_string_from_json(json, expr, info));
}
else
{
auto initial_object = zero_initializer(expr.type(), info.loc, ns);
CHECK_RETURN(initial_object.has_value());
set_class_identifier(
to_struct_expr(*initial_object),
ns,
struct_tag_typet("java::" + id2string(java_class_type.get_tag())));
result.add(code_frontend_assignt{expr, *initial_object});
result.append(assign_struct_components_from_json(expr, json, info));
}
return result;
}
/// Same as \ref assign_pointer_from_json without special cases (enums).
static code_with_references_listt assign_non_enum_pointer_from_json(
const exprt &expr,
const jsont &json,
object_creation_infot &info)
{
code_with_references_listt result;
code_blockt output_code;
exprt dereferenced_symbol_expr =
info.allocate_objects.allocate_dynamic_object(
output_code, expr, to_pointer_type(expr.type()).base_type());
for(codet &code : output_code.statements())
result.add(std::move(code));
result.append(assign_struct_from_json(dereferenced_symbol_expr, json, info));
return result;
}
/// One of the cases in the recursive algorithm: the case where the expression
/// to be assigned a value is an enum constant that is referenced outside of the
/// definition of its type. (See \ref is_enum_with_type_equal_to_declaring_type
/// for this temporary distinction. See \ref assign_from_json for details about
/// the recursion.)
/// Once reference-equality of fields in different classes is supported, this
/// function can be removed.
static code_with_references_listt assign_enum_from_json(
const exprt &expr,
const jsont &json,
object_creation_infot &info)
{
const auto &java_class_type = followed_class_type(expr, info.symbol_table);
const std::string &enum_name = id2string(java_class_type.get_name());
code_with_references_listt result;
if(const auto func = info.symbol_table.lookup(clinit_wrapper_name(enum_name)))
result.add(code_function_callt{func->symbol_expr()});
const irep_idt values_name = enum_name + ".$VALUES";
if(!info.symbol_table.has_symbol(values_name))
{
// Fallback: generate a new enum instance instead of getting it from $VALUES
result.append(assign_non_enum_pointer_from_json(expr, json, info));
return result;
}
dereference_exprt values_struct{
info.symbol_table.lookup_ref(values_name).symbol_expr()};
const auto &values_struct_type =
to_struct_type(namespacet{info.symbol_table}.follow(values_struct.type()));
PRECONDITION(is_valid_java_array(values_struct_type));
const member_exprt values_data = member_exprt{
values_struct, "data", values_struct_type.components()[2].type()};
const exprt ordinal_expr =
from_integer(std::stoi(json["ordinal"].value), java_int_type());
result.add(code_frontend_assignt{
expr,
typecast_exprt::conditional_cast(
array_element_from_pointer(values_data, ordinal_expr), expr.type())});
return result;
}
/// One of the cases in the recursive algorithm: the case where \p expr is a
/// pointer to a struct, whose type is the same as the runtime-type of the
/// corresponding Java object.
/// See \ref assign_from_json_rec.
static code_with_references_listt assign_pointer_from_json(
const exprt &expr,
const jsont &json,
object_creation_infot &info)
{
// This check can be removed when tracking reference-equal objects across
// different classes has been implemented.
if(has_enum_type(expr, info.symbol_table))
return assign_enum_from_json(expr, json, info);
else
return assign_non_enum_pointer_from_json(expr, json, info);
}
/// One of the cases in the recursive algorithm: the case where \p expr is a
/// pointer to a struct, and \p runtime_type is the runtime type of the
/// corresponding Java object, which may be more specific than the type pointed
/// to by `expr.type()` (the compile-time type of the object).
/// See \ref assign_from_json_rec.
static code_with_references_listt assign_pointer_with_given_type_from_json(
const exprt &expr,
const jsont &json,
const java_class_typet &runtime_type,
object_creation_infot &info)
{
const auto &pointer_type = to_pointer_type(expr.type());
pointer_typet replacement_pointer_type =
pointer_to_replacement_type(pointer_type, runtime_type);
if(!equal_java_types(pointer_type, replacement_pointer_type))
{
const auto &new_symbol =
info.allocate_objects.allocate_automatic_local_object(
replacement_pointer_type, "user_specified_subtype_symbol");
if(info.needed_lazy_methods)
{
info.needed_lazy_methods->add_all_needed_classes(
replacement_pointer_type);
}
auto result = assign_pointer_from_json(new_symbol, json, info);
result.add(
code_frontend_assignt{expr, typecast_exprt{new_symbol, pointer_type}});
return result;
}
else
return assign_pointer_from_json(expr, json, info);
}
struct get_or_create_reference_resultt
{
/// true if a new symbol was allocated for
/// the given ID and false if the ID was found in the reference map
bool newly_allocated;
/// symbol expression(s) for the given ID.
std::unordered_map<std::string, object_creation_referencet>::iterator
reference;
/// initialization code for the reference
code_with_references_listt code;
};
/// Helper function for \ref assign_reference_from_json.
/// Look up the given \p id in the reference map and gets or creates the symbol
/// for it.
/// In the case of arrays, if the first time we see an ID is in a \c \@ref object
/// (rather than \c \@id), we do not know what the length of the array will be, so
/// we need to allocate an array of nondeterministic length. The length will
/// be constrained (in \ref assign_nondet_length_array_from_json) once we find the
/// corresponding \c \@id object.
/// \param expr: expression representing the Java object for which a symbol is
/// retrieved or allocated.
/// \param id: key in the reference map for this object
/// \param info: references used throughout the recursive algorithm.
/// \return a pair: the first element is true if a new symbol was allocated for
/// the given ID and false if the ID was found in the reference map. The
/// second element has the symbol expression(s) for this ID.
static get_or_create_reference_resultt get_or_create_reference(
const exprt &expr,
const std::string &id,
object_creation_infot &info)
{
const auto &pointer_type = to_pointer_type(expr.type());
const auto id_it = info.references.find(id);
if(id_it == info.references.end())
{
code_with_references_listt code;
object_creation_referencet reference;
if(is_java_array_type(expr.type()))
{
reference.expr = info.allocate_objects.allocate_automatic_local_object(
pointer_type, "user_specified_array_ref");
reference.array_length =
info.allocate_objects.allocate_automatic_local_object(
java_int_type(), "user_specified_array_length");
code.add(reference_allocationt{id, info.loc});
}
else
{
code_blockt block;
reference.expr = info.allocate_objects.allocate_dynamic_object_symbol(
block, expr, pointer_type.base_type());
code.add(block);
}
auto iterator_inserted_pair = info.references.insert({id, reference});
return {iterator_inserted_pair.second, iterator_inserted_pair.first, code};
}
return {false, id_it, {}};
}
/// One of the cases in the recursive algorithm: the case where \p expr
/// corresponds to a Java object that is reference-equal to one or more other
/// Java objects represented in the initial JSON file.
/// See \ref assign_from_json_rec.
/// Such an object will either have the key-value pair `@id: some_key` in
/// \p json, together with a full representation of the object, or it will only
/// have one key-value pair, `@ref: some_key`. For each key, there is only one
/// `@id` field in the JSON file.
/// A special case is enums, which are always represented as a full object
/// without any `@id` or `@ref` keys. This is mostly the same as the output from
/// json-io for enums, except that in our representation, we need to include the
/// ordinal field so that e.g. switch statements on enums will work.
/// We keep track of object IDs using a map from IDs to symbol expressions.
/// Usually the ID is the `some_key` from the example above, except for enums,
/// where the ID is of the form `my.package.name.EnumName.CONSTANT`.
/// The first time we see an ID (`@id`, `@ref` or enum constant), we allocate a
/// symbol for it. The first time we see the full representation of the object
/// (`@id` or enum constant) we initialize the allocated memory. This strategy
/// may need to be changed to support reference-equality of fields across
/// several different classes (e.g.\ as soon as we find a `@ref` for the first
/// time we might want to search the whole initial JSON file for the
/// corresponding `@id`).
static code_with_references_listt assign_reference_from_json(
const exprt &expr,
const jsont &json,
const std::optional<std::string> &type_from_array,
object_creation_infot &info)
{
const std::string &id = has_enum_type(expr, info.symbol_table)
? get_enum_id(expr, json, info.symbol_table)
: get_id_or_reference_value(json);
auto get_reference_result = get_or_create_reference(expr, id, info);
const bool is_new_id = get_reference_result.newly_allocated;
object_creation_referencet &reference =
get_reference_result.reference->second;
code_with_references_listt result = std::move(get_reference_result.code);
if(has_id(json) || (is_new_id && has_enum_type(expr, info.symbol_table)))
{
if(is_java_array_type(expr.type()))
{
INVARIANT(
reference.array_length, "an array reference should store its length");
if(has_nondet_length(json))
{
result.append(assign_nondet_length_array_from_json(
reference.expr,
json,
*reference.array_length,
type_from_array,
info));
}
else
{
auto code_length_pair = assign_det_length_array_from_json(
reference.expr, json, type_from_array, info);
result.append(std::move(code_length_pair.first));
reference.array_length = std::move(code_length_pair.second);
}
}
else
{
result.append(
assign_struct_from_json(dereference_exprt(reference.expr), json, info));
}
}
result.add(code_frontend_assignt{
expr, typecast_exprt::conditional_cast(reference.expr, expr.type())});
return result;
}
/// Entry point of the recursive deterministic assignment algorithm.
/// \param expr: expression to assign a deterministic value to. In the case of
/// the entry point, this is either a pointer to a struct, or an expression
/// corresponding to a Java primitive.
/// \param json: a JSON representation of the deterministic value to assign.
/// \param type_from_array: if `expr` was found as an element of an array,
/// the element type of this array.
/// \param info: references used throughout the recursive algorithm.
code_with_references_listt assign_from_json_rec(
const exprt &expr,
const jsont &json,
const std::optional<std::string> &type_from_array,
object_creation_infot &info)
{
code_with_references_listt result;
if(can_cast_type<pointer_typet>(expr.type()))
{
if(json.is_null())
{
result.add(assign_null(expr));
return result;
}
else if(
is_reference(json) || has_id(json) ||
is_enum_with_type_equal_to_declaring_type(
expr, info.symbol_table, info.declaring_class_type))
// The last condition can be replaced with
// has_enum_type(expr, info.symbol_table) once tracking reference-equality
// across different classes has been implemented.
{
return assign_reference_from_json(expr, json, type_from_array, info);
}
else if(is_java_array_type(expr.type()))
{
if(has_nondet_length(json))
{
auto length_code_pair = nondet_length(info.allocate_objects, info.loc);
length_code_pair.second.add(
allocate_array(expr, length_code_pair.first, info.loc));
length_code_pair.second.append(assign_nondet_length_array_from_json(
expr, json, length_code_pair.first, type_from_array, info));
return length_code_pair.second;
}
else
{
PRECONDITION(is_java_array_type(expr.type()));
const auto &element_type = java_array_element_type(
to_struct_tag_type(to_java_reference_type(expr.type()).base_type()));
const std::size_t length = get_untyped_array(json, element_type).size();
result.add(allocate_array(
expr, from_integer(length, java_int_type()), info.loc));
result.append(assign_array_data_component_from_json(
expr, json, type_from_array, info));
return result;
}
}
else if(
const auto runtime_type =
::runtime_type(json, type_from_array, info.symbol_table))
{
return assign_pointer_with_given_type_from_json(
expr, json, *runtime_type, info);
}
else
return assign_pointer_from_json(expr, json, info);
}
else
result.append(
assign_primitive_from_json(expr, get_untyped_primitive(json)));
return result;
}
code_with_references_listt assign_from_json(
const exprt &expr,
const jsont &json,
const irep_idt &function_id,
symbol_table_baset &symbol_table,
std::optional<ci_lazy_methods_neededt> &needed_lazy_methods,
size_t max_user_array_length,
std::unordered_map<std::string, object_creation_referencet> &references)
{
source_locationt location{};
location.set_function(function_id);
allocate_objectst allocate(ID_java, location, function_id, symbol_table);
const symbolt *function_symbol = symbol_table.lookup(function_id);
INVARIANT(function_symbol, "Function must appear in symbol table");
const auto class_name = declaring_class(*function_symbol);
INVARIANT(
class_name,
"Function " + id2string(function_id) + " must be declared by a class.");
const auto &class_type =
to_java_class_type(symbol_table.lookup_ref(*class_name).type);
object_creation_infot info{allocate,
symbol_table,
needed_lazy_methods,
references,
location,
max_user_array_length,
class_type};
code_with_references_listt code_with_references =
assign_from_json_rec(expr, json, {}, info);
code_blockt assignments;
allocate.declare_created_symbols(assignments);
std::for_each(
assignments.statements().rbegin(),
assignments.statements().rend(),
[&](const codet &c) {
code_with_references.add_to_front(code_without_referencest{c});
});
return code_with_references;
}