Skip to content

[TG-9299] Fix crash in case of user provided clinit for a clinit which is not in the symbol table #5073

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

Conversation

romainbrenguier
Copy link
Contributor

The look-up for clinit could fail when we had a user provided clinit but the real clinit was not in the symbol table.
This adds a test which illustrate the case that was previously causing the exception.

  • Each commit message has a non-empty body, explaining why the change was made.
  • [na] Methods or procedures I have added are documented, following the guidelines provided in CODING_STANDARD.md.
  • [na] The feature or user visible behaviour I have added or modified has been documented in the User Guide in doc/cprover-manual/
  • Regression or unit tests are included, or existing tests cover the modified code (in this case I have detailed which ones those are in the commit message).
  • [na] My commit message includes data points confirming performance improvements (if claimed).
  • My PR is restricted to a single feature or bugfix.
  • White-space or formatting changes outside the feature-related changed lines are in commits of their own.

@@ -0,0 +1,3 @@
public class Empty {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rather that emptiness, is having or not having static fields the critical attribute?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I've clarified that in the class names

@@ -838,7 +838,9 @@ void assign_from_json(
location.set_function(function_id);
allocate_objectst allocate(ID_java, location, function_id, symbol_table);
code_blockt body_rec;
const auto class_name = declaring_class(symbol_table.lookup_ref(function_id));
const symbolt *function_symbol = symbol_table.lookup(function_id);
Copy link
Contributor

Choose a reason for hiding this comment

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

is lookup-ref vs. lookup-then-invariant really that different?

Copy link
Member

Choose a reason for hiding this comment

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

yes, you get a reasonable message on failure.

--
This tests what happens when the non-empty class is substitued with an empty
one after compilation: After compiling EmptyNonEmpty.class and Empty.class,
Empty.class is moved to NonEmpty.class.
Copy link
Member

Choose a reason for hiding this comment

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

What does 'moved' mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've clarified that

public class EmptyNonEmpty {

/// This test is meant to illustrate what happens when the non-empty
/// class is substitued with an empty one after compilation.
Copy link
Member

Choose a reason for hiding this comment

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

substituted

^SIGNAL=0$
--
--
This tests what happens when the non-empty class is substitued with an empty
Copy link
Member

Choose a reason for hiding this comment

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

substituted

@@ -777,6 +777,7 @@ code_blockt get_user_specified_clinit_body(
std::unordered_map<std::string, object_creation_referencet> &references)
{
jsont json;
const irep_idt real_clinit_name = clinit_function_name(class_id);
Copy link
Member

Choose a reason for hiding this comment

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

(commit message) careful

@@ -838,7 +838,9 @@ void assign_from_json(
location.set_function(function_id);
allocate_objectst allocate(ID_java, location, function_id, symbol_table);
code_blockt body_rec;
const auto class_name = declaring_class(symbol_table.lookup_ref(function_id));
const symbolt *function_symbol = symbol_table.lookup(function_id);
Copy link
Member

Choose a reason for hiding this comment

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

yes, you get a reasonable message on failure.

@@ -0,0 +1,9 @@
public class EmptyNonEmpty {
Copy link
Contributor

Choose a reason for hiding this comment

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

Change this name too

Copy link
Contributor

@allredj allredj left a comment

Choose a reason for hiding this comment

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

✔️
Passed Diffblue compatibility checks (cbmc commit: 81e1c9b).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/125753721

@codecov-io
Copy link

codecov-io commented Sep 3, 2019

Codecov Report

Merging #5073 into develop will increase coverage by <.01%.
The diff coverage is 95.65%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #5073      +/-   ##
===========================================
+ Coverage    69.67%   69.67%   +<.01%     
===========================================
  Files         1319     1319              
  Lines       109290   109298       +8     
===========================================
+ Hits         76150    76157       +7     
- Misses       33140    33141       +1
Impacted Files Coverage Δ
...bmc/src/java_bytecode/java_static_initializers.cpp 99.09% <100%> (-0.01%) ⬇️
...a_static_initializers/java_static_initializers.cpp 100% <100%> (ø) ⬆️
jbmc/src/java_bytecode/assignments_from_json.cpp 97.45% <66.66%> (-0.35%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 522ae30...1b3c289. Read the comment docs.

Copy link
Contributor

@antlechner antlechner left a comment

Choose a reason for hiding this comment

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

Is there any way to add a regression test for this? See my question below: I can't see how the bug that this is fixing would occur in practice.

@@ -838,7 +838,9 @@ void assign_from_json(
location.set_function(function_id);
allocate_objectst allocate(ID_java, location, function_id, symbol_table);
code_blockt body_rec;
const auto class_name = declaring_class(symbol_table.lookup_ref(function_id));
const symbolt *function_symbol = symbol_table.lookup(function_id);
INVARIANT(function_symbol, "Function must appear in symbol table");
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 This message is quite general, maybe include function_id in it and/or mention the context in which this was looked up.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't know, the backtrace of the invariant should already give us the context.

if(symbol_table.lookup(real_clinit_name) == nullptr)
{
// Case where there is a user provided clinit, but the real clinit
// doesn't appear in the symbol table. This may occur when some class
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Do you have an example of such a class substitution? Having these additional checks in is good of course, but I don't understand how this ever happened in practice. get_user_specified_clinit_body is only called to populate the body of a synthetic_method_typet::USER_SPECIFIED_STATIC_INITIALIZER if we see one. Such a synthetic method is only created in create_user_specified_clinit_function_symbol, which in turn is only called after several ifs, one of which is symbol_table.has_symbol(clinit_function_name(class_name)).
So it looks like the only way we could end up with a user_specified_clinit but no clinit would be if clinit was first added to the symbol table, then user_specified_clinit was added, and finally clinit was removed - but I'm not aware of the latter ever happening?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@antlechner
create_user_specified_clinit_function_symbol is called if needs_clinit_wrapper is true, which is when clinit exists or some other condition. If you think this is wrong then needs_clinit_wrapper needs to be corrected.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, that's the part I overlooked! needs_clinit_wrapper is true if clinit exists for this class or a superclass.
So it looks like the mistake is that currently, we create a user_specified_clinit iff we create a clinit_wrapper. But really we should be creating a user_specified_clinit iff there is a clinit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, I'm still going to merge this PR since it prevents a crash but the condition for calling create_user_specified_clinit_function_symbol should be fixed. Do you want to create an issue for that?

@@ -817,6 +817,13 @@ code_blockt get_user_specified_clinit_body(
}
}
}
if(symbol_table.lookup(real_clinit_name) == nullptr)
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Looks like it would make more sense to move this check just below the definition of real_clinit_name.
Edit: Ah, this is done in the next commit. Those two commits should probably be squashed.

This code doesn't need a writable reference so it should call lookup
instead.
We also add an invariant to check that the reference does exists, this
gives more helpful messages than the generic `_Map_base::at`.
The assign_from_json will fail with an invariant failure if it is called
on a method name that does not exist in the symbol table so we should be
careful when calling it.
This also make the function return early when the real clinit is not in
the symbol table, which can avoid some unecessary computation.
The declaration of the reference class_json_value prevented merging the
two if, and it can be removed without making the code more complicated
since it is just an alias for class_entry->second
Test what happens when clinit for a class is not in the symbol table
though the JSON file contains entries for this class.
This used to cause map::at to throw an exception.
Copy link
Contributor

@allredj allredj left a comment

Choose a reason for hiding this comment

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

✔️
Passed Diffblue compatibility checks (cbmc commit: 1b3c289).
Build URL: https://travis-ci.com/diffblue/test-gen/builds/126241524

@romainbrenguier romainbrenguier merged commit a60f45c into diffblue:develop Sep 6, 2019
@romainbrenguier romainbrenguier deleted the bugfix/map-base-at branch September 6, 2019 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants