Skip to content

Fix for cbmc running out of memory while printing traces using json_ui / target: develop #1814

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 5 commits into from
Mar 19, 2018

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.

@@ -11,4 +11,172 @@ Author: Daniel Kroening
/// \file
/// Traces of GOTO Programs

#include <cassert>
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.

@@ -61,14 +61,6 @@ class ui_message_handlert:public message_handlert
int sequence_number,
const source_locationt &location) override;

virtual void print(
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.

result["property"]=json_stringt(id2string(g.first));
result["description"]=json_stringt(id2string(g.second.description));
result["status"]=json_stringt(g.second.status_string());

if(g.second.status==goalt::statust::FAILURE)
{
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.

@@ -133,6 +117,32 @@ void jsont::output_rec(std::ostream &out, unsigned indent) const
}
}

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).

@@ -10,6 +10,8 @@ Author: Peter Schrammel
#ifndef CPROVER_UTIL_JSON_STREAM_H
#define CPROVER_UTIL_JSON_STREAM_H

#include <memory>
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

@@ -123,51 +122,6 @@ void ui_message_handlert::print(
}
}

void ui_message_handlert::print(
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.

@@ -6,8 +6,6 @@ Author: Daniel Kroening, kroening@kroening.com

\*******************************************************************/

#include "ui_message.h"
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.

@@ -17,6 +15,7 @@ Author: Daniel Kroening, kroening@kroening.com
#include "json_expr.h"
#include "json_stream.h"
#include "cout_message.h"
#include "ui_message.h"
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.

const namespacet &ns=conversion_dependencies.ns;

irep_idt identifier=step.lhs_object.get_identifier();

json_assignment["stepType"]=json_stringt("assignment");

if(!location.is_null())
json_assignment["sourceLocation"]=location;
if(!json_location.is_null())
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
@@ -109,7 +109,6 @@ class jsont
protected:
void output_rec(std::ostream &, unsigned indent) const;
static void escape_string(const std::string &, std::ostream &);

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


#include <ostream>

void json_streamt::output_delimiter()
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

class json_streamt
{
public:
void close()
Copy link
Member

Choose a reason for hiding this comment

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

doxygen

@@ -52,13 +52,15 @@ json_stream_objectt::json_stream_objectt(std::ostream &out, unsigned indent)
out << '{';
}

/// Create a new JSON array stream.
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.

@@ -232,6 +238,12 @@ class messaget
return func(*this);
}

json_stream_arrayt &json_stream()
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?


source_locationt previous_source_location;
#include "json_goto_trace.h"
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

@@ -10,6 +10,8 @@ Author: Peter Schrammel
#ifndef CPROVER_UTIL_JSON_STREAM_H
#define CPROVER_UTIL_JSON_STREAM_H

#include <memory>
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


typedef struct
{
jsont &location;
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?

for(const auto &step : goto_trace.steps)
{
const source_locationt &source_location=step.pc->source_location;
void convert_assert(json_objectt &json_failure,
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

conversion_dependenciest &conversion_dependencies)
{
const goto_trace_stept& step=conversion_dependencies.step;
jsont &location=conversion_dependencies.location;
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.


#include <util/json_expr.h>
#include <util/arith_tools.h>
#include <util/config.h>
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.

if(o_it!=object.begin())
out << ',';

out << '\n';
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).

out << '"';
jsont::escape_string(key, out);
out << '"';
out << ": ";
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

std::string value;

friend class json_stream_objectt;
friend class json_stream_arrayt;
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.

{
current->close();
}
}
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.

{
out << '\n' << std::string(indent*2, ' ');
out << ']';
}
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).

return create_current_array();
}

/// Overloaded version of the push_back_stream_object, that
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"

/// This approach was necessary to fix some issues with
/// CBMC running out of memory when it had to print some
/// huge traces for some programs.
class 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.

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.

/// JSON object.
/// \param key: The key to be looked up inside the
/// attributes of the JSON object.
jsont &operator[](const std::string &key)
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?!

@@ -207,18 +207,18 @@ void bmc_all_propertiest::report(const cover_goalst &cover_goals)
case ui_message_handlert::uit::JSON_UI:
{
json_objectt json_result;
json_arrayt &result_array=json_result["result"].make_array();
json_arrayt &result_array = json_result["result"].make_array();
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.

trace_optionst trace_options = trace_optionst::default_options);
const namespacet &ns,
const goto_tracet &goto_trace,
json_arrayT &dest_array,
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
trace_optionst trace_options = trace_optionst::default_options);
const namespacet &ns,
const goto_tracet &goto_trace,
json_arrayT &dest_array,
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.

@@ -207,18 +207,18 @@ void bmc_all_propertiest::report(const cover_goalst &cover_goals)
case ui_message_handlert::uit::JSON_UI:
{
json_objectt json_result;
json_arrayt &result_array=json_result["result"].make_array();
json_arrayt &result_array = json_result["result"].make_array();
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.

@@ -369,7 +369,7 @@ bool bmc_covert::operator()()
json_objectt &result=tests_array.push_back().make_object();
if(bmc.options.get_bool_option("trace"))
{
jsont &json_trace=result["trace"];
jsont &json_trace = result["trace"];
Copy link
Member

Choose a reason for hiding this comment

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

unnecessary.

@@ -33,20 +33,20 @@ void convert(
jsont &dest,
trace_optionst trace_options)
{
json_arrayt &dest_array=dest.make_array();
json_arrayt &dest_array = dest.make_array();
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.

if(o_it!=object.begin())
out << ',';

out << '\n';
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).

current =
std::unique_ptr<json_streamt>(new json_stream_objectt(out, indent + 1));
return static_cast<json_stream_objectt &>(*current);
}
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.

class json_stream_objectt;
class json_stream_arrayt;

/// New class whose aim is to stream newly created JSON
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" ;)

/// This approach was necessary to fix some issues with
/// CBMC running out of memory when it had to print some
/// huge traces for some programs.
class json_streamt
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.

/// JSON object.
/// \param key: The key to be looked up inside the
/// attributes of the JSON object.
jsont &operator[](const std::string &key)
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.

@@ -209,7 +209,17 @@ class messaget
return *this;
}

mstreamt &operator << (const json_objectt &data)
mstreamt &operator<<(const jsont &data)
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)?

virtual json_stream_arrayt &get_json_stream()
{
UNREACHABLE;
}
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.

@@ -16,6 +16,7 @@ Author: Daniel Kroening, kroening@kroening.com

#include "invariant.h"
#include "json.h"
#include "json_stream.h"
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.

@@ -106,7 +106,6 @@ class jsont
return it->second;
}

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 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.

if(child_stream)
{
child_stream->close();
}
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).

output_delimiter();
jsont::output_key(out, key);
return create_child_stream_object();
}
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.).

/// array stream.
/// Provided for compatibility with `jsont`.
/// \return a new empty, non-streaming JSON object
jsont &push_back()
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.

/// JSON object.
/// \param key: The key to be looked up inside the
/// attributes of the JSON object.
jsont &operator[](const std::string &key)
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?!

@@ -232,6 +238,12 @@ class messaget
return func(*this);
}

json_stream_arrayt &json_stream()
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?

@@ -10,9 +10,14 @@ Author: Daniel Kroening, kroening@kroening.com
#ifndef CPROVER_UTIL_UI_MESSAGE_H
#define CPROVER_UTIL_UI_MESSAGE_H

#include <memory>
#include <iostream>
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.

: _ui(uit::PLAIN), time(timestampert::make(timestampert::clockt::NONE))
: _ui(uit::PLAIN),
time(timestampert::make(timestampert::clockt::NONE)),
out(std::cout),
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.

@@ -106,7 +106,6 @@ class jsont
return it->second;
}

protected:
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.

output_delimiter();
object["array_element"].output_rec(out, indent + 1);
object.clear();
}
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.

if(child_stream)
{
child_stream->close();
}
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).

output_delimiter();
jsont::output_key(out, key);
return create_child_stream_object();
}
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).

{
output_child_stream();
output_finalizer();
open = false;
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.

{
}

bool open;
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.

@@ -10,9 +10,14 @@ Author: Daniel Kroening, kroening@kroening.com
#ifndef CPROVER_UTIL_UI_MESSAGE_H
#define CPROVER_UTIL_UI_MESSAGE_H

#include <memory>
#include <iostream>
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.

: _ui(uit::PLAIN), time(timestampert::make(timestampert::clockt::NONE))
: _ui(uit::PLAIN),
time(timestampert::make(timestampert::clockt::NONE)),
out(std::cout),
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