Skip to content

Commit 639b864

Browse files
Merge pull request #4929 from NathanJPhillips/bugfix/generic_parameter_specialization_map
Fix generic_parameter_specialization_map in the presence of non-symmetric generic recursion [TG-7815]
2 parents d2fb690 + 186adb8 commit 639b864

35 files changed

+612
-359
lines changed

jbmc/src/java_bytecode/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ SRC = assignments_from_json.cpp \
55
ci_lazy_methods_needed.cpp \
66
convert_java_nondet.cpp \
77
expr2java.cpp \
8+
generic_parameter_specialization_map.cpp \
89
generic_parameter_specialization_map_keys.cpp \
910
jar_file.cpp \
1011
jar_pool.cpp \

jbmc/src/java_bytecode/README.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,7 @@ types so we keep a stack of types for each parameter.
7676

7777
We use the map in \ref select_pointer_typet::specialize_generics to
7878
retrieve the concrete type of generic parameters such as `MyGeneric::T` and of
79-
arrays of generic parameters such as `MyGeneric::T[]`. Special attention
80-
is paid to breaking infinite recursion when searching the map, e.g.,
81-
`MyGeneric::T` being specialized with `MyGeneric::U` and vice versa, for an
82-
example of such a recursion see
83-
\ref select_pointer_typet::get_recursively_instantiated_type. More complicated
79+
arrays of generic parameters such as `MyGeneric::T[]`. More complicated
8480
generic types such as `MyGeneric<T>` are specialized indirectly within \ref
8581
java_object_factoryt. Their concrete types are already stored in the map and are
8682
retrieved when needed, e.g., to initialize their fields.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/// Author: Diffblue Ltd.
2+
3+
#include "generic_parameter_specialization_map.h"
4+
5+
std::size_t generic_parameter_specialization_mapt::insert(
6+
const std::vector<java_generic_parametert> &parameters,
7+
std::vector<reference_typet> types)
8+
{
9+
PRECONDITION(!parameters.empty());
10+
const auto first_param_it =
11+
param_to_container.find(parameters.front().get_name());
12+
std::size_t container_index;
13+
if(first_param_it == param_to_container.end())
14+
{
15+
container_index = container_to_specializations.size();
16+
container_to_specializations.emplace_back();
17+
std::size_t param_index = 0;
18+
for(const java_generic_parametert &parameter : parameters)
19+
{
20+
const auto result = param_to_container.emplace(
21+
parameter.get_name(), container_paramt{container_index, param_index++});
22+
INVARIANT(
23+
result.second, "Some type parameters are already mapped but not all");
24+
}
25+
}
26+
else
27+
{
28+
container_index = first_param_it->second.container_index;
29+
std::size_t param_index = 0;
30+
for(const java_generic_parametert &parameter : parameters)
31+
{
32+
const auto param_it = param_to_container.find(parameter.get_name());
33+
INVARIANT(
34+
param_it != param_to_container.end(),
35+
"Some type parameters are already mapped but not all");
36+
INVARIANT(
37+
param_it->second.container_index == container_index,
38+
"Not all type parameters are assigned to same container");
39+
INVARIANT(
40+
param_it->second.param_index == param_index,
41+
"Type parameters have been encountered in two different orders");
42+
++param_index;
43+
}
44+
}
45+
container_to_specializations[container_index].push(std::move(types));
46+
return container_index;
47+
}
48+
49+
void generic_parameter_specialization_mapt::pop(std::size_t container_index)
50+
{
51+
container_to_specializations.at(container_index).pop();
52+
}
53+
54+
optionalt<reference_typet>
55+
generic_parameter_specialization_mapt::pop(const irep_idt &parameter_name)
56+
{
57+
const auto types_it = param_to_container.find(parameter_name);
58+
if(types_it == param_to_container.end())
59+
return {};
60+
std::stack<std::vector<reference_typet>> &stack =
61+
container_to_specializations.at(types_it->second.container_index);
62+
if(stack.empty())
63+
return {};
64+
reference_typet result = stack.top().at(types_it->second.param_index);
65+
stack.pop();
66+
return result;
67+
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/// Author: Diffblue Ltd.
2+
3+
#ifndef CPROVER_JAVA_BYTECODE_GENERIC_PARAMETER_SPECIALIZATION_MAP_H
4+
#define CPROVER_JAVA_BYTECODE_GENERIC_PARAMETER_SPECIALIZATION_MAP_H
5+
6+
#include <stack>
7+
#include <vector>
8+
9+
#include <util/string_utils.h>
10+
11+
#include "expr2java.h"
12+
#include "java_types.h"
13+
14+
/// A map from generic type parameters to their specializations (type arguments)
15+
/// We need to map from the names of parameters to the container (class or
16+
/// method) that contains them as well as the particular specialization of that
17+
/// parameter. However we do not need the name of the container, merely the full
18+
/// set of specializations for the parameters of that container.
19+
/// We store a stack of specializations for each container for a particular
20+
/// context. Finding the value for a type parameter is a matter of following
21+
/// the specialization for that parameter, unwinding the stack of its container
22+
/// as you do so and repeating until one reaches a non-generic type.
23+
class generic_parameter_specialization_mapt
24+
{
25+
private:
26+
/// The index of the container and the type parameter inside that container
27+
struct container_paramt
28+
{
29+
std::size_t container_index;
30+
std::size_t param_index;
31+
};
32+
/// A map from parameter names to container_paramt instances
33+
std::unordered_map<irep_idt, container_paramt> param_to_container;
34+
/// The list of containers and, for each one, the stack of lists of
35+
/// specializations
36+
std::vector<std::stack<std::vector<reference_typet>>>
37+
container_to_specializations;
38+
39+
public:
40+
/// Insert a specialization for each type parameters of a container
41+
/// \param parameters: The type parameters
42+
/// \param types: The type arguments
43+
/// \returns: The index of the added container
44+
std::size_t insert(
45+
const std::vector<java_generic_parametert> &parameters,
46+
std::vector<reference_typet> types);
47+
48+
/// Pop the top of the specialization stack for a given container
49+
/// \param container_index: The index of the container to pop
50+
void pop(std::size_t container_index);
51+
52+
/// Pop the top of the specialization stack for the container associated with
53+
/// a given type parameter
54+
/// \param parameter_name: The name of the type parameter
55+
/// \returns: The specialization for the given type parameter, if there was
56+
/// one before the pop, or an empty optionalt if the stack was empty
57+
optionalt<reference_typet> pop(const irep_idt &parameter_name);
58+
59+
/// A wrapper for a generic_parameter_specialization_mapt and a namespacet
60+
/// that can be output to a stream
61+
struct printert
62+
{
63+
const generic_parameter_specialization_mapt &map;
64+
const namespacet &ns;
65+
66+
printert(
67+
const generic_parameter_specialization_mapt &map,
68+
const namespacet &ns)
69+
: map(map), ns(ns)
70+
{
71+
}
72+
};
73+
74+
template <typename ostreamt>
75+
friend ostreamt &operator<<(ostreamt &stm, const printert &map);
76+
};
77+
78+
/// Output a generic_parameter_specialization_mapt wrapped in a
79+
/// generic_parameter_specialization_mapt::printert to a stream
80+
/// \tparam ostreamt: The type of stream to output to (not restricted to be
81+
/// derived from std::ostream)
82+
/// \param stm: The stream to output to
83+
/// \param map: The generic_parameter_specialization_mapt printer to output
84+
/// \returns: A reference to the stream passed in
85+
template <typename ostreamt>
86+
ostreamt &operator<<(
87+
ostreamt &stm,
88+
const generic_parameter_specialization_mapt::printert &map)
89+
{
90+
if(map.map.container_to_specializations.empty())
91+
stm << "empty map";
92+
else
93+
stm << "map:\n";
94+
std::vector<std::vector<irep_idt>> container_to_params(
95+
map.map.container_to_specializations.size());
96+
for(const auto &elt : map.map.param_to_container)
97+
{
98+
std::vector<irep_idt> &params =
99+
container_to_params[elt.second.container_index];
100+
if(params.size() < elt.second.param_index + 1)
101+
params.resize(elt.second.param_index + 1);
102+
params[elt.second.param_index] = elt.first;
103+
}
104+
const namespacet &ns = map.ns;
105+
for(std::size_t index = 0; index < container_to_params.size(); ++index)
106+
{
107+
stm << "<";
108+
join_strings(
109+
stm,
110+
container_to_params.at(index).begin(),
111+
container_to_params.at(index).end(),
112+
", ");
113+
stm << ">: ";
114+
std::stack<std::vector<reference_typet>> stack =
115+
map.map.container_to_specializations.at(index);
116+
while(!stack.empty())
117+
{
118+
stm << "<";
119+
join_strings(
120+
stm,
121+
stack.top().begin(),
122+
stack.top().end(),
123+
", ",
124+
[&ns](const reference_typet &pointer_type) {
125+
if(is_java_generic_parameter(pointer_type))
126+
{
127+
return id2string(
128+
to_java_generic_parameter(pointer_type).get_name());
129+
}
130+
else
131+
return type2java(pointer_type, ns);
132+
});
133+
stm << ">, ";
134+
stack.pop();
135+
}
136+
stm << "\n";
137+
}
138+
return stm;
139+
}
140+
141+
#endif // CPROVER_JAVA_BYTECODE_GENERIC_PARAMETER_SPECIALIZATION_MAP_H

jbmc/src/java_bytecode/generic_parameter_specialization_map_keys.cpp

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -35,51 +35,18 @@ get_all_generic_parameters(const typet &type)
3535
return generic_parameters;
3636
}
3737

38-
/// Add pairs to the controlled map. Own the keys and pop from their stack
39-
/// on destruction; otherwise do nothing.
40-
/// \param parameters: generic parameters that are the keys of the pairs to add
41-
/// \param types: a type to add for each parameter
42-
void generic_parameter_specialization_map_keyst::insert_pairs(
43-
const std::vector<java_generic_parametert> &parameters,
44-
const std::vector<reference_typet> &types)
45-
{
46-
INVARIANT(erase_keys.empty(), "insert_pairs should only be called once");
47-
48-
for(const auto &pair : make_range(parameters).zip(types))
49-
{
50-
// Only add the pair if the type is not the parameter itself, e.g.,
51-
// pair.first = pair.second = java::A::T. This can happen for example
52-
// when initializing a pointer to an implicitly generic Java class type
53-
// in gen_nondet_init and would result in a loop when the map is used
54-
// to look up the type of the parameter.
55-
if(
56-
!(is_java_generic_parameter(pair.second) &&
57-
to_java_generic_parameter(pair.second).get_name() ==
58-
pair.first.get_name()))
59-
{
60-
const irep_idt &key = pair.first.get_name();
61-
const auto map_it = generic_parameter_specialization_map
62-
.emplace(key, std::vector<reference_typet>{})
63-
.first;
64-
map_it->second.push_back(pair.second);
65-
66-
// We added something; pop it when this
67-
// generic_parameter_specialization_map_keyst is destroyed
68-
erase_keys.push_back(key);
69-
}
70-
}
71-
}
72-
73-
/// Add a pair of a parameter and its types for each generic parameter of the
74-
/// given generic pointer type to the controlled map. Own the keys and pop
75-
/// from their stack on destruction; otherwise do nothing.
38+
/// Add the parameters and their types for each generic parameter of the
39+
/// given generic pointer type to the map.
40+
/// Own the keys and pop from their stack on destruction.
7641
/// \param pointer_type: pointer type to get the specialized generic types from
7742
/// \param pointer_subtype_struct: struct type to which the generic pointer
7843
/// points, must be generic if the pointer is generic
79-
void generic_parameter_specialization_map_keyst::insert_pairs_for_pointer(
44+
void generic_parameter_specialization_map_keyst::insert(
8045
const pointer_typet &pointer_type,
8146
const typet &pointer_subtype_struct)
8247
{
48+
// Use a fresh generic_parameter_specialization_map_keyst for each scope
49+
PRECONDITION(!container_id);
8350
if(!is_java_generic_type(pointer_type))
8451
return;
8552
// The supplied type must be the full type of the pointer's subtype
@@ -91,7 +58,7 @@ void generic_parameter_specialization_map_keyst::insert_pairs_for_pointer(
9158
// - an incomplete class or
9259
// - a class that is neither generic nor implicitly generic (this
9360
// may be due to unsupported class signature)
94-
// then ignore the generic types in the pointer and do not add any pairs.
61+
// then ignore the generic types in the pointer and do not add an entry.
9562
// TODO TG-1996 should decide how mocking and generics should work
9663
// together. Currently an incomplete class is never marked as generic. If
9764
// this changes in TG-1996 then the condition below should be updated.
@@ -115,34 +82,39 @@ void generic_parameter_specialization_map_keyst::insert_pairs_for_pointer(
11582
type_args.size() == generic_parameters.size(),
11683
"All generic parameters of the pointer type need to be specified");
11784

118-
insert_pairs(generic_parameters, type_args);
85+
container_id =
86+
generic_parameter_specialization_map.insert(generic_parameters, type_args);
11987
}
12088

121-
/// Add a pair of a parameter and its types for each generic parameter of the
122-
/// given generic symbol type to the controlled map. This function is used
123-
/// for generic bases (superclass or interfaces) where the reference to it is
124-
/// in the form of a symbol rather than a pointer (as opposed to the function
125-
/// insert_pairs_for_pointer). Own the keys and pop from their stack
126-
/// on destruction; otherwise do nothing.
89+
/// Add the parameters and their types for each generic parameter of the
90+
/// given generic symbol type to the map.
91+
/// This function is used for generic bases (superclass or interfaces) where
92+
/// the reference to it is in the form of a symbol rather than a pointer.
93+
/// Own the keys and pop from their stack on destruction.
12794
/// \param struct_tag_type: symbol type to get the specialized generic types
12895
/// from
12996
/// \param symbol_struct: struct type of the symbol type, must be generic if
13097
/// the symbol is generic
131-
void generic_parameter_specialization_map_keyst::insert_pairs_for_symbol(
98+
void generic_parameter_specialization_map_keyst::insert(
13299
const struct_tag_typet &struct_tag_type,
133100
const typet &symbol_struct)
134101
{
102+
// Use a fresh generic_parameter_specialization_map_keyst for each scope
103+
PRECONDITION(!container_id);
104+
if(!is_java_generic_struct_tag_type(struct_tag_type))
105+
return;
106+
// The supplied type must be the full type of the pointer's subtype
107+
PRECONDITION(
108+
struct_tag_type.get(ID_identifier) == symbol_struct.get(ID_name));
109+
135110
// If the struct is:
136111
// - an incomplete class or
137112
// - a class that is neither generic nor implicitly generic (this
138113
// may be due to unsupported class signature)
139-
// then ignore the generic types in the struct_tag_type and do not add any
140-
// pairs.
114+
// then ignore the generic types in the struct and do not add an entry.
141115
// TODO TG-1996 should decide how mocking and generics should work
142116
// together. Currently an incomplete class is never marked as generic. If
143117
// this changes in TG-1996 then the condition below should be updated.
144-
if(!is_java_generic_struct_tag_type(struct_tag_type))
145-
return;
146118
if(to_java_class_type(symbol_struct).get_is_stub())
147119
return;
148120
if(
@@ -151,17 +123,18 @@ void generic_parameter_specialization_map_keyst::insert_pairs_for_symbol(
151123
{
152124
return;
153125
}
126+
154127
const java_generic_struct_tag_typet &generic_symbol =
155128
to_java_generic_struct_tag_type(struct_tag_type);
156129

157130
const std::vector<java_generic_parametert> &generic_parameters =
158131
get_all_generic_parameters(symbol_struct);
159132
const java_generic_typet::generic_type_argumentst &type_args =
160133
generic_symbol.generic_types();
161-
162134
INVARIANT(
163135
type_args.size() == generic_parameters.size(),
164136
"All generic parameters of the superclass need to be concretized");
165137

166-
insert_pairs(generic_parameters, type_args);
138+
container_id =
139+
generic_parameter_specialization_map.insert(generic_parameters, type_args);
167140
}

0 commit comments

Comments
 (0)