Skip to content

Commit a89fc23

Browse files
Merge pull request #7437 from thomasspriggs/tas/api_messages
Add call back system for reporting messages through the API
2 parents ddc7ad7 + f7ba800 commit a89fc23

File tree

12 files changed

+233
-0
lines changed

12 files changed

+233
-0
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ cprover_default_properties(
240240
json
241241
json-symtab-language
242242
langapi
243+
lib-unit
243244
linking
244245
pointer-analysis
245246
solvers

regression/libcprover-cpp/call_bmc.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99
#include <iostream>
1010
#include <vector>
1111

12+
void print_messages_to_stdout(
13+
const api_messaget &message,
14+
api_call_back_contextt)
15+
{
16+
std::cout << api_message_get_string(message) << std::endl;
17+
}
18+
1219
int main(int argc, char *argv[])
1320
{
1421
try
@@ -25,6 +32,7 @@ int main(int argc, char *argv[])
2532
api_sessiont api(api_options);
2633

2734
// Demonstrate the loading of a goto-model from the command line arguments
35+
api.set_message_callback(print_messages_to_stdout, nullptr);
2836
api.load_model_from_files(arguments);
2937

3038
std::cout << "Successfully initialised goto_model" << std::endl;

regression/libcprover-cpp/model_loading/load_basic_c_file/test.desc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ test.c
33

44
^EXIT=0$
55
^SIGNAL=0$
6+
Parsing test.c
7+
Converting
8+
Type\-checking test
9+
Generating GOTO Program
610
Successfully initialised goto_model
711
--
812
--

src/libcprover-cpp/api.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,60 @@ api_sessiont::api_sessiont(const api_optionst &options)
4646

4747
api_sessiont::~api_sessiont() = default;
4848

49+
struct api_messaget
50+
{
51+
std::string string;
52+
};
53+
54+
const char *api_message_get_string(const api_messaget &message)
55+
{
56+
return message.string.c_str();
57+
}
58+
59+
class api_message_handlert : public message_handlert
60+
{
61+
public:
62+
explicit api_message_handlert(
63+
api_message_callbackt callback,
64+
api_call_back_contextt context);
65+
66+
void print(unsigned level, const std::string &message) override;
67+
68+
// Unimplemented for now. We may need to implement these as string conversions
69+
// or something later.
70+
void print(unsigned level, const xmlt &xml) override{};
71+
void print(unsigned level, const jsont &json) override{};
72+
73+
void flush(unsigned) override{};
74+
75+
private:
76+
api_call_back_contextt context;
77+
api_message_callbackt callback;
78+
};
79+
80+
api_message_handlert::api_message_handlert(
81+
api_message_callbackt callback,
82+
api_call_back_contextt context)
83+
: message_handlert{}, context{context}, callback{callback}
84+
{
85+
}
86+
87+
void api_message_handlert::print(unsigned level, const std::string &message)
88+
{
89+
if(!callback)
90+
return;
91+
api_messaget api_message{message};
92+
callback(api_message, context);
93+
}
94+
95+
void api_sessiont::set_message_callback(
96+
api_message_callbackt callback,
97+
api_call_back_contextt context)
98+
{
99+
implementation->message_handler =
100+
util_make_unique<api_message_handlert>(callback, context);
101+
}
102+
49103
void api_sessiont::load_model_from_files(const std::vector<std::string> &files)
50104
{
51105
implementation->model = util_make_unique<goto_modelt>(initialize_goto_model(

src/libcprover-cpp/api.h

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,34 @@ struct api_session_implementationt;
1919

2020
#include "options.h" // IWYU pragma: keep
2121

22+
/// Opaque message type. Properties of messages to be fetched through further
23+
/// api calls.
24+
struct api_messaget;
25+
26+
/// Given a \p api_message, this function returns that message expressed as a
27+
/// C language string.
28+
/// \note The memory for the returned string is owned by the message and as such
29+
/// does not need to be freed by users of the API.
30+
const char *api_message_get_string(const api_messaget &message);
31+
32+
/// The type of pointers to contextual data passed to the api_message_callback
33+
/// functions. These pointers point to api consumer data and are just passed
34+
/// through to the callback verbatim. These support users of the api to avoid
35+
/// using global variables.
36+
using api_call_back_contextt = void *;
37+
38+
/// The type of call back for feedback of status information and results.
39+
/// \param message: A structured message object. The lifetime of this object is
40+
/// the duration of the call to the callback. So if any data from it is
41+
/// required to persist, then this data must be copied into the API consumers
42+
/// memory.
43+
/// \param call_back_context: A pointer to the context for the function. This
44+
/// is passed through the API to the function and is for use like a capture
45+
/// group. Memory for this object is owned by the consumer of the API.
46+
using api_message_callbackt = void (*)(
47+
const api_messaget &message,
48+
api_call_back_contextt call_back_context);
49+
2250
// An object in the pattern of Session Facade - owning all of the memory
2351
// the API is using and being responsible for the management of that.
2452
struct api_sessiont
@@ -28,6 +56,14 @@ struct api_sessiont
2856
explicit api_sessiont(const api_optionst &options);
2957
~api_sessiont(); // default constructed in the .cpp file
3058

59+
/// \param callback: A call back function to receive progress updates and
60+
/// success/failure statuses.
61+
/// \param context: A context pointer passed through to the callback function.
62+
/// This is used similarly to a capture in a lambda function.
63+
void set_message_callback(
64+
api_message_callbackt callback,
65+
api_call_back_contextt context);
66+
3167
/// Load a goto_model from a given vector of filenames.
3268
/// \param files: A vector<string> containing the filenames to be loaded
3369
void load_model_from_files(const std::vector<std::string> &files);

unit/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ endif()
1212
file(GLOB_RECURSE sources "*.cpp" "*.h")
1313

1414
file(GLOB_RECURSE testing_utils "testing-utils/*.cpp" "testing-utils/*.h")
15+
file(GLOB_RECURSE libcprover_sources "libcprover-cpp/*.cpp" "libcprover-cpp/*.h")
1516

1617
if(NOT WITH_MEMORY_ANALYZER)
1718
file(GLOB_RECURSE memory_analyzer_sources "memory-analyzer/*.cpp")
@@ -62,9 +63,13 @@ list(REMOVE_ITEM sources
6263

6364
# Intended to fail to compile
6465
${CMAKE_CURRENT_SOURCE_DIR}/util/expr_cast/expr_undefined_casts.cpp
66+
67+
# Built into separate binary
68+
${libcprover_sources}
6569
)
6670

6771
add_subdirectory(testing-utils)
72+
add_subdirectory(libcprover-cpp)
6873

6974
add_executable(unit ${sources})
7075
target_include_directories(unit

unit/count_tests.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ def tests_in_file(file_path):
5656
def count_tests_in_directory(directory_path, exclude_files):
5757
test_count = 0
5858
for root, sub_directories, files in os.walk("."):
59+
# Excluded from count as built into separate binary.
60+
if root.endswith("libcprover-cpp"):
61+
continue
5962
for file_name in files:
6063
if any(file_name == excluded_file for excluded_file in exclude_files):
6164
continue

unit/libcprover-cpp/CMakeLists.txt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
file(GLOB_RECURSE sources "*.cpp" "*.h")
3+
4+
add_executable(lib-unit ${sources})
5+
target_include_directories(lib-unit
6+
PUBLIC
7+
${CBMC_SOURCE_DIR}/libcprover-cpp
8+
)
9+
target_link_libraries(lib-unit
10+
cprover-api-cpp
11+
)
12+
13+
add_test(
14+
NAME lib-unit
15+
COMMAND $<TARGET_FILE:lib-unit>
16+
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
17+
)
18+
19+
set_tests_properties(lib-unit PROPERTIES LABELS "CORE;CBMC")

unit/libcprover-cpp/api.cpp

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Author: Diffblue Ltd.
2+
3+
// Note that this test should not include any headers which are internal to
4+
// cbmc. API headers (and STL/catch) should be included only. This is in order
5+
// to confirm that the API can be used without reference to cmbc internals.
6+
7+
#include <libcprover-cpp/api.h>
8+
9+
#include <algorithm>
10+
#include <sstream>
11+
#include <string>
12+
#include <vector>
13+
14+
#include "../catch/catch.hpp"
15+
16+
class contains_in_ordert final
17+
: public Catch::MatcherBase<std::vector<std::string>>
18+
{
19+
public:
20+
contains_in_ordert(std::initializer_list<std::string> expected);
21+
bool match(const std::vector<std::string> &actual) const override;
22+
23+
protected:
24+
std::string describe() const override;
25+
26+
private:
27+
std::vector<std::string> expected;
28+
};
29+
30+
contains_in_ordert::contains_in_ordert(
31+
std::initializer_list<std::string> expected)
32+
: expected{
33+
std::make_move_iterator(expected.begin()),
34+
std::make_move_iterator(expected.end())}
35+
{
36+
}
37+
38+
bool contains_in_ordert::match(const std::vector<std::string> &actual) const
39+
{
40+
auto begin_search = actual.begin();
41+
for(const auto &expected_item : expected)
42+
{
43+
auto find_result = std::find(begin_search, actual.end(), expected_item);
44+
if(find_result == actual.end())
45+
return false;
46+
else
47+
begin_search = ++find_result;
48+
}
49+
return true;
50+
}
51+
52+
std::string contains_in_ordert::describe() const
53+
{
54+
std::stringstream description;
55+
description << "contains these strings in order ";
56+
description << '"' << *expected.begin() << '"';
57+
for(auto iterator = std::next(expected.begin()); iterator != expected.end();
58+
++iterator)
59+
{
60+
description << ", \"" << *iterator << '"';
61+
}
62+
return description.str();
63+
}
64+
65+
TEST_CASE("Test loading model from file.", "[core][libcprover-cpp]")
66+
{
67+
api_sessiont api(api_optionst::create());
68+
69+
std::vector<std::string> output;
70+
// This lambda needs to be non-capturing in order for it to be convertible
71+
// to a function pointer, to pass to the API.
72+
const auto write_output =
73+
[](const api_messaget &message, api_call_back_contextt context) {
74+
std::vector<std::string> &output =
75+
*static_cast<std::vector<std::string> *>(context);
76+
output.emplace_back(api_message_get_string(message));
77+
};
78+
79+
api.set_message_callback(write_output, &output);
80+
api.load_model_from_files({"test.c"});
81+
CHECK_THAT(
82+
output,
83+
(contains_in_ordert{
84+
"Parsing test.c",
85+
"Converting",
86+
"Type-checking test",
87+
"Generating GOTO Program"}));
88+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Author: Diffblue Ltd.
2+
3+
#define CATCH_CONFIG_MAIN
4+
#include "../catch/catch.hpp"
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
../catch
2+
libcprover-cpp

unit/libcprover-cpp/test.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Input file to test reading through API, not for compiling!
2+
3+
#include <assert.h>
4+
5+
int main(int argc, char *argv[])
6+
{
7+
int a[] = {0, 1, 2, 3, 4};
8+
assert(a[4] != 4);
9+
}

0 commit comments

Comments
 (0)