Skip to content

Fix bug in preprocessing of String.equals #1977

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified regression/jbmc-strings/StringEquals/Test.class
Binary file not shown.
48 changes: 48 additions & 0 deletions regression/jbmc-strings/StringEquals/Test.java
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// Uses CProverString, must be compiled with ../cprover/CProverString.java
import org.cprover.*;

public class Test {
public static void check(int i, String s) {
String t = "Hello World";
Expand All @@ -15,4 +18,49 @@ else if (i==4)
else if (i==5)
assert(s.equals(x));
}

public static boolean referenceImplementation(String s, Object o) {
if (! (o instanceof String))
return false;

String s2 = (String) o;
if (s.length() != s2.length())
return false;

for (int i = 0; i < s.length(); i++) {
if (CProverString.charAt(s, i) != CProverString.charAt(s2, i))
return false;
}

return true;
}

public static boolean verifyNonNull(String s, Object o) {
// Filter
if (s == null || o == null)
return false;

// Act
boolean result = s.equals(o);
boolean referenceResult = referenceImplementation(s, o);

// Assert
assert result == referenceResult;

// Return
return result;
}

public static boolean verify(String s, Object o) {
// Act
boolean result = s.equals(o);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note even this case effectively requires s != null

boolean referenceResult = referenceImplementation(s, o);

// Assert
assert result == referenceResult;

// Return
return result;
}

}
12 changes: 6 additions & 6 deletions regression/jbmc-strings/StringEquals/test.desc
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ Test.class
--refine-strings --string-max-length 40 --function Test.check
^EXIT=10$
^SIGNAL=0$
assertion at file Test.java line 6 .* SUCCESS
assertion at file Test.java line 8 .* FAILURE
assertion at file Test.java line 10 .* SUCCESS
assertion at file Test.java line 12 .* FAILURE
assertion at file Test.java line 14 .* SUCCESS
assertion at file Test.java line 16 .* FAILURE
assertion at file Test.java line 9 .* SUCCESS
assertion at file Test.java line 11 .* FAILURE
assertion at file Test.java line 13 .* SUCCESS
assertion at file Test.java line 15 .* FAILURE
assertion at file Test.java line 17 .* SUCCESS
assertion at file Test.java line 19 .* FAILURE
--
11 changes: 11 additions & 0 deletions regression/jbmc-strings/StringEquals/test_verify.desc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
KNOWNBUG
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You still seem to have left this as KNOWNBUG but your comment suggests you think this now works with --throw-runtime-exceptions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't pass on AppVeyor for some reason so I've left it as knownbug, and I will investigate a bit later.

Test.class
--refine-strings --string-max-input-length 5 --string-max-length 100 --unwind 10 --function Test.verify --java-throw-runtime-exceptions
^EXIT=0$
^SIGNAL=0$
assertion at file Test.java line 60 .* SUCCESS
--
--
null case not handled currently
TG-2179

Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CORE
Test.class
--refine-strings --string-max-input-length 20 --string-max-length 100 --unwind 30 --function Test.verifyNonNull
^EXIT=0$
^SIGNAL=0$
assertion at file Test.java line 48 .* SUCCESS
71 changes: 37 additions & 34 deletions src/java_bytecode/java_string_library_preprocess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -930,53 +930,56 @@ java_string_library_preprocesst::string_literal_to_string_expr(
/// \param symbol_table: symbol table
/// \return Code corresponding to:
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/// string_expr1 = {this->length; *this->data}
/// string_expr2 = {arg->length; *arg->data}
/// return cprover_string_equal(string_expr1, string_expr2)
/// IF arg->@class_identifier == "java.lang.String"
/// THEN
/// string_expr1 = {this->length; *this->data}
/// string_expr2 = {arg->length; *arg->data}
/// bool string_equals_tmp = cprover_string_equal(string_expr1, string_expr2)
/// return string_equals_tmp
/// ELSE
/// return false
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
codet java_string_library_preprocesst::make_equals_function_code(
const code_typet &type,
const source_locationt &loc,
symbol_table_baset &symbol_table)
{
code_blockt code;
const typet &return_type = type.return_type();
exprt::operandst ops;
for(const auto &p : type.parameters())
{
symbol_exprt sym(p.get_identifier(), p.type());
ops.push_back(sym);
}
exprt::operandst args=process_equals_function_operands(
ops, loc, symbol_table, code);

member_exprt class_identifier(
checked_dereference(ops[1], ops[1].type().subtype()),
"@class_identifier",
string_typet());

// Check the object argument is a String.
equal_exprt arg_is_string(
class_identifier, constant_exprt("java::java.lang.String", string_typet()));

// Check content equality
const symbolt string_equals_sym = get_fresh_aux_symbol(
java_boolean_type(), "string_equals", "str_eq", loc, ID_java, symbol_table);
const symbol_exprt string_equals = string_equals_sym.symbol_expr();
code.add(code_declt(string_equals), loc);
code.add(
code_assignt(
string_equals,
make_function_application(
ID_cprover_string_equal_func, args, type.return_type(), symbol_table)),
loc);

code.add(
code_returnt(
if_exprt(
arg_is_string,
string_equals,
from_integer(false, java_boolean_type()))),
loc);
code_ifthenelset code;
code.cond() = [&] {
const member_exprt class_identifier(
checked_dereference(ops[1], ops[1].type().subtype()),
"@class_identifier",
string_typet());
return equal_exprt(
class_identifier,
constant_exprt("java::java.lang.String", string_typet()));
}();

code.then_case() = [&] {
code_blockt instance_case;
// Check content equality
const symbolt string_equals_sym = get_fresh_aux_symbol(
return_type, "string_equals", "str_eq", loc, ID_java, symbol_table);
const symbol_exprt string_equals = string_equals_sym.symbol_expr();
instance_case.add(code_declt(string_equals), loc);
const exprt::operandst args =
process_equals_function_operands(ops, loc, symbol_table, instance_case);
const auto fun_app = make_function_application(
ID_cprover_string_equal_func, args, return_type, symbol_table);
instance_case.add(code_assignt(string_equals, fun_app), loc);
instance_case.add(code_returnt(string_equals), loc);
return instance_case;
}();

code.else_case() = code_returnt(from_integer(false, return_type));
return code;
}

Expand Down