diff --git a/src/java_bytecode/java_bytecode_convert_method_class.h b/src/java_bytecode/java_bytecode_convert_method_class.h index aa3a008faaf..91995fcc0cf 100644 --- a/src/java_bytecode/java_bytecode_convert_method_class.h +++ b/src/java_bytecode/java_bytecode_convert_method_class.h @@ -250,8 +250,6 @@ class java_bytecode_convert_methodt:public messaget const bytecode_infot &get_bytecode_info(const irep_idt &statement); - bool class_needs_clinit(const irep_idt &classname); - exprt get_or_create_clinit_wrapper(const irep_idt &classname); codet get_clinit_call(const irep_idt &classname); bool is_method_inherited( diff --git a/unit/Makefile b/unit/Makefile index c3872f7a98b..1157bb72db0 100644 --- a/unit/Makefile +++ b/unit/Makefile @@ -21,6 +21,7 @@ SRC += unit_tests.cpp \ goto-programs/class_hierarchy_graph.cpp \ goto-programs/remove_virtual_functions_without_fallback.cpp \ java_bytecode/java_bytecode_convert_class/convert_abstract_class.cpp \ + java_bytecode/java_bytecode_convert_method/convert_invoke_dynamic.cpp \ java_bytecode/java_bytecode_parse_generics/parse_generic_class.cpp \ java_bytecode/java_object_factory/gen_nondet_string_init.cpp \ java_bytecode/java_bytecode_parse_lambdas/java_bytecode_parse_lambda_method_table.cpp \ diff --git a/unit/java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp b/unit/java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp index e83fa65af09..3c44a323eee 100644 --- a/unit/java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp +++ b/unit/java_bytecode/ci_lazy_methods/lazy_load_lambdas.cpp @@ -18,7 +18,8 @@ SCENARIO( { const symbol_tablet symbol_table = load_java_class_lazy( "LocalLambdas", - "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + "openjdk_8_classes", "LocalLambdas.test"); THEN("Then the lambdas should be loaded") @@ -68,7 +69,8 @@ SCENARIO( { const symbol_tablet symbol_table = load_java_class_lazy( "MemberLambdas", - "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + "openjdk_8_classes", "MemberLambdas.test"); THEN("Then the lambdas should be loaded") @@ -117,7 +119,8 @@ SCENARIO( { const symbol_tablet symbol_table = load_java_class_lazy( "StaticLambdas", - "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + "openjdk_8_classes", "StaticLambdas.test"); THEN("Then the lambdas should be loaded") @@ -166,7 +169,8 @@ SCENARIO( { const symbol_tablet symbol_table = load_java_class_lazy( "OuterMemberLambdas$Inner", - "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + "openjdk_8_classes", "OuterMemberLambdas$Inner.test"); THEN("Then the lambdas should be loaded") @@ -192,7 +196,8 @@ SCENARIO( { const symbol_tablet symbol_table = load_java_class_lazy( "ExternalLambdaAccessor", - "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + "openjdk_8_classes", "ExternalLambdaAccessor.test"); THEN("Then the lambdas should be loaded") diff --git a/unit/java_bytecode/java_bytecode_convert_method/convert_invoke_dynamic.cpp b/unit/java_bytecode/java_bytecode_convert_method/convert_invoke_dynamic.cpp new file mode 100644 index 00000000000..57233930ac7 --- /dev/null +++ b/unit/java_bytecode/java_bytecode_convert_method/convert_invoke_dynamic.cpp @@ -0,0 +1,699 @@ +/*******************************************************************\ + + Module: Unit tests for converting invokedynamic instructions into codet + + Author: Diffblue Ltd. + +\*******************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct lambda_assignment_test_datat +{ + irep_idt lambda_interface; + std::string lambda_interface_method_descriptor; + irep_idt lambda_function_id; + + std::vector expected_params; + bool should_return_value = true; +}; + +/// Verifies for a given function that contains: +/// {lambda_variable_id} = invokedynamic method_handle_index +/// code looks +/// like: +/// tmp_object_symbol = java_new(lambda_tmp_type) +/// {lambda_variable_id} = (cast)tmp_object_symbol +/// and the type lambda_tmp_type: +/// implements {lambda_interface} +/// has method {lambda_interface_method_descriptor} +/// and the method body looks like +/// function_call_exprt( +/// should_return_value ? symbol_exprt : nil_exprt, +/// {lambda_function_id}, +/// {expected_params}) +/// \param symbol_table: The loaded symbol table +/// \param instructions: The instructions of the method that calls invokedynamic +/// \param test_data: The parameters for the test +/// \param lambda_assignment: The assignment statement for the lambda method +void validate_lambda_assignment( + const symbol_tablet &symbol_table, + const std::vector &instructions, + const lambda_assignment_test_datat &test_data, + const code_assignt &lambda_assignment) +{ + const typecast_exprt &rhs_value = + require_expr::require_typecast(lambda_assignment.rhs()); + + const symbol_exprt &rhs_symbol = + require_expr::require_symbol(rhs_value.op0()); + + const irep_idt &tmp_object_symbol = rhs_symbol.get_identifier(); + + const auto tmp_object_assignments = + require_goto_statements::find_pointer_assignments( + tmp_object_symbol, instructions); + + REQUIRE(tmp_object_assignments.non_null_assignments.size() == 1); + REQUIRE_FALSE(tmp_object_assignments.null_assignment.has_value()); + + const side_effect_exprt &side_effect_expr = + require_expr::require_side_effect_expr( + tmp_object_assignments.non_null_assignments[0].rhs(), ID_java_new); + + const pointer_typet &lambda_temp_type = + require_type::require_pointer(side_effect_expr.type(), {}); + + const symbol_typet &lambda_implementor_type = + require_type::require_symbol(lambda_temp_type.subtype()); + + const irep_idt &tmp_class_identifier = + lambda_implementor_type.get_identifier(); + + const symbolt &lambda_implementor_type_symbol = + require_symbol::require_symbol_exists(symbol_table, tmp_class_identifier); + + REQUIRE(lambda_implementor_type_symbol.is_type); + const class_typet &tmp_lambda_class_type = + require_type::require_complete_class(lambda_implementor_type_symbol.type); + + REQUIRE(tmp_lambda_class_type.has_base("java::java.lang.Object")); + REQUIRE(tmp_lambda_class_type.has_base(test_data.lambda_interface)); + + class_hierarchyt class_hierarchy; + class_hierarchy(symbol_table); + + const auto &parents = class_hierarchy.get_parents_trans(tmp_class_identifier); + REQUIRE_THAT( + parents, + // NOLINTNEXTLINE(whitespace/braces) + Catch::Matchers::Vector::ContainsElementMatcher{ + test_data.lambda_interface}); + + const auto &interface_children = + class_hierarchy.get_children_trans(test_data.lambda_interface); + + REQUIRE_THAT( + interface_children, + // NOLINTNEXTLINE(whitespace/braces) + Catch::Matchers::Vector::ContainsElementMatcher{ + tmp_class_identifier}); + + const java_class_typet::componentt super_class_component = + require_type::require_component(tmp_lambda_class_type, "@java.lang.Object"); + + const symbol_typet &super_class_type = require_type::require_symbol( + super_class_component.type(), "java::java.lang.Object"); + + THEN("The function in the class should call the lambda method") + { + const irep_idt method_identifier = + id2string(tmp_class_identifier) + + test_data.lambda_interface_method_descriptor; + const symbolt &method_symbol = + require_symbol::require_symbol_exists(symbol_table, method_identifier); + + REQUIRE(method_symbol.is_function()); + + const std::vector &assignments = + require_goto_statements::get_all_statements(method_symbol.value); + + std::vector function_calls = + require_goto_statements::find_function_calls( + assignments, test_data.lambda_function_id); + + INFO("Looking for function call of " << test_data.lambda_function_id); + REQUIRE(function_calls.size() == 1); + const code_function_callt &function_call = function_calls[0]; + + std::string variable_prefix = id2string(method_identifier) + "::"; + // replace all symbol exprs with a prefixed symbol expr + std::vector expected_args = test_data.expected_params; + for(exprt &arg : expected_args) + { + for(auto it = arg.depth_begin(); it != arg.depth_end(); ++it) + { + if(it->id() == ID_symbol) + { + symbol_exprt &symbol_expr = to_symbol_expr(it.mutate()); + const irep_idt simple_id = symbol_expr.get_identifier(); + symbol_expr.set_identifier(variable_prefix + id2string(simple_id)); + } + } + } + + REQUIRE_THAT( + function_call.arguments(), + Catch::Matchers::Vector::EqualsMatcher{expected_args}); + + if(test_data.should_return_value) + { + require_expr::require_symbol(function_call.lhs()); + } + else + { + REQUIRE(function_call.lhs().is_nil()); + } + } +} + +/// Find the assignment to the lambda and then call validate_lamdba_assignement +/// for full validation. +/// \param symbol_table: The loaded symbol table +/// \param instructions: The instructions of the method that calls invokedynamic +/// \param test_data: The parameters for the test +/// \param lambda_variable_id: A regex matching the name of the variable which +/// stores the lambda +void validate_local_variable_lambda_assignment( + const symbol_tablet &symbol_table, + const std::vector &instructions, + const lambda_assignment_test_datat &test_data, + const std::regex lambda_variable_id) +{ + const auto lambda_assignment = + require_goto_statements::find_pointer_assignments( + lambda_variable_id, instructions); + + REQUIRE(lambda_assignment.non_null_assignments.size() == 1); + REQUIRE_FALSE(lambda_assignment.null_assignment.has_value()); + + validate_lambda_assignment( + symbol_table, + instructions, + test_data, + lambda_assignment.non_null_assignments[0]); +} + +SCENARIO( + "Converting invokedynamic with a local lambda", + "[core]" + "[lambdas][java_bytecode][java_bytecode_convert_method][!mayfail]") +{ + // NOLINTNEXTLINE(whitespace/braces) + run_test_with_compilers([](const std::string &compiler) { + GIVEN( + "A class with a static lambda variables from " + compiler + " compiler.") + { + symbol_tablet symbol_table = load_java_class( + "LocalLambdas", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + + compiler + "_classes/", + "LocalLambdas.test"); + + WHEN("Inspecting the assignments of the entry function") + { + const std::vector &instructions = + require_goto_statements::get_all_statements( + symbol_table.lookup_ref("java::LocalLambdas.test:()V").value); + + const std::string function_prefix_regex_str = + R"(java::LocalLambdas\.test:\(\)V)"; + + THEN( + "The local variable should be assigned a temp object implementing " + "SimpleLambda") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::SimpleLambda"; + test_data.lambda_interface_method_descriptor = ".Execute:()V"; + test_data.lambda_function_id = "java::LocalLambdas.pretendLambda:()V"; + test_data.expected_params = {}; + test_data.should_return_value = false; + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex(function_prefix_regex_str + R"(::\d+::simpleLambda$)")); + } + THEN( + "The local variable should be assigned a non-null pointer to a " + "parameter interface implementor") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:(ILjava/lang/Object;LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::LocalLambdas.lambda$test$1:(ILjava/lang/" + "Object;LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_int_type()}; + symbol_exprt ref_param{"reference", + java_type_from_string("Ljava/lang/Object;")}; + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + test_data.should_return_value = false; + + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex(function_prefix_regex_str + R"(::\d+::paramLambda$)")); + } + THEN( + "The local variable should be assigned a non-null pointer to an " + "array parameter interface implementor") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ArrayParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:([I[Ljava/lang/Object;[LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::LocalLambdas.lambda$test$2:" + "[I[Ljava/lang/Object;[LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_type_from_string("[I")}; + symbol_exprt ref_param{"reference", + java_type_from_string("[Ljava/lang/Object;")}; + + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("[LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + test_data.should_return_value = false; + + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex( + function_prefix_regex_str + R"(::\d+::arrayParamLambda$)")); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaPrimitive") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaPrimitive"; + test_data.lambda_interface_method_descriptor = ".Execute:()I"; + test_data.lambda_function_id = "java::LocalLambdas.lambda$test$3:()I"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex( + function_prefix_regex_str + R"(::\d+::returnPrimitiveLambda$)")); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaReference") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaReference"; + + test_data.lambda_interface_method_descriptor = + ".Execute:()Ljava/lang/Object;"; + + test_data.lambda_function_id = + "java::LocalLambdas.lambda$test$4:()Ljava/lang/Object;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex( + function_prefix_regex_str + R"(::\d+::returnReferenceLambda$)")); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaSpecalisedGeneric") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaSpecalisedGeneric"; + + test_data.lambda_interface_method_descriptor = + ".Execute:()LDummyGeneric;"; + test_data.lambda_function_id = + "java::LocalLambdas.lambda$test$5:()LDummyGeneric;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_local_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + std::regex( + function_prefix_regex_str + + R"(::\d+::returningSpecalisedGenericLambda$)")); + } + // TODO(tkiley): Tests for local lambdas that capture + // TODO(tkiley): variables [TG-2482] + } + } + }); +} + +/// Find the assignment to the lambda in the constructor +/// and then call validate_lamdba_assignement for full validation. +/// \param symbol_table: The loaded symbol table +/// \param instructions: The instructions of the method that calls invokedynamic +/// \param test_data: The parameters for the test +/// \param lambda_variable_id: The name of the member variable that stores the +/// lambda +void validate_member_variable_lambda_assignment( + const symbol_tablet &symbol_table, + const std::vector &instructions, + const lambda_assignment_test_datat &test_data, + const std::string lambda_variable_id) +{ + const auto lambda_assignment = + require_goto_statements::find_this_component_assignment( + instructions, lambda_variable_id); + + REQUIRE(lambda_assignment.non_null_assignments.size() == 1); + REQUIRE_FALSE(lambda_assignment.null_assignment.has_value()); + + validate_lambda_assignment( + symbol_table, + instructions, + test_data, + lambda_assignment.non_null_assignments[0]); +} + +SCENARIO( + "Converting invokedynamic with a member lambda", + "[core]" + "[lamdba][java_bytecode][java_bytecode_convert_method][!mayfail]") +{ + // NOLINTNEXTLINE(whitespace/braces) + run_test_with_compilers([](const std::string &compiler) { + GIVEN( + "A class with a static lambda variables from " + compiler + " compiler.") + { + symbol_tablet symbol_table = load_java_class( + "MemberLambdas", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + + compiler + "_classes/", + "MemberLambdas."); + + WHEN("Inspecting the assignments of the entry function") + { + const std::vector &instructions = + require_goto_statements::get_all_statements( + symbol_table.lookup_ref("java::MemberLambdas.:()V").value); + + const std::string function_prefix_regex_str = "java::MemberLambdas"; + + THEN( + "The local variable should be assigned a temp object implementing " + "SimpleLambda") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::SimpleLambda"; + test_data.lambda_interface_method_descriptor = ".Execute:()V"; + test_data.lambda_function_id = "java::MemberLambdas.lambda$new$0:()V"; + test_data.expected_params = {}; + test_data.should_return_value = false; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "simpleLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ParameterLambda") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:(ILjava/lang/Object;LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::MemberLambdas.lambda$new$1:" + "(ILjava/lang/Object;LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_int_type()}; + symbol_exprt ref_param{"reference", // NOLINT(whitespace/braces) + java_type_from_string("Ljava/lang/Object;")}; + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + + test_data.should_return_value = false; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "paramLambda"); + } + THEN( + "The local variable should be assigned a non-null pointer to an " + "array parameter interface implementor") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ArrayParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:([I[Ljava/lang/Object;[LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::MemberLambdas.lambda$new$2:" + "([I[Ljava/lang/Object;[LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_type_from_string("[I")}; + symbol_exprt ref_param{"reference", // NOLINT(whitespace/braces) + java_type_from_string("[Ljava/lang/Object;")}; + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("[LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + test_data.should_return_value = false; + + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "arrayParamLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaPrimitive") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaPrimitive"; + test_data.lambda_interface_method_descriptor = ".Execute:()I"; + test_data.lambda_function_id = "java::MemberLambdas.lambda$new$3:()I"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "returnPrimitiveLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaReference") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaReference"; + test_data.lambda_interface_method_descriptor = + ".Execute:()Ljava/lang/Object;"; + test_data.lambda_function_id = + "java::MemberLambdas.lambda$new$4:()Ljava/lang/Object;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "returnReferenceLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaSpecalisedGeneric") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaSpecalisedGeneric"; + test_data.lambda_interface_method_descriptor = + ".Execute:()LDummyGeneric;"; + test_data.lambda_function_id = + "java::MemberLambdas.lambda$new$5:()LDummyGeneric;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + "returningSpecalisedGenericLambda"); + } + + // TODO(tkiley): Tests for member lambdas that capture member variables + // TODO(tkiley): [TG-2486] + } + } + }); +} + +/// Find the assignment to the lambda in the and then call +/// validate_lamdba_assignement for full validation. +/// \param symbol_table: The loaded symbol table +/// \param instructions: The instructions of the method that calls invokedynamic +/// \param test_data: The parameters for the test +/// \param static_field_name: The name of the static variable that stores the +/// lambda +void validate_static_member_variable_lambda_assignment( + const symbol_tablet &symbol_table, + const std::vector &instructions, + const lambda_assignment_test_datat &test_data, + const std::string static_field_name) +{ + const auto lambda_assignment = + require_goto_statements::find_pointer_assignments( + static_field_name, instructions); + + REQUIRE(lambda_assignment.non_null_assignments.size() == 1); + REQUIRE_FALSE(lambda_assignment.null_assignment.has_value()); + + validate_lambda_assignment( + symbol_table, + instructions, + test_data, + lambda_assignment.non_null_assignments[0]); +} + +SCENARIO( + "Converting invokedynamic with a static member lambda", + "[core]" + "[lamdba][java_bytecode][java_bytecode_convert_method][!mayfail]") +{ + // NOLINTNEXTLINE(whitespace/braces) + run_test_with_compilers([](const std::string &compiler) { + GIVEN( + "A class with a static lambda variables from " + compiler + " compiler.") + { + symbol_tablet symbol_table = load_java_class( + "StaticLambdas", + "./java_bytecode/java_bytecode_parse_lambdas/lambda_examples/" + + compiler + "_classes/", + "StaticLambdas."); + + WHEN("Inspecting the assignments of the entry function") + { + const std::vector &instructions = + require_goto_statements::get_all_statements( + symbol_table.lookup_ref("java::StaticLambdas.:()V").value); + + const std::string function_prefix_regex_str = "java::StaticLambdas"; + + THEN( + "The local variable should be assigned a temp object implementing " + "SimpleLambda") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::SimpleLambda"; + test_data.lambda_interface_method_descriptor = ".Execute:()V"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$0:()V"; + test_data.expected_params = {}; + test_data.should_return_value = false; + validate_static_member_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + function_prefix_regex_str + ".simpleLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ParameterLambda") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:(ILjava/lang/Object;LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$1:" + "(ILjava/lang/Object;LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_int_type()}; + symbol_exprt ref_param{"reference", + java_type_from_string("Ljava/lang/Object;")}; + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + + test_data.should_return_value = false; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "paramLambda"); + } + THEN( + "The local variable should be assigned a non-null pointer to an " + "array parameter interface implementor") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ArrayParameterLambda"; + test_data.lambda_interface_method_descriptor = + ".Execute:([I[Ljava/lang/Object;[LDummyGeneric;)V"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$2:" + "([I[Ljava/lang/Object;[LDummyGeneric;)V"; + + symbol_exprt integer_param{"primitive", java_type_from_string("[I")}; + symbol_exprt ref_param{"reference", + java_type_from_string("[Ljava/lang/Object;")}; + // NOLINTNEXTLINE(whitespace/braces) + symbol_exprt generic_param{ + "specalisedGeneric", + java_type_from_string("[LDummyGeneric;")}; + test_data.expected_params = {integer_param, ref_param, generic_param}; + test_data.should_return_value = false; + + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "arrayParamLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaPrimitive") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaPrimitive"; + test_data.lambda_interface_method_descriptor = ".Execute:()I"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$3:()I"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "returnPrimitiveLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaReference") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaReference"; + test_data.lambda_interface_method_descriptor = + ".Execute:()Ljava/lang/Object;"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$4:()Ljava/lang/Object;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, instructions, test_data, "returnReferenceLambda"); + } + THEN( + "The local variable should be assigned a temp object implementing " + "ReturningLambdaSpecalisedGeneric") + { + lambda_assignment_test_datat test_data; + test_data.lambda_interface = "java::ReturningLambdaSpecalisedGeneric"; + test_data.lambda_interface_method_descriptor = + ".Execute:()LDummyGeneric;"; + test_data.lambda_function_id = + "java::StaticLambdas.lambda$static$5:()LDummyGeneric;"; + test_data.expected_params = {}; + test_data.should_return_value = true; + validate_member_variable_lambda_assignment( + symbol_table, + instructions, + test_data, + "returningSpecalisedGenericLambda"); + } + + // TODO(tkiley): Tests for member lambdas that capture member variables + // TODO(tkiley): [TG-2486] + } + } + }); +} diff --git a/unit/testing-utils/load_java_class.cpp b/unit/testing-utils/load_java_class.cpp index c8da44814cc..f198008dff4 100644 --- a/unit/testing-utils/load_java_class.cpp +++ b/unit/testing-utils/load_java_class.cpp @@ -17,6 +17,7 @@ #include #include +#include /// Go through the process of loading, type-checking and finalising loading a /// specific class file to build the symbol table. The functions are converted @@ -128,6 +129,12 @@ symbol_tablet load_java_class( const typet &class_type=class_symbol.type; REQUIRE(class_type.id()==ID_struct); + // Log the working directory to help people identify the common error + // of wrong working directory (should be the `unit` directory when running + // the unit tests). + std::string path = get_current_working_directory(); + INFO("Working directory: " << path); + // if this fails it indicates the class was not loaded // Check your working directory and the class path is correctly configured // as this often indicates that one of these is wrong. diff --git a/unit/testing-utils/require_expr.cpp b/unit/testing-utils/require_expr.cpp index b4ad9d470c6..c338293f3d2 100644 --- a/unit/testing-utils/require_expr.cpp +++ b/unit/testing-utils/require_expr.cpp @@ -16,6 +16,7 @@ #include #include +#include /// Verify a given exprt is an index_exprt with a a constant value equal to the /// expected value @@ -69,8 +70,39 @@ member_exprt require_expr::require_member( symbol_exprt require_expr::require_symbol( const exprt &expr, const irep_idt &symbol_name) { - REQUIRE(expr.id()==ID_symbol); - const symbol_exprt &symbol_expr=to_symbol_expr(expr); + const symbol_exprt &symbol_expr = require_symbol(expr); REQUIRE(symbol_expr.get_identifier()==symbol_name); return symbol_expr; } + +/// Verify a given exprt is a symbol_exprt. +/// \param expr: The expression. +/// \return The expr cast to a symbol_exprt +symbol_exprt require_expr::require_symbol(const exprt &expr) +{ + REQUIRE(expr.id() == ID_symbol); + return to_symbol_expr(expr); +} + +/// Verify a given exprt is a typecast_expr. +/// \param expr: The expression. +/// \return The expr cast to a typecast_exprt +typecast_exprt require_expr::require_typecast(const exprt &expr) +{ + REQUIRE(expr.id() == ID_typecast); + return to_typecast_expr(expr); +} + +/// Verify a given exprt is a side_effect_exprt with appropriate statement. +/// \param expr: The expression. +/// \param symbol_name: The intended identifier of statement +/// \return The expr cast to a side_effect_exprt +side_effect_exprt require_expr::require_side_effect_expr( + const exprt &expr, + const irep_idt &side_effect_statement) +{ + REQUIRE(expr.id() == ID_side_effect); + const side_effect_exprt &side_effect_expr = to_side_effect_expr(expr); + REQUIRE(side_effect_expr.get_statement() == side_effect_statement); + return side_effect_expr; +} diff --git a/unit/testing-utils/require_expr.h b/unit/testing-utils/require_expr.h index 6e545e90a3a..b663578a94c 100644 --- a/unit/testing-utils/require_expr.h +++ b/unit/testing-utils/require_expr.h @@ -16,6 +16,7 @@ #define CPROVER_TESTING_UTILS_REQUIRE_EXPR_H #include +#include // NOLINTNEXTLINE(readability/namespace) namespace require_expr @@ -28,6 +29,14 @@ namespace require_expr symbol_exprt require_symbol( const exprt &expr, const irep_idt &symbol_name); + + symbol_exprt require_symbol(const exprt &expr); + + typecast_exprt require_typecast(const exprt &expr); + + side_effect_exprt require_side_effect_expr( + const exprt &expr, + const irep_idt &side_effect_statement); } #endif // CPROVER_TESTING_UTILS_REQUIRE_EXPR_H diff --git a/unit/testing-utils/require_goto_statements.cpp b/unit/testing-utils/require_goto_statements.cpp index 7a2974bb827..2b265c99736 100644 --- a/unit/testing-utils/require_goto_statements.cpp +++ b/unit/testing-utils/require_goto_statements.cpp @@ -13,6 +13,7 @@ #include #include #include +#include /// Expand value of a function to include all child codets /// \param function_value: The value of the function (e.g. got by looking up @@ -141,6 +142,53 @@ require_goto_statements::find_struct_component_assignments( return locations; } +/// Find assignment statements that set this->{component_name} +/// \param statements The statements to look through +/// \param component_name The name of the component whose assignments we are +/// looking for. +/// \return A collection of all non-null assignments to this component +/// and, if present, a null assignment. +require_goto_statements::pointer_assignment_locationt +require_goto_statements::find_this_component_assignment( + const std::vector &statements, + const irep_idt &component_name) +{ + pointer_assignment_locationt locations; + + for(const auto &assignment : statements) + { + if(assignment.get_statement() == ID_assign) + { + const code_assignt &code_assign = to_code_assign(assignment); + + if(code_assign.lhs().id() == ID_member) + { + const member_exprt &member_expr = to_member_expr(code_assign.lhs()); + if( + member_expr.get_component_name() == component_name && + member_expr.op().id() == ID_dereference && + member_expr.op().op0().id() == ID_symbol && + has_suffix( + id2string(to_symbol_expr(member_expr.op().op0()).get_identifier()), + "this")) + { + if( + code_assign.rhs() == + null_pointer_exprt(to_pointer_type(code_assign.lhs().type()))) + { + locations.null_assignment = code_assign; + } + else + { + locations.non_null_assignments.push_back(code_assign); + } + } + } + } + } + return locations; +} + /// For a given variable name, gets the assignments to it in the provided /// instructions. /// \param pointer_name: The name of the variable @@ -151,31 +199,60 @@ require_goto_statements::pointer_assignment_locationt require_goto_statements::find_pointer_assignments( const irep_idt &pointer_name, const std::vector &instructions) +{ + INFO("Looking for symbol: " << pointer_name); + std::regex special_chars{R"([-[\]{}()*+?.,\^$|#\s])"}; + std::string sanitized = + std::regex_replace(id2string(pointer_name), special_chars, R"(\$&)"); + return find_pointer_assignments( + std::regex("^" + sanitized + "$"), instructions); +} + +require_goto_statements::pointer_assignment_locationt +require_goto_statements::find_pointer_assignments( + const std::regex &pointer_name_match, + const std::vector &instructions) { pointer_assignment_locationt locations; + bool found_assignment = false; + std::vector all_symbols; for(const codet &statement : instructions) { if(statement.get_statement() == ID_assign) { const code_assignt &code_assign = to_code_assign(statement); - if( - code_assign.lhs().id() == ID_symbol && - to_symbol_expr(code_assign.lhs()).get_identifier() == pointer_name) + if(code_assign.lhs().id() == ID_symbol) { + const symbol_exprt &symbol_expr = to_symbol_expr(code_assign.lhs()); + all_symbols.push_back(symbol_expr.get_identifier()); if( - code_assign.rhs() == - null_pointer_exprt(to_pointer_type(code_assign.lhs().type()))) - { - locations.null_assignment = code_assign; - } - else + std::regex_search( + id2string(symbol_expr.get_identifier()), pointer_name_match)) { - locations.non_null_assignments.push_back(code_assign); + if( + code_assign.rhs() == + null_pointer_exprt(to_pointer_type(code_assign.lhs().type()))) + { + locations.null_assignment = code_assign; + } + else + { + locations.non_null_assignments.push_back(code_assign); + } + found_assignment = true; } } } } + std::ostringstream found_symbols; + for(const auto entry : all_symbols) + { + found_symbols << entry << std::endl; + } + INFO("Symbols: " << found_symbols.str()); + REQUIRE(found_assignment); + return locations; } @@ -395,3 +472,35 @@ require_goto_statements::require_entry_point_argument_assignment( .get_identifier(); return argument_tmp_name; } + +/// Verify that a collection of statements contains a function call to a +/// function whose symbol identifier matches the provided identifier +/// \param statements: The collection of statements to inspect +/// \param function_call_identifier: The symbol identifier of the function +/// that should have been called +/// \return All calls to the matching function inside the statements +std::vector require_goto_statements::find_function_calls( + const std::vector &statements, + const irep_idt &function_call_identifier) +{ + std::vector function_calls; + for(const codet &statement : statements) + { + if(statement.get_statement() == ID_function_call) + { + const code_function_callt &function_call = + to_code_function_call(statement); + + if(function_call.function().id() == ID_symbol) + { + if( + to_symbol_expr(function_call.function()).get_identifier() == + function_call_identifier) + { + function_calls.push_back(function_call); + } + } + } + } + return function_calls; +} diff --git a/unit/testing-utils/require_goto_statements.h b/unit/testing-utils/require_goto_statements.h index f2767b73b55..c8bc8ca40ff 100644 --- a/unit/testing-utils/require_goto_statements.h +++ b/unit/testing-utils/require_goto_statements.h @@ -16,6 +16,8 @@ #include #include +#include + #ifndef CPROVER_TESTING_UTILS_REQUIRE_GOTO_STATEMENTS_H #define CPROVER_TESTING_UTILS_REQUIRE_GOTO_STATEMENTS_H @@ -53,6 +55,10 @@ pointer_assignment_locationt find_struct_component_assignments( const optionalt &superclass_name, const irep_idt &component_name); +pointer_assignment_locationt find_this_component_assignment( + const std::vector &statements, + const irep_idt &component_name); + std::vector get_all_statements(const exprt &function_value); const std::vector @@ -62,6 +68,10 @@ pointer_assignment_locationt find_pointer_assignments( const irep_idt &pointer_name, const std::vector &instructions); +pointer_assignment_locationt find_pointer_assignments( + const std::regex &pointer_name_match, + const std::vector &instructions); + const code_declt &require_declaration_of_name( const irep_idt &variable_name, const std::vector &entry_point_instructions); @@ -85,6 +95,10 @@ const irep_idt &require_struct_array_component_assignment( const irep_idt &require_entry_point_argument_assignment( const irep_idt &argument_name, const std::vector &entry_point_statements); + +std::vector find_function_calls( + const std::vector &statements, + const irep_idt &function_call_identifier); } #endif // CPROVER_TESTING_UTILS_REQUIRE_GOTO_STATEMENTS_H