Skip to content

Commit af9104f

Browse files
committed
Java frontend: support functional interfaces created by defining or explicitly abstracting methods
It's possible to make a functional interface by taking a parent or parents that have more than one abstract method, then inheriting default definitions or making definitions yourself so that only one abstract method remains, and so the interface becomes functional. Similarly you can re-declare a method as abstract even if a parent made a default definition. This commit adds support for all of these cases and tests them.
1 parent d8fad9c commit af9104f

File tree

10 files changed

+152
-65
lines changed

10 files changed

+152
-65
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
public class Test {
2+
3+
interface TwoUndef {
4+
5+
int f(int x);
6+
int g(int x);
7+
8+
}
9+
10+
interface OneUndefDirect extends TwoUndef {
11+
12+
default int f(int x) { return x + 1; }
13+
14+
}
15+
16+
interface OneUndefIndirect extends OneUndefDirect, TwoUndef {
17+
18+
}
19+
20+
interface OneDef {
21+
22+
default int h(int x) { return x + 1; }
23+
24+
}
25+
26+
interface MakeOneDefAbstract {
27+
28+
int h(int x);
29+
30+
}
31+
32+
public static void main(String[] args) {
33+
34+
OneUndefDirect test1 = x -> x + 2;
35+
OneUndefIndirect test2 = x -> x + 2;
36+
MakeOneDefAbstract test3 = x -> x + 2;
37+
assert test1.f(1) == 2;
38+
assert test1.g(1) == 3;
39+
assert test2.f(1) == 2;
40+
assert test2.g(1) == 3;
41+
assert test3.h(1) == 3;
42+
assert false; // Check we got here
43+
44+
}
45+
46+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE
2+
Test
3+
--function Test.main
4+
line 37 assertion at file Test\.java line 37 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 16: SUCCESS
5+
line 38 assertion at file Test\.java line 38 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 27: SUCCESS
6+
line 39 assertion at file Test\.java line 39 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 38: SUCCESS
7+
line 40 assertion at file Test\.java line 40 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 49: SUCCESS
8+
line 41 assertion at file Test\.java line 41 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 60: SUCCESS
9+
line 42 assertion at file Test\.java line 42 function java::Test\.main:\(\[Ljava/lang/String;\)V bytecode-index 66: FAILURE
10+
^EXIT=10$
11+
^SIGNAL=0$
12+
--
13+
--
14+
This checks that we recognise functional interfaces created by taking an interface with two undefined methods
15+
and then supplying a default definition of one of them, either directly or by inheritence.
16+
We also test the case where an interface has inherited a default but then makes it explicitly abstract again.

jbmc/src/java_bytecode/lambda_synthesis.cpp

Lines changed: 88 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -102,58 +102,37 @@ class no_unique_unimplemented_method_exceptiont : public std::exception
102102
const std::string message;
103103
};
104104

105-
/// Find the unique abstract method that isn't equals(...) or hashCode() in a
106-
/// list of methods. Returns the method if one is found, or null if there are no
107-
/// matching methods, or throws no_unique_unimplemented_method_exceptiont if
108-
/// there are at least two.
109-
static const java_class_typet::methodt *get_unique_abstract_method(
110-
const java_class_typet::methodst &methods,
111-
const namespacet &ns)
105+
struct compare_base_name_and_descriptort
112106
{
113-
const java_class_typet::methodt *result = nullptr;
114-
for(const auto &method : methods)
107+
int operator()(
108+
const java_class_typet::methodt *a,
109+
const java_class_typet::methodt *b) const
115110
{
116-
const auto &mangled_name = method.get_name();
117-
static const irep_idt equals = "equals";
118-
static const irep_idt hashCode = "hashCode";
119-
if(method.get_base_name() == equals || method.get_base_name() == hashCode)
120-
{
121-
// equals and hashCode methods can't be the implemented method of a
122-
// functional interface, even if the interface re-declares them as
123-
// abstract.
124-
continue;
125-
}
126-
if(!ns.lookup(mangled_name).type.get_bool(ID_C_abstract))
127-
continue;
128-
if(result)
129-
{
130-
throw no_unique_unimplemented_method_exceptiont(
131-
"produces a type with at least two unimplemented methods");
132-
}
133-
result = &method;
111+
return a->get_base_name() == b->get_base_name()
112+
? (a->get_descriptor() == b->get_descriptor()
113+
? 0
114+
: a->get_descriptor() < b->get_descriptor())
115+
: a->get_base_name() < b->get_base_name();
134116
}
135-
return result;
136-
}
137-
138-
static bool same_base_name_and_descriptor(
139-
const java_class_typet::methodt &method1,
140-
const java_class_typet::methodt &method2)
141-
{
142-
return method1.get_base_name() == method2.get_base_name() &&
143-
method1.get_descriptor() == method2.get_descriptor();
144-
}
117+
};
145118

