Skip to content

Conversation

@NlightNFotis
Copy link
Contributor

This is a port of #1767 to develop (instead of goto-analyzer-develop as requested by @peterschrammel and @martin-cs . Below is the message of the original PR:

There has been a bug reported to us where cbmc would run out of memory while creating a huge json object in memory to report the traces with. This is changing the tracing report facility to use a new json class that streams the objects immediately as created, and therefore fixes the issue.

Copy link
Member

Choose a reason for hiding this comment

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

This can be removed now.

Copy link
Member

Choose a reason for hiding this comment

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

The commit message isn't very useful.

Copy link
Member

Choose a reason for hiding this comment

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

This change is in the wrong commit.

{
jsont &json_trace=result["trace"];
convert(bmc.ns, g.second.goto_trace, json_trace, bmc.trace_options());
json_stream_arrayt &json_trace=result.push_back_stream_array("trace");
Copy link
Member

Choose a reason for hiding this comment

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

(commit for entire commit) This commit should come after the json_goto_trace refactoring.

}
}

void jsont::output_object(std::ostream &out, const objectt &object, unsigned indent)
Copy link
Member

Choose a reason for hiding this comment

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

Some doxygen would be good (sorry that I threw that over to you without docs).

Copy link
Member

Choose a reason for hiding this comment

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

this should go into the commit where this file was introduced

Copy link
Member

Choose a reason for hiding this comment

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

these changes should go into the first commit

Copy link
Member

Choose a reason for hiding this comment

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

This seems still necessary as XML output hasn't been moved an xml_stream yet.

Copy link
Member

Choose a reason for hiding this comment

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

This include should come first.

Copy link
Member

Choose a reason for hiding this comment

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

Unclear why this file is modified in this commit.

Copy link
Member

Choose a reason for hiding this comment

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

(comment for entire commit) These changes should be squashed with the previous commit.

Copy link
Member

Choose a reason for hiding this comment

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

I suggest to improve the commit message.

src/util/json.h Outdated
Copy link
Member

Choose a reason for hiding this comment

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

unclear why this line is removed

Copy link
Member

Choose a reason for hiding this comment

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

there should be some doxygen for all this

Copy link
Member

Choose a reason for hiding this comment

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

doxygen

Copy link
Member

Choose a reason for hiding this comment

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

The changes to this file should be in the previous commit.

Copy link
Member

Choose a reason for hiding this comment

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

doxygen

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is there a json_stream_arrayt variant, but no json_stream_objectt one?

Copy link
Member

Choose a reason for hiding this comment

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

this include should be the first

Copy link
Member

Choose a reason for hiding this comment

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

these changes should go into the first commit

Copy link
Member

Choose a reason for hiding this comment

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

why is this non-const?

Copy link
Member

Choose a reason for hiding this comment

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

doxygen missing throughout these files

Copy link
Member

Choose a reason for hiding this comment

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

why is this non-const?

@peterschrammel
Copy link
Member

@NlightNFotis, there's a compiliation error: https://travis-ci.org/diffblue/cbmc/jobs/339128397#L1550

@peterschrammel
Copy link
Member

Also, cpplint and clang-format are complaining. Be careful to reformat the changes in each commit separately.

Copy link
Member

Choose a reason for hiding this comment

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

There are a couple of header files that can be put into the cpp file.

@NlightNFotis NlightNFotis force-pushed the flush-json-stream branch 3 times, most recently from 268a8f5 to 7c8ea18 Compare February 9, 2018 17:41
@NlightNFotis
Copy link
Contributor Author

This should be ready to review, this only needs the clang format changes to be squashed to their appropriate commits.

Copy link
Collaborator

@tautschnig tautschnig left a comment

Choose a reason for hiding this comment

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

The PR talks about fixing an out-of-memory problem, but then in the middle of the PR suddenly there is a refactoring commit as well. Lots of seemingly unnecessary whitespace changes. All of that makes reviewing extremely hard.

My main concern, however, is the core API that is not at all self-documenting. I get the problem that it is trying to solve, but it's very hard to tell whether that solution is actually fit for purpose. See details in my comments.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn't the newline also be conditional on o_it!=object.beginn()?

Copy link
Member

Choose a reason for hiding this comment

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

No, because an object always starts with an opening brace, after which we put a newline (code snippet factored out from lines 69-85 above).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not a big deal, but out << "\": "; would also do the trick, wouldn't it?

src/util/json.h Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remote friends are bad.

Copy link
Member

Choose a reason for hiding this comment

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

Seems unnecessary to have them.

Copy link
Collaborator

Choose a reason for hiding this comment

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

To be honest, that's a very strange piece of code. It seems to be extremely context-dependent and far from side-effect free.

Copy link
Collaborator

Choose a reason for hiding this comment

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

The constructor does out << '[' so I'd expect this to be done in a destructor (otherwise the constructor shouldn't be doing the opening bracket).

Copy link
Member

Choose a reason for hiding this comment

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

This is exactly what it is doing (by calling close() which, in turn, calls output_finalizer).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit pick: no comma before "that"

Copy link
Collaborator

Choose a reason for hiding this comment

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

The only public operation is close - that's truly weird. Please create a meaningful API, which might have virtual ...(...) = 0; members - but at least it would explain how to use this thingy.

Copy link
Member

Choose a reason for hiding this comment

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

The only public facility that this base class provides is close().
output_current and output_finalizer should be made abstract.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think this has happened? Maybe it has, but they are all protected so I have no idea what is the public interface of (children of) json_streamt.

Copy link
Collaborator

Choose a reason for hiding this comment

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

How does this fit in a "stream" class?!

Copy link
Member

Choose a reason for hiding this comment

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

It allows mixing streamed and non-streamed json elements. The API enforces that the parent of a streamed element must always be a streamed element.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does it even make sense to mix them? Or is my above thought that a json_streamt is actually also some form of jsont that actually holds data?!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Whitespace changes?

Copy link
Member

Choose a reason for hiding this comment

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

These whitespace changes are unnecessary.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does this need to be a template argument?

Copy link
Member

Choose a reason for hiding this comment

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

To be able to use both the streaming and the object version.

@NlightNFotis NlightNFotis force-pushed the flush-json-stream branch 4 times, most recently from 8bb7f83 to 6544980 Compare February 12, 2018 17:09
Copy link
Member

Choose a reason for hiding this comment

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

To be able to use both the streaming and the object version.

Copy link
Member

Choose a reason for hiding this comment

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

These whitespace changes are unnecessary.

Copy link
Member

Choose a reason for hiding this comment

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

unnecessary.

Copy link
Member

Choose a reason for hiding this comment

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

The changes in this file are unnecessary.

Copy link
Member

Choose a reason for hiding this comment

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

No, because an object always starts with an opening brace, after which we put a newline (code snippet factored out from lines 69-85 above).

Copy link
Member

Choose a reason for hiding this comment

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

I agree. There is some doxygen work to be done to explain how things fit together.

Copy link
Member

Choose a reason for hiding this comment

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

This "new class" will soon be an "old class" ;)

Copy link
Member

Choose a reason for hiding this comment

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

The only public facility that this base class provides is close().
output_current and output_finalizer should be made abstract.

Copy link
Member

Choose a reason for hiding this comment

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

It allows mixing streamed and non-streamed json elements. The API enforces that the parent of a streamed element must always be a streamed element.

Copy link
Member

Choose a reason for hiding this comment

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

This change seems unnecessary.

@NlightNFotis NlightNFotis force-pushed the flush-json-stream branch 2 times, most recently from 3e9a845 to fdb4576 Compare February 15, 2018 11:42
@NlightNFotis
Copy link
Contributor Author

I have added one last commit that attempts to fix the goto-diff broken tests, due to the way they interact with the new class.

I will write a lengthier report about what was happening and why this is necessary later.

Copy link
Member

@peterschrammel peterschrammel left a comment

Choose a reason for hiding this comment

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

Thanks, @NlightNFotis, this looks good now. Please update the TG bump.

Copy link
Collaborator

@tautschnig tautschnig left a comment

Choose a reason for hiding this comment

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

Various comments below, with the definitive blocker of using std::cout (and thus iostream) in a header file.

I'm still not sure I'm on the right track: do we now have multiple classes that serve one and the same purpose, but differ in how output is implemented? Does this mix data representation and behaviour? Are you solving an original design deficiency in that a jsont took care of its own output by adding a variant that takes care of its own output in a different way? Shouldn't output just never have been a member function of jsont? Isn't this one of the prototypical situations to use the visitor pattern? Even more so as jsont is the root of a nice family of classes, and also a very much bounded one so that it wouldn't be necessary to add more members to a visitor (as is sort of the case for anything based on irept)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please explain why this isn't get_json_stream() = 0; or marked protected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I change it to get_json_stream() = 0; then I get compilation errors that some variables, such as message_handler in line 189 of graphml.cpp can not be initialised to an abstract type. I understand that this happens because other members of the hierarchy, like null_message_handlert are not redefining get_json_stream(), a now pure virtual function.

If it's marked protected, that creates problems with the interaction with the class mstreamt in the same file (message.h, line 184), particularly with the method json_stream() in line 244, which contains the statement return message.message_handler->get_json_stream();, which can not work with get_json_stream() being protected.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If I change it to get_json_stream() = 0; then I get compilation errors that some variables, such as message_handler in line 189 of graphml.cpp can not be initialised to an abstract type. I understand that this happens because other members of the hierarchy, like null_message_handlert are not redefining get_json_stream(), a now pure virtual function.

To me, this looks like a prime example of why compile-time errors are preferable over runtime errors: in my opinion, that just means that null_message_handlert needs to be fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I believe a forward declaration class json_stream_arrayt; would suffice.

src/util/json.h Outdated
Copy link
Collaborator

Choose a reason for hiding this comment

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

If you remove this, then also the public in line 123 is redundant.

Copy link
Member

Choose a reason for hiding this comment

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

@NlightNFotis, this shouldn't be removed. Only move the function that is required to the public section.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Isn't this prone to attempts of closing a closed file? child_stream is not reset.

Copy link
Member

Choose a reason for hiding this comment

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

Closing closed files is not a practical issue here because json_streamt only wraps the actual stream (that could be a file). json_streamt never closes that stream - that's in the realm of the user of json_streamt. However, to avoid confusion, I would assign child_stream = nullptr here and put appropriate PRECONDITIONS to make sure that there is no double-close (which is indeed a sign of a programming error).

Copy link
Collaborator

Choose a reason for hiding this comment

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

What is the purpose of all these push_back_stream* functions ("Add a JSON ..." -- to what?) as none of them are invoked anywhere in this commit, and why aren't they just a single one in the parent class? I understand that they have different return types, but is that actually necessary (as anything done to those return values can be resolved to the right child class via virtual tables)?

Copy link
Member

Choose a reason for hiding this comment

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

It would be very inconvenient to let the user cast to json_stream_arrayt and json_stream_objectt in order to use their respective interfaces (objects and arrays are quite different things).

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this fits with my overall comment: there seems to be a strong case for re-architecting here to use a visitor rather than this proposed design of inheritance.

Copy link
Member

Choose a reason for hiding this comment

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

@tautschnig, could you please explain how 'use a visitor' would contribute to a solution in this context?
Inheritance is used here because json_stream_arrayt and json_stream_objectt are both json streams, which share common functionality, namely flushing the data. If this is considered too little to justify a base class the common code can be duplicated in the respective derived classes at the cost of complicating the logic to flush the stream and to enforce the single-stream-at-a-time invariant.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In my opinion, there should be one class that knows how to stream jsont. That class would be the visitor. It has methods that know how to stream a json_arrayt or json_objectt (and json_numbert, json_stringt, etc.).

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do you need compatibility with jsont? Are you saying that this some sort of replacement for jsont? Please tell me that I'm wrong.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does it even make sense to mix them? Or is my above thought that a json_streamt is actually also some form of jsont that actually holds data?!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is there a json_stream_arrayt variant, but no json_stream_objectt one?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Nope. Not in a header file.

Copy link
Member

Choose a reason for hiding this comment

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

@NlightNFotis, the implementation of the constructor must go into the .cpp file.

Copy link
Collaborator

Choose a reason for hiding this comment

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

First, there seems to be no point of even having a std::ostream member as there is a single function that relies on it, and no way to setting it to any other value. Second, this has no place in a header file.

Copy link
Member

Choose a reason for hiding this comment

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

No, out is used many, many times in the .cpp file. Eventually, assigning out should be the only use of std::cout in the codebase.
The implementation of the constructor must be moved back into the cpp file.

src/util/json.h Outdated
Copy link
Member

Choose a reason for hiding this comment

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

@NlightNFotis, this shouldn't be removed. Only move the function that is required to the public section.

Copy link
Member

Choose a reason for hiding this comment

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

They are actually quite similar. The only difference is that array elements don't have keys.

Copy link
Member

Choose a reason for hiding this comment

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

Closing closed files is not a practical issue here because json_streamt only wraps the actual stream (that could be a file). json_streamt never closes that stream - that's in the realm of the user of json_streamt. However, to avoid confusion, I would assign child_stream = nullptr here and put appropriate PRECONDITIONS to make sure that there is no double-close (which is indeed a sign of a programming error).

Copy link
Member

Choose a reason for hiding this comment

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

It would be very inconvenient to let the user cast to json_stream_arrayt and json_stream_objectt in order to use their respective interfaces (objects and arrays are quite different things).

Copy link
Member

Choose a reason for hiding this comment

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

No. open is the state of this not child_stream. You can create and close many child streams sequentially (timewise), e.g. for each element in an array or each property in an object). The invariant is that there can only be at most child stream at a time.

Copy link
Member

Choose a reason for hiding this comment

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

@NlightNFotis, it seems that open requires explanation.

Copy link
Member

Choose a reason for hiding this comment

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

@NlightNFotis, the implementation of the constructor must go into the .cpp file.

Copy link
Member

Choose a reason for hiding this comment

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

No, out is used many, many times in the .cpp file. Eventually, assigning out should be the only use of std::cout in the codebase.
The implementation of the constructor must be moved back into the cpp file.

@NlightNFotis
Copy link
Contributor Author

Hi @tautschnig. I think your concerns have been addressed. Is it possible that you take another look and let me know if there's anything more you want to be done, else give it the green light?

Thanks for your time,

@tautschnig
Copy link
Collaborator

To what extent has #1814 (comment):

think this fits with my overall comment: there seems to be a strong case for re-architecting here to use a visitor rather than this proposed design of inheritance.

been addressed? Anyone is free to override my non-approval, but I do maintain that the architecture here is flawed and that implementing a different form of output by inheriting and overriding the data representation is wrong.

@martin-cs
Copy link
Collaborator

@tautschnig As the person who had the initial problem -- would it help if I had a review?

@tautschnig
Copy link
Collaborator

@martin-cs Your reviews are always welcome! It may very well be the case that an urgent solution is required and I thus invite overrides to my non-approval. Any maybe my suggested architecture is also flawed. I'm always open to input.

Introduce a new json object class that streams the object
immediately to an output instead of building the object
in memory and then output it to the stream.
Refactor the templated convert function with switch
statements that handle different instructions to dispatch
to functions that are specialised per instruction.
@NlightNFotis
Copy link
Contributor Author

@tautschnig @peterschrammel I have been made aware that there have been offline discussions regarding the architectural changes in this PR, and the disagreements have been resolved. Can you both take a look to let me know if everything is alright, and if yes, possibly merge it as well?

@peterschrammel The associated testgen PR is also passing with no problems

@tautschnig
Copy link
Collaborator

My apologies for having been dense and not fully understanding the problem this was trying to solve. It took an out-of-band chat with @peterschrammel to get me on track: it was not clear to me that constructing the jsont (and keeping it in memory) was the problem, not just constructing a very long std::string of output. With that insight I was able to make sense of all the bits that make up the proposed API. It's not the way I would have solved the problem (see below), but it's an existing solution so I'm not going to stand in the way any further.

Here is what I would have done: make json_streamt a factory (of sorts) that owns all jsont objects and can memory-manage them. Rather than changing jsont (or its children) I'd keep a stack of "open" objects in json_streamt, with any push_back or operator[] access going through the factory. The desired advantage is that the data representation would remain with jsont, and it would seem closer to an established design pattern. But it might have disadvantages that one would only get to see when really implementing it in full.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants