Skip to content

Commit 0d04f37

Browse files
author
Matthias Güdemann
authored
Merge pull request #2239 from mgudemann/bugfix/generics/fix_infinite_recursion
[TG-3067] Support generic type parameters that create recursion loops in instantiation stack
2 parents b7725b8 + d196cf7 commit 0d04f37

17 files changed

+473
-21
lines changed
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class KeyValue<K, V> {
2+
KeyValue<K, V> next;
3+
KeyValue<V, K> reverse;
4+
K key;
5+
V value;
6+
}
7+
class MutuallyRecursiveGenerics {
8+
void f() {
9+
KeyValue<String, Integer> example1;
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
MutuallyRecursiveGenerics.class
3+
--cover location --function MutuallyRecursiveGenerics.f
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
--
7+
--
8+
This failed before due to infinite recursion in the way how the instantiated
9+
generic parameters were used. cf. TG-3067

jbmc/src/java_bytecode/generic_parameter_specialization_map_keys.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ const void generic_parameter_specialization_map_keyst::insert_pairs(
7272
const irep_idt &key = pair.first.get_name();
7373
if(generic_parameter_specialization_map.count(key) == 0)
7474
generic_parameter_specialization_map.emplace(
75-
key, std::stack<reference_typet>());
75+
key, std::vector<reference_typet>());
7676
(*generic_parameter_specialization_map.find(key))
77-
.second.push(pair.second);
77+
.second.push_back(pair.second);
7878

7979
// We added something, so pop it when this is destroyed:
8080
erase_keys.push_back(key);

jbmc/src/java_bytecode/generic_parameter_specialization_map_keys.h

+3-4
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,10 @@ class generic_parameter_specialization_map_keyst
3333
for(const auto key : erase_keys)
3434
{
3535
PRECONDITION(generic_parameter_specialization_map.count(key) != 0);
36-
(*generic_parameter_specialization_map.find(key)).second.pop();
37-
if((*generic_parameter_specialization_map.find(key)).second.empty())
38-
{
36+
auto &val = generic_parameter_specialization_map.find(key)->second;
37+
val.pop_back();
38+
if(val.empty())
3939
generic_parameter_specialization_map.erase(key);
40-
}
4140
}
4241
}
4342

jbmc/src/java_bytecode/java_object_factory.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class java_object_factoryt
4040

4141
/// Every time the non-det generator visits a type and the type is generic
4242
/// (either a struct or a pointer), the following map is used to store and
43-
/// look up the concrete types of the generic paramaters in the current
43+
/// look up the concrete types of the generic parameters in the current
4444
/// scope. Note that not all generic parameters need to have a concrete
4545
/// type, e.g., the method under test is generic. The types are removed
4646
/// from the map when the scope changes. Note that in different depths

jbmc/src/java_bytecode/select_pointer_type.cpp

+123-10
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ pointer_typet select_pointer_typet::convert_pointer_type(
3232
// a generic parameter, specialize it with concrete types
3333
if(!generic_parameter_specialization_map.empty())
3434
{
35-
return specialize_generics(
36-
pointer_type, generic_parameter_specialization_map);
35+
generic_parameter_recursion_trackingt visited;
36+
const auto &type = specialize_generics(
37+
pointer_type, generic_parameter_specialization_map, visited);
38+
INVARIANT(visited.empty(), "recursion stack must be empty here");
39+
return type;
3740
}
3841
else
3942
{
@@ -62,13 +65,24 @@ pointer_typet select_pointer_typet::convert_pointer_type(
6265
pointer_typet select_pointer_typet::specialize_generics(
6366
const pointer_typet &pointer_type,
6467
const generic_parameter_specialization_mapt
65-
&generic_parameter_specialization_map) const
68+
&generic_parameter_specialization_map,
69+
generic_parameter_recursion_trackingt &visited_nodes) const
6670
{
6771
if(is_java_generic_parameter(pointer_type))
6872
{
6973
const java_generic_parametert &parameter =
7074
to_java_generic_parameter(pointer_type);
7175
const irep_idt &parameter_name = parameter.get_name();
76+
77+
// avoid infinite recursion by looking at each generic argument from
78+
// previous assignments
79+
if(visited_nodes.find(parameter_name) != visited_nodes.end())
80+
{
81+
const optionalt<pointer_typet> result = get_recursively_instantiated_type(
82+
parameter_name, generic_parameter_specialization_map);
83+
return result.has_value() ? result.value() : pointer_type;
84+
}
85+
7286
if(generic_parameter_specialization_map.count(parameter_name) == 0)
7387
{
7488
// this means that the generic pointer_type has not been specialized
@@ -78,15 +92,20 @@ pointer_typet select_pointer_typet::specialize_generics(
7892
return pointer_type;
7993
}
8094
const pointer_typet &type =
81-
generic_parameter_specialization_map.find(parameter_name)->second.top();
95+
generic_parameter_specialization_map.find(parameter_name)->second.back();
8296

8397
// generic parameters can be adopted from outer classes or superclasses so
8498
// we may need to search for the concrete type recursively
85-
return is_java_generic_parameter(type)
86-
? specialize_generics(
87-
to_java_generic_parameter(type),
88-
generic_parameter_specialization_map)
89-
: type;
99+
if(!is_java_generic_parameter(type))
100+
return type;
101+
102+
visited_nodes.insert(parameter_name);
103+
const auto returned_type = specialize_generics(
104+
to_java_generic_parameter(type),
105+
generic_parameter_specialization_map,
106+
visited_nodes);
107+
visited_nodes.erase(parameter_name);
108+
return returned_type;
90109
}
91110
else if(pointer_type.subtype().id() == ID_symbol)
92111
{
@@ -99,7 +118,8 @@ pointer_typet select_pointer_typet::specialize_generics(
99118
{
100119
const pointer_typet &new_array_type = specialize_generics(
101120
to_pointer_type(array_element_type),
102-
generic_parameter_specialization_map);
121+
generic_parameter_specialization_map,
122+
visited_nodes);
103123

104124
pointer_typet replacement_array_type = java_array_type('a');
105125
replacement_array_type.subtype().set(ID_C_element_type, new_array_type);
@@ -109,3 +129,96 @@ pointer_typet select_pointer_typet::specialize_generics(
109129
}
110130
return pointer_type;
111131
}
132+
133+
/// Return the first concrete type instantiation if any such exists. This method
134+
/// is only to be called when the `specialize_generics` cannot find an
135+
/// instantiation due to a loop in its recursion.
136+
/// \param parameter_name The name of the generic parameter type we want to have
137+
/// instantiated
138+
/// \param generic_parameter_specialization_map Map of type names to
139+
/// specialization stack
140+
/// \return The first instantiated type for the generic type or nothing if no
141+
/// such instantiation exists.
142+
optionalt<pointer_typet>
143+
select_pointer_typet::get_recursively_instantiated_type(
144+
const irep_idt &parameter_name,
145+
const generic_parameter_specialization_mapt
146+
&generic_parameter_specialization_map) const
147+
{
148+
generic_parameter_recursion_trackingt visited;
149+
const size_t max_depth =
150+
generic_parameter_specialization_map.find(parameter_name)->second.size();
151+
152+
irep_idt current_parameter = parameter_name;
153+
for(size_t depth = 0; depth < max_depth; depth++)
154+
{
155+
const auto retval = get_recursively_instantiated_type(
156+
current_parameter, generic_parameter_specialization_map, visited, depth);
157+
if(retval.has_value())
158+
{
159+
CHECK_RETURN(!is_java_generic_parameter(*retval));
160+
return retval;
161+
}
162+
CHECK_RETURN(visited.empty());
163+
164+
const auto &entry =
165+
generic_parameter_specialization_map.find(current_parameter)
166+
->second.back();
167+
current_parameter = to_java_generic_parameter(entry).get_name();
168+
}
169+
return {};
170+
}
171+
172+
/// See get_recursively instantiated_type, the additional parameters just track
173+
/// the recursion to prevent visiting the same depth again and specify which
174+
/// stack depth is analyzed.
175+
/// \param parameter_name The name of the generic parameter type we want to have
176+
/// instantiated
177+
/// \param generic_parameter_specialization_map Map of type names to
178+
/// specialization stack
179+
/// \param visited Tracks the visited parameter names
180+
/// \param depth Stack depth to analyze
181+
/// \return if this type is not a generic type, it is returned as a valid
182+
/// instantiation, if nothing can be found at the given depth, en empty
183+
/// optional is returned
184+
optionalt<pointer_typet>
185+
select_pointer_typet::get_recursively_instantiated_type(
186+
const irep_idt &parameter_name,
187+
const generic_parameter_specialization_mapt
188+
&generic_parameter_specialization_map,
189+
generic_parameter_recursion_trackingt &visited,
190+
const size_t depth) const
191+
{
192+
const auto &val = generic_parameter_specialization_map.find(parameter_name);
193+
INVARIANT(
194+
val != generic_parameter_specialization_map.end(),
195+
"generic parameter must be a key in map");
196+
197+
const auto &replacements = val->second;
198+
199+
INVARIANT(
200+
depth < replacements.size(), "cannot access elements outside stack");
201+
202+
// Check if there is a recursion loop, if yes return with nothing found
203+
if(visited.find(parameter_name) != visited.end())
204+
{
205+
return {};
206+
}
207+
208+
const size_t index = (replacements.size() - 1) - depth;
209+
const auto &type = replacements[index];
210+
211+
if(!is_java_generic_parameter(type))
212+
{
213+
return type;
214+
}
215+
216+
visited.insert(parameter_name);
217+
const auto inst_val = get_recursively_instantiated_type(
218+
to_java_generic_parameter(type).get_name(),
219+
generic_parameter_specialization_map,
220+
visited,
221+
depth);
222+
visited.erase(parameter_name);
223+
return inst_val;
224+
}

jbmc/src/java_bytecode/select_pointer_type.h

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,29 @@
1111
/// \file
1212
/// Handle selection of correct pointer type (for example changing abstract
1313
/// classes to concrete versions).
14+
#include <vector>
1415

15-
#include <util/std_types.h>
16-
#include <stack>
1716
#include "java_types.h"
17+
#include <util/optional.h>
18+
#include <util/std_types.h>
1819

19-
typedef std::unordered_map<irep_idt, std::stack<reference_typet>>
20+
typedef std::unordered_map<irep_idt, std::vector<reference_typet>>
2021
generic_parameter_specialization_mapt;
22+
typedef std::set<irep_idt> generic_parameter_recursion_trackingt;
2123

2224
class namespacet;
2325

2426
class select_pointer_typet
2527
{
28+
optionalt<pointer_typet> get_recursively_instantiated_type(
29+
const irep_idt &,
30+
const generic_parameter_specialization_mapt &,
31+
generic_parameter_recursion_trackingt &,
32+
const size_t) const;
33+
optionalt<pointer_typet> get_recursively_instantiated_type(
34+
const irep_idt &parameter_name,
35+
const generic_parameter_specialization_mapt &visited) const;
36+
2637
public:
2738
virtual ~select_pointer_typet() = default;
2839
virtual pointer_typet convert_pointer_type(
@@ -34,7 +45,8 @@ class select_pointer_typet
3445
pointer_typet specialize_generics(
3546
const pointer_typet &pointer_type,
3647
const generic_parameter_specialization_mapt
37-
&generic_parameter_specialization_map) const;
48+
&generic_parameter_specialization_map,
49+
generic_parameter_recursion_trackingt &visited) const;
3850
};
3951

4052
#endif // CPROVER_JAVA_BYTECODE_SELECT_POINTER_TYPE_H

jbmc/unit/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ SRC += java_bytecode/goto-programs/class_hierarchy_output.cpp \
3232
util/simplify_expr.cpp \
3333
java_bytecode/java_virtual_functions/virtual_functions.cpp \
3434
java_bytecode/java_bytecode_parse_generics/parse_generic_superclasses.cpp \
35+
java_bytecode/goto_program_generics/mutually_recursive_generics.cpp \
3536
# Empty last line
3637

3738
INCLUDES= -I ../src/ -I. -I $(CPROVER_DIR)/src -I $(CPROVER_DIR)/unit
Binary file not shown.
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
class Outer<K, V, U> {
2+
class Inner<U> {
3+
Outer<V, K, U> o;
4+
U u;
5+
}
6+
Inner<U> inner;
7+
K key;
8+
V value;
9+
}
10+
class Three<X, E, U> {
11+
Three<U, X, E> rotate;
12+
Three<X, E, U> normal;
13+
X x;
14+
E e;
15+
U u;
16+
}
17+
class KeyValue<K, V> {
18+
KeyValue<K, V> next;
19+
KeyValue<V, K> reverse;
20+
K key;
21+
V value;
22+
}
23+
class MutuallyRecursiveGenerics {
24+
KeyValue<String, Integer> example1;
25+
Three<Byte, Character, Integer> example2;
26+
Outer<Boolean, Byte, Short> example3;
27+
void f() {
28+
}
29+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)