146-
/// Find the unique unimplemented method on a given interface type, including
147-
/// considering its parents. Returns the method if one is found, or empty if
148-
/// there are no unimplemented methods, or throws
149-
/// no_unique_unimplemented_method_exceptiont if there are at least two.
150-
/// If there are multiple name-and-descriptor-compatible unimplemented methods,
151-
/// for example because both If1.f(int) and If2.f(int) are unimplemented but
152-
/// due to their compatible descriptors both can be satisfied with one method,
153-
/// one of the matching methods is chosen arbitrarily.
154-
static const java_class_typet::methodt *get_unique_unimplemented_method(
155-
const irep_idt &interface_id,
156-
const namespacet &ns)
119+
/// Map from method, indexed by name and descriptor but not defining class, onto
120+
/// defined-ness (i.e. true if defined with a default method, false if abstract)
121+
typedef std::map<
122+
const java_class_typet::methodt *,
123+
bool,
124+
compare_base_name_and_descriptort>
125+
methods_by_name_and_descriptort;
126+
127+
/// Find all methods defined by this method and its parent types, returned as
128+
/// a map from const java_class_typet::methodt * onto a boolean value which is
129+
/// true if the method is defined (i.e. has a default definition) as of this
130+
/// node in the class graph.
131+
/// If there are multiple name-and-descriptor-compatible methods,
132+
/// for example because both If1.f(int) and If2.f(int) are inherited here, only
133+
/// one is stored in the map, chosen arbitrarily.
134+
static const methods_by_name_and_descriptort
135+
get_interface_methods(const irep_idt &interface_id, const namespacet &ns)
157136
{
158137
static const irep_idt jlo = "java::java.lang.Object";
159138
// Terminate recursion at Object; any other base of an interface must
@@ -170,25 +149,56 @@ static const java_class_typet::methodt *get_unique_unimplemented_method(
170149
"produces a type that inherits the stub type " + id2string(interface_id));
171150
}
172151

173-
const java_class_typet::methodt *result =
174-
get_unique_abstract_method(interface.methods(), ns);
152+
methods_by_name_and_descriptort all_methods;
175153

154+
// First accumulate definitions from child types:
176155
for(const auto &base : interface.bases())
177156
{
178-
const java_class_typet::methodt *base_result =
179-
get_unique_unimplemented_method(base.type().get_identifier(), ns);
157+
const methods_by_name_and_descriptort base_methods =
158+
get_interface_methods(base.type().get_identifier(), ns);
159+
for(const auto base_method : base_methods)
160+
{
161+
if(base_method.second)
162+
{
163+
// Any base definition fills any abstract definition from another base:
164+
all_methods[base_method.first] = true;
165+
}
166+
else
167+
{
168+
// An abstract method incoming from a base falls to any existing
169+
// definition, so only insert if not present:
170+
all_methods.emplace(base_method.first, false);
171+
}
172+
}
173+
}
174+
175+
// Now insert defintions from this class:
176+
for(const auto &method : interface.methods())
177+
{
178+
static const irep_idt equals = "equals";
179+
static const irep_idt equals_descriptor = "(Ljava/lang/Object;)Z";
180+
static const irep_idt hashCode = "hashCode";
181+
static const irep_idt hashCode_descriptor = "()I";
180182
if(
181-
base_result && result &&
182-
!same_base_name_and_descriptor(*base_result, *result))
183+
(method.get_base_name() == equals &&
184+
method.get_descriptor() == equals_descriptor) ||
185+
(method.get_base_name() == hashCode &&
186+
method.get_descriptor() == hashCode_descriptor))
183187
{
184-
throw no_unique_unimplemented_method_exceptiont(
185-
"produces a type with at least two unimplemented methods");
188+
// Ignore any uses of functions that are certainly defined on
189+
// java.lang.Object-- even if explicitly made abstract, they can't be the
190+
// implemented method of a functional interface.
191+
continue;
186192
}
187-
else if(base_result)
188-
result = base_result;
193+
194+
// Note unlike inherited definitions, an abstract definition here *does*
195+
// wipe out a non-abstract definition (i.e. a default method) from a parent
196+
// type.
197+
all_methods[&method] =
198+
!ns.lookup(method.get_name()).type.get_bool(ID_C_abstract);
189199
}
190200

191-
return result;
201+
return all_methods;
192202
}
193203

194204
static const java_class_typet::methodt *try_get_unique_unimplemented_method(
@@ -201,13 +211,29 @@ static const java_class_typet::methodt *try_get_unique_unimplemented_method(
201211
const namespacet ns{symbol_table};
202212
try
203213
{
214+
const methods_by_name_and_descriptort all_methods =
215+
get_interface_methods(functional_interface_tag.get_identifier(), ns);
216+
204217
const java_class_typet::methodt *method_and_descriptor_to_implement =
205-
get_unique_unimplemented_method(
206-
functional_interface_tag.get_identifier(), ns);
218+
nullptr;
219+
220+
for(const auto &entry : all_methods)
221+
{
222+
if(!entry.second)
223+
{
224+
if(method_and_descriptor_to_implement != nullptr)
225+
{
226+
throw no_unique_unimplemented_method_exceptiont(
227+
"produces a type with at least two unimplemented methods");
228+
}
229+
method_and_descriptor_to_implement = entry.first;
230+
}
231+
}
232+
207233
if(!method_and_descriptor_to_implement)
208234
{
209235
throw no_unique_unimplemented_method_exceptiont(
210-
"produces a type with no methods");
236+
"produces a type with no unimplemented methods");
211237
}
212238
return method_and_descriptor_to_implement;
213239
}

src/goto-symex/complexity_limiter.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ bool complexity_limitert::are_loop_children_too_complicated(
9898
// complicated, we make sure we don't execute it the other 17 times. But as
9999
// soon as we're running the loop again via a different context it gets a
100100
// chance to redeem itself.
101-
optionalt<std::reference_wrapper<lexical_loopst::loopt>> loop_to_blacklist;
101+
lexical_loopst::loopt *loop_to_blacklist = nullptr;
102102
for(auto frame_iter = current_call_stack.rbegin();
103103
frame_iter != current_call_stack.rend();
104104
++frame_iter)
@@ -121,8 +121,7 @@ bool complexity_limitert::are_loop_children_too_complicated(
121121
sum_complexity += loop_info.children_too_complex;
122122
if(sum_complexity > max_loops_complexity)
123123
{
124-
loop_to_blacklist =
125-
std::reference_wrapper<lexical_loopst::loopt>(loop_info.loop);
124+
loop_to_blacklist = &loop_info.loop;
126125
}
127126
}
128127
}

0 commit comments

Comments
 (0)