NEURON
hoc_interpreter.cpp
Go to the documentation of this file.
1 #include <catch2/generators/catch_generators_range.hpp>
2 #include <catch2/matchers/catch_matchers_string.hpp>
3 #include <catch2/catch_test_macros.hpp>
4 
5 #include "classreg.h"
6 #include "code.h"
7 #include "hocdec.h"
8 #include "classreg.h"
9 #include "ocfunc.h"
10 
11 #include <sstream>
12 
13 TEST_CASE("Test hoc interpreter", "[NEURON][hoc_interpreter]") {
15  hoc_pushx(4.0);
16  hoc_pushx(5.0);
17  hoc_add();
18  REQUIRE(hoc_xpop() == 9.0);
19 }
20 
21 constexpr static auto check_tempobj_canary = 0xDEADBEEF;
22 
23 static void istmpobj() {
24  auto _listmpobj = hoc_is_tempobj_arg(1) ? check_tempobj_canary : 0;
25  hoc_retpushx(_listmpobj);
26 }
27 
28 static VoidFunc hoc_intfunc[] = {{"istmpobj", istmpobj}, {0, 0}};
29 
30 
31 TEST_CASE("Test hoc_register_var", "[NEURON][hoc_interpreter][istmpobj]") {
32  hoc_register_var(nullptr, nullptr, hoc_intfunc);
33  REQUIRE(hoc_lookup("istmpobj") != nullptr);
34  hoc_pushobj(hoc_temp_objptr(nullptr));
35  REQUIRE(hoc_call_func(hoc_lookup("istmpobj"), 1) == check_tempobj_canary);
36 }
37 
38 SCENARIO("Test for issue #1995", "[NEURON][hoc_interpreter][issue-1995]") {
39  // This is targeting AddressSanitizer errors that were seen in #1995
40  GIVEN("Test calling a function that returns an Object that lived on the stack") {
41  constexpr auto vector_size = 5;
42  REQUIRE(hoc_oc(("obfunc foo() {localobj lv1\n"
43  " lv1 = new Vector(" +
44  std::to_string(vector_size) +
45  ")\n"
46  " lv1.indgen()\n"
47  " return lv1\n"
48  "}\n")
49  .c_str()) == 0);
50  WHEN("It is called") {
51  REQUIRE(hoc_oc("objref v1\n"
52  "v1 = foo()\n") == 0);
53  THEN("The returned value should be correct") {
54  auto const i = GENERATE_COPY(range(1, vector_size));
55  REQUIRE(hoc_oc(("hoc_ac_ = v1.x[" + std::to_string(i) + "]\n").c_str()) == 0);
56  REQUIRE(hoc_ac_ == i);
57  }
58  }
59  }
60 }
61 
62 namespace {
63 struct UtilityThatLikesThrowing {
64  UtilityThatLikesThrowing(int data)
65  : m_data{data} {}
66  [[nodiscard]] bool hoc_destructor_should_throw() const {
67  return m_data == 1.0;
68  }
69  [[noreturn]] void throw_error() const {
70  throw std::runtime_error("throwing from throw_error");
71  }
72  [[noreturn]] void call_execerror() const {
73  hoc_execerror("throwing a tantrum", nullptr);
74  }
75 
76  private:
77  int m_data{};
78 };
79 double throwing_util_member_call_execerror(void* ob_void) {
80  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
81  ob->call_execerror();
82 }
83 double throwing_util_member_throw_error(void* ob_void) {
84  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
85  ob->throw_error();
86 }
87 Object** throwing_util_member_call_execerror_obj_func(void* ob_void) {
88  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
89  ob->call_execerror();
90 }
91 Object** throwing_util_member_throw_error_obj_func(void* ob_void) {
92  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
93  ob->throw_error();
94 }
95 const char** throwing_util_member_call_execerror_str_func(void* ob_void) {
96  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
97  ob->call_execerror();
98 }
99 const char** throwing_util_member_throw_error_str_func(void* ob_void) {
100  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
101  ob->throw_error();
102 }
103 static Member_func throwing_util_members[] = {{"call_execerror",
104  throwing_util_member_call_execerror},
105  {"throw_error", throwing_util_member_throw_error},
106  {nullptr, nullptr}};
107 static Member_ret_obj_func throwing_util_ret_obj_members[] = {
108  {"call_execerror_obj_func", throwing_util_member_call_execerror_obj_func},
109  {"throw_error_obj_func", throwing_util_member_throw_error_obj_func},
110  {nullptr, nullptr}};
111 static Member_ret_str_func throwing_util_ret_str_members[] = {
112  {"call_execerror_str_func", throwing_util_member_call_execerror_str_func},
113  {"throw_error_str_func", throwing_util_member_throw_error_str_func},
114  {nullptr, nullptr}};
115 void* throwing_util_constructor(Object*) {
116  if (!ifarg(1)) {
117  throw std::runtime_error("need at least one argument");
118  }
119  return new UtilityThatLikesThrowing{static_cast<int>(*hoc_getarg(1))};
120 }
121 void throwing_util_destructor(void* ob_void) {
122  auto* const ob = static_cast<UtilityThatLikesThrowing*>(ob_void);
123  bool const should_throw = ob->hoc_destructor_should_throw();
124  delete ob;
125  if (should_throw) {
126  throw std::runtime_error("throwing from HOC destructor");
127  }
128 }
129 } // namespace
130 
131 std::string hoc_oc_require_error(const char* buf) {
132  std::ostringstream oss;
133  auto const code = hoc_oc(buf, oss);
134  REQUIRE(code != 0);
135  auto const output = std::move(oss).str();
136  REQUIRE(!output.empty());
137  return output;
138 }
139 
140 using Catch::Matchers::ContainsSubstring;
141 SCENARIO("Test calling code from HOC that throws exceptions", "[NEURON][hoc_interpreter]") {
142  static bool registered = false;
143  if (!registered) {
144  class2oc("UtilityThatLikesThrowing",
145  throwing_util_constructor,
146  throwing_util_destructor,
147  throwing_util_members,
148  throwing_util_ret_obj_members,
149  throwing_util_ret_str_members);
150  registered = true;
151  // declare these test variables exactly once
152  REQUIRE(hoc_oc("objref nil, util\n") == 0);
153  }
154  GIVEN("A HOC object constructor that throws") {
155  REQUIRE_THAT(hoc_oc_require_error("util = new UtilityThatLikesThrowing()\n"),
156  ContainsSubstring(
157  "hoc_execerror: UtilityThatLikesThrowing[0] constructor: need at "
158  "least one argument"));
159  }
160  GIVEN("A HOC object destructor that throws") {
161  REQUIRE_THAT(hoc_oc_require_error("util = nil\n"
162  "util = new UtilityThatLikesThrowing(1)\n"
163  "util = nil\n"),
164  ContainsSubstring(
165  "hoc_execerror: UtilityThatLikesThrowing[0] destructor: throwing "
166  "from HOC destructor"));
167  }
168  GIVEN("A HOC object whose constructor and destructor succeed") {
169  // Make sure there are not any instances alive from previous tests, otherwise we can't
170  // hardcode the zero in "UtilityThatLikesThrowing[0]"
171  REQUIRE(hoc_oc("util = nil\n") == 0);
172  // Make an instance and tell it not to throw from its destructor (with the 2)
173  REQUIRE(hoc_oc("util = new UtilityThatLikesThrowing(2)\n") == 0);
174  // Generate tests for the HOC member functions returning doubles, objects and strings
175  auto const method_suffix = GENERATE(as<std::string>{}, "", "_obj_func", "_str_func");
176  WHEN("A member function that throws is called") {
177  REQUIRE_THAT(
178  hoc_oc_require_error(("util.throw_error" + method_suffix + "()\n").c_str()),
179  ContainsSubstring("hoc_execerror: UtilityThatLikesThrowing[0]::throw_error" +
180  method_suffix +
181  ": throwing "
182  "from throw_error"));
183  }
184  WHEN("A member function that calls hoc_execerror is called") {
185  REQUIRE_THAT(hoc_oc_require_error(
186  ("util.call_execerror" + method_suffix + "()\n").c_str()),
187  ContainsSubstring("hoc_execerror: throwing a tantrum"));
188  }
189  }
190 }
191 
192 #if USE_PYTHON
193 TEST_CASE("Test hoc_array_access", "[NEURON][hoc_interpreter][nrnpython][array_access]") {
194  REQUIRE(hoc_oc("nrnpython(\"avec = [0,1,2]\")\n"
195  "objref po\n"
196  "po = new PythonObject()\n"
197  "po = po.avec\n") == 0);
198  THEN("The avec can value should be correct") {
199  auto const i = GENERATE_COPY(range(0, 3));
200  REQUIRE(hoc_oc(("hoc_ac_ = po._[" + std::to_string(i) + "]\n").c_str()) == 0);
201  REQUIRE(hoc_ac_ == i);
202  }
203 }
204 #endif
void class2oc(const char *, ctor_f *cons, dtor_f *destruct, Member_func *, Member_ret_obj_func *, Member_ret_str_func *)
Definition: hoc_oop.cpp:1631
void hoc_init_space()
Definition: code.cpp:416
void hoc_add(void)
Definition: code.cpp:1969
#define i
Definition: md1redef.h:19
constexpr auto range(T &&iterable)
Definition: enumerate.h:32
char buf[512]
Definition: init.cpp:13
double hoc_xpop()
Definition: code.cpp:903
double * hoc_getarg(int narg)
Definition: code.cpp:1641
double hoc_call_func(Symbol *s, int narg)
Definition: code.cpp:1477
void hoc_pushobj(Object **d)
Definition: code.cpp:784
int hoc_oc(const char *buf)
Definition: hoc.cpp:1314
void hoc_retpushx(double x)
Definition: hocusr.cpp:154
double hoc_ac_
Definition: hoc_init.cpp:222
Symbol * hoc_lookup(const char *)
Definition: symbol.cpp:59
int hoc_is_tempobj_arg(int narg)
Definition: code.cpp:881
SCENARIO("Test for issue #1995", "[NEURON][hoc_interpreter][issue-1995]")
std::string hoc_oc_require_error(const char *buf)
TEST_CASE("Test hoc interpreter", "[NEURON][hoc_interpreter]")
static void istmpobj()
constexpr static auto check_tempobj_canary
static VoidFunc hoc_intfunc[]
void hoc_pushx(double)
Definition: code.cpp:779
void move(Item *q1, Item *q2, Item *q3)
Definition: list.cpp:200
void hoc_execerror(const char *s1, const char *s2)
Definition: nrnoc_aux.cpp:39
void hoc_register_var(DoubScal *ds, DoubVec *dv, VoidFunc *)
Definition: global_vars.cpp:31
std::string to_string(const T &obj)
int ifarg(int)
Definition: code.cpp:1607
Object ** hoc_temp_objptr(Object *)
Definition: code.cpp:151
Definition: hocdec.h:173