Skip to content

Commit 931c7d0

Browse files
committed
neuronapi: return nullptr from nrn_symbol_dataptr for non-scalar symbols
Follow-up to #3815. nrn_symbol_dataptr returned sym->u.pval for any symbol that was not a NOTUSER scalar, so asking for the dataptr of a symbol with no double storage -- a function such as finitialize, a top-level objref or strdef, or a section-level property (nseg/L/Ra/ rallbranch) -- handed back a reinterpreted union member (an Object*, char*, function pointer, or a {type, index} pair) that dereferences to garbage. Return nullptr for those instead. The function now yields a real double* only for a scalar/array VAR (USERDOUBLE, the typed USERINT/USERFLOAT that alias sym->u.pval, and NOTUSER whose storage lives in the top-level object-data array) and nullptr for everything else. Extends test/api/global_scalar.cpp with the nullptr cases (null symbol, finitialize, a top-level objref and strdef, and L) and updates the capi.rst return doc to state the failure behavior.
1 parent 5ac449d commit 931c7d0

3 files changed

Lines changed: 54 additions & 13 deletions

File tree

docs/capi.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -742,8 +742,10 @@ Functions, objects, and the stack
742742
in the top-level object-data array rather than at ``sym->u.pval``.
743743
744744
:param sym: Pointer to the symbol.
745-
:returns: Pointer to the symbol's data, or the raw ``sym->u.pval`` for
746-
symbols that are not top-level runtime scalars.
745+
:returns: A pointer to the variable's storage, or ``NULL`` if ``sym`` is not
746+
a scalar variable with a data pointer (a null symbol, a function,
747+
an object, a string, or a section-level property such as ``L`` or
748+
``nseg``).
747749
748750
**Usage Pattern:**
749751

src/nrniv/neuronapi.cpp

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -258,19 +258,32 @@ 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) {
261+
// Only a scalar/array VAR has a real double* to hand back. Anything else --
262+
// a function (e.g. finitialize), an object, a string, a template (all with
263+
// type != VAR), or a section-level property (USERPROPERTY: nseg/L/Ra/
264+
// rallbranch, whose u member is a {membrane type, index} pair, not a
265+
// pointer) -- has no dataptr, so return nullptr instead of a reinterpreted
266+
// union member that the caller would dereference as garbage.
267+
if (!sym || sym->type != VAR) {
268+
return nullptr;
269+
}
270+
switch (sym->subtype) {
271+
case NOTUSER:
272+
// A NOTUSER runtime scalar (created in HOC by e.g. `x = 42`) does not
273+
// store its value at sym->u.pval -- for that subtype the union member
274+
// holds an object-data offset, not a pointer. The real storage is in
275+
// the top-level object-data array (see the NOTUSER branch of eval() in
276+
// oc/code.cpp, which reads *OPVAL(sym) ==
277+
// *hoc_top_level_data[sym->u.oboff].pval). Return that address so the
278+
// result is a dereferenceable double*, as the name promises.
271279
return hoc_top_level_data[sym->u.oboff].pval;
280+
case USERPROPERTY:
281+
return nullptr;
282+
default:
283+
// USERDOUBLE built-ins such as `t`, plus the typed USERINT/USERFLOAT
284+
// scalars whose storage aliases sym->u.pval (callers cast as needed).
285+
return sym->u.pval;
272286
}
273-
return sym->u.pval;
274287
}
275288

276289
bool nrn_symbol_is_array(const Symbol* sym) {

test/api/global_scalar.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,31 @@ int main(void) {
5454
nrn_hoc_call("hoc_ac_ = t");
5555
ok &= eq(*ac, 12.0, "USERDOUBLE dataptr round-trips through HOC");
5656

57+
// Symbols with no data pointer must return nullptr, not a reinterpreted
58+
// union member the caller would dereference as garbage.
59+
ok &= check(nrn_symbol_dataptr(nullptr) == nullptr, "null symbol -> nullptr");
60+
61+
// A function symbol (finitialize) is non-null but has no dataptr.
62+
Symbol* fi = nrn_symbol("finitialize");
63+
ok &= check(fi != nullptr, "finitialize symbol exists");
64+
ok &= check(nrn_symbol_dataptr(fi) == nullptr, "function symbol -> nullptr");
65+
66+
// Top-level object and string variables are not double storage.
67+
nrn_hoc_call("objref myobj");
68+
Symbol* obj = nrn_symbol("myobj");
69+
ok &= check(obj != nullptr, "objref symbol exists");
70+
ok &= check(nrn_symbol_dataptr(obj) == nullptr, "objref -> nullptr");
71+
72+
nrn_hoc_call("strdef mystr");
73+
Symbol* str = nrn_symbol("mystr");
74+
ok &= check(str != nullptr, "strdef symbol exists");
75+
ok &= check(nrn_symbol_dataptr(str) == nullptr, "strdef -> nullptr");
76+
77+
// A section-level property (USERPROPERTY: L, nseg, ...) has no global
78+
// storage pointer.
79+
Symbol* len = nrn_symbol("L");
80+
ok &= check(len != nullptr, "L symbol exists");
81+
ok &= check(nrn_symbol_dataptr(len) == nullptr, "section-level property -> nullptr");
82+
5783
return ok ? 0 : 1;
5884
}

0 commit comments

Comments
 (0)