Skip to content

Commit 7dfd52b

Browse files
committed
neuronapi: fix nrn_symbol_dataptr for top-level runtime scalars
nrn_symbol_dataptr returned sym->u.pval for every symbol. For a NOTUSER runtime scalar (created in HOC by e.g. `x = 42`) that union member holds an object-data offset, not a pointer, so the returned value was a small integer that segfaults on dereference. The real storage for such a scalar is hoc_top_level_data[sym->u.oboff].pval, matching the NOTUSER branch of eval() in oc/code.cpp. Return that address for the top-level VAR/NOTUSER case so the result is a dereferenceable double*, as the name promises; every other symbol is unchanged. A caller can now read and write `x = 42`-style globals through the pointer, the same as built-in USERDOUBLE scalars like t. Adds test/api/global_scalar.cpp: a NOTUSER scalar's dataptr now dereferences to the stored value and aliases the storage HOC reads (cross-checked by copying into hoc_ac_), plus a USERDOUBLE round-trip.
1 parent 4f53d26 commit 7dfd52b

4 files changed

Lines changed: 85 additions & 3 deletions

File tree

docs/capi.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -736,10 +736,14 @@ Functions, objects, and the stack
736736
737737
.. c:function:: double* nrn_symbol_dataptr(const Symbol* sym)
738738
739-
Get a pointer to the data for a symbol (for variables).
739+
Get a pointer to the storage for a scalar variable, for direct reading and
740+
writing. This covers built-in ``USERDOUBLE`` scalars such as ``t`` (time) as
741+
well as runtime scalars created in HOC (e.g. ``x = 42``), whose value lives
742+
in the top-level object-data array rather than at ``sym->u.pval``.
740743
741744
:param sym: Pointer to the symbol.
742-
:returns: Pointer to the symbol's data, or NULL if not applicable.
745+
:returns: Pointer to the symbol's data, or the raw ``sym->u.pval`` for
746+
symbols that are not top-level runtime scalars.
743747
744748
**Usage Pattern:**
745749

src/nrniv/neuronapi.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,18 @@ int nrn_symbol_subtype(const Symbol* sym) {
258258
}
259259

260260
double* nrn_symbol_dataptr(const Symbol* sym) {
261+
// A NOTUSER runtime scalar (created in HOC by e.g. `x = 42`) does not store
262+
// its value at sym->u.pval -- for that subtype the union member holds an
263+
// object-data offset, not a pointer, so returning it hands back garbage that
264+
// segfaults on dereference. The real storage is in the top-level
265+
// object-data array (see the NOTUSER branch of eval() in oc/code.cpp, which
266+
// reads *OPVAL(sym) == *hoc_top_level_data[sym->u.oboff].pval). Return that
267+
// address so the result is a dereferenceable double*, as the name promises.
268+
// Every other case (USERDOUBLE built-ins such as `t`, and the typed USER*
269+
// subtypes that already alias sym->u.pval) is unchanged.
270+
if (sym && sym->type == VAR && sym->subtype == NOTUSER) {
271+
return hoc_top_level_data[sym->u.oboff].pval;
272+
}
261273
return sym->u.pval;
262274
}
263275

test/api/CMakeLists.txt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
foreach(api_test_file hh_sim.cpp netcon.cpp node_index.cpp segment_diam.cpp sections.cpp vclamp.cpp)
1+
foreach(
2+
api_test_file
3+
global_scalar.cpp
4+
hh_sim.cpp
5+
netcon.cpp
6+
node_index.cpp
7+
segment_diam.cpp
8+
sections.cpp
9+
vclamp.cpp)
210
string(REPLACE "." "_" api_test_name "${api_test_file}")
311
add_executable(${api_test_name} ${api_test_file})
412
cpp_cc_configure_sanitizers(TARGET ${api_test_name})

test/api/global_scalar.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH
2+
// Exercises nrn_symbol_dataptr on a NOTUSER runtime scalar (created by HOC
3+
// `x = 42`). Its value is stored in the top-level object-data array, not at
4+
// sym->u.pval, so before the fix the returned pointer was an offset cast to a
5+
// pointer and dereferencing it was undefined. The pointer must now alias the
6+
// same storage HOC reads and writes.
7+
#include <cmath>
8+
#include <iostream>
9+
#include "neuronapi.h"
10+
11+
using std::cerr;
12+
using std::endl;
13+
14+
extern "C" void modl_reg(){/* No modl_reg */};
15+
16+
static bool check(bool cond, const char* msg) {
17+
if (!cond) {
18+
cerr << "FAIL: " << msg << endl;
19+
}
20+
return cond;
21+
}
22+
23+
static bool eq(double got, double want, const char* msg) {
24+
if (std::fabs(got - want) > 1e-12) {
25+
cerr << "FAIL: " << msg << " — got " << got << ", want " << want << endl;
26+
return false;
27+
}
28+
return true;
29+
}
30+
31+
int main(void) {
32+
static const char* argv[] = {"global_scalar", "-nogui", "-nopython", nullptr};
33+
nrn_init(3, argv);
34+
35+
bool ok = true;
36+
37+
// NOTUSER: a runtime scalar. Its data lives in the top-level object-data
38+
// array, not at sym->u.pval -- this is the case the fix addresses.
39+
nrn_hoc_call("myvar = 42");
40+
double* p = nrn_symbol_dataptr(nrn_symbol("myvar"));
41+
ok &= check(p != nullptr, "NOTUSER dataptr is non-null");
42+
ok &= eq(*p, 42.0, "NOTUSER dataptr dereferences to 42");
43+
44+
// The pointer must alias the storage HOC uses: write through it, then have
45+
// HOC copy myvar into the built-in hoc_ac_ and confirm HOC saw the change.
46+
*p = 3.5;
47+
nrn_hoc_call("hoc_ac_ = myvar");
48+
double* ac = nrn_symbol_dataptr(nrn_symbol("hoc_ac_"));
49+
ok &= eq(*ac, 3.5, "HOC reads the value written through the NOTUSER dataptr");
50+
51+
// USERDOUBLE built-in (t): dataptr already worked; make sure it still does.
52+
double* t = nrn_symbol_dataptr(nrn_symbol("t"));
53+
*t = 12.0;
54+
nrn_hoc_call("hoc_ac_ = t");
55+
ok &= eq(*ac, 12.0, "USERDOUBLE dataptr round-trips through HOC");
56+
57+
return ok ? 0 : 1;
58+
}

0 commit comments

Comments
 (0)