Skip to content

Commit b204a38

Browse files
committed
neuronapi: rework nrn_setpointer to the pop idiom
Per review: rather than naming one kind of source (a range variable at sec/x), take the source pointer from the top of the stack. The caller pushes it any way the stack supports (e.g. nrn_rangevar_push), then nrn_setpointer_pop consumes it and assigns it to the POINTER's dparam slot. The pushed pointer is popped even on the error paths, so the stack stays balanced; the test now pushes-then-calls and asserts that hygiene. Also switch the error-buffer copy to snprintf (guaranteed NUL-termination, clears the SonarCloud strncpy flag).
1 parent 7d2a12a commit b204a38

6 files changed

Lines changed: 60 additions & 45 deletions

File tree

docs/capi.rst

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -668,17 +668,20 @@ Segments
668668
seg.g_pas = 0.001 # S/cm²
669669
670670
671-
.. c:function:: int nrn_setpointer(Symbol* pointer_sym, Section* sec, double x, Symbol* src_sym, Section* src_sec, double src_x, char* error_msg, size_t error_msg_size)
671+
.. c:function:: int nrn_setpointer_pop(Symbol* pointer_sym, Section* sec, double x, char* error_msg, size_t error_msg_size)
672672
673-
Wire an NMODL ``POINTER`` variable to a source variable's storage.
673+
Wire an NMODL ``POINTER`` variable to the source pointer on top of the stack.
674+
675+
The source is whatever pointer the caller has pushed, e.g. with
676+
:c:func:`nrn_rangevar_push`. Pushing the source rather than naming it lets
677+
this single function accept a pointer obtained any way the stack supports,
678+
instead of enumerating source kinds. The pushed pointer is consumed (popped)
679+
even on the error paths, so the stack is left balanced.
674680
675681
:param pointer_sym: Symbol of the ``POINTER`` range variable to wire (the
676682
target).
677683
:param sec: Section of the mechanism instance owning the POINTER.
678684
:param x: Normalized position (0.0 to 1.0) of that instance.
679-
:param src_sym: Symbol of the source range variable.
680-
:param src_sec: Section of the source variable.
681-
:param src_x: Normalized position of the source variable.
682685
:param error_msg: Buffer filled with a message on failure (may be ``NULL``).
683686
:param error_msg_size: Size of ``error_msg``.
684687
:returns: 0 on success; nonzero on error, with ``error_msg`` populated when
@@ -698,7 +701,8 @@ Segments
698701
Symbol* vgap = nrn_symbol("vgap_halfgap");
699702
Symbol* v = nrn_symbol("v");
700703
char err[256];
701-
if (nrn_setpointer(vgap, cell1, 0.5, v, cell2, 0.5, err, sizeof(err))) {
704+
nrn_rangevar_push(v, cell2, 0.5); // push the source pointer
705+
if (nrn_setpointer_pop(vgap, cell1, 0.5, err, sizeof(err))) {
702706
fprintf(stderr, "setpointer failed: %s\n", err);
703707
}
704708

src/nrniv/neuronapi.cpp

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -230,24 +230,25 @@ void nrn_rangevar_push(Symbol* sym, Section* sec, double x) {
230230
hoc_push(nrn_rangepointer(sec, sym, x));
231231
}
232232

233-
int nrn_setpointer(Symbol* pointer_sym,
234-
Section* sec,
235-
double x,
236-
Symbol* src_sym,
237-
Section* src_sec,
238-
double src_x,
239-
char* error_msg,
240-
size_t error_msg_size) {
233+
int nrn_setpointer_pop(Symbol* pointer_sym,
234+
Section* sec,
235+
double x,
236+
char* error_msg,
237+
size_t error_msg_size) {
241238
if (error_msg && error_msg_size > 0) {
242239
error_msg[0] = '\0';
243240
}
244241
auto fail = [&](const char* msg) {
245242
if (error_msg && error_msg_size > 0) {
246-
strncpy(error_msg, msg, error_msg_size - 1);
247-
error_msg[error_msg_size - 1] = '\0';
243+
std::snprintf(error_msg, error_msg_size, "%s", msg);
248244
}
249245
return 1;
250246
};
247+
// The source pointer is whatever the caller pushed (e.g. via
248+
// nrn_rangevar_push), which reuses every existing way of obtaining one. Pop
249+
// it first, before the validations below, so the stack stays balanced even
250+
// on the error paths. It is the same data handle a dparam slot holds.
251+
neuron::container::data_handle<double> src = hoc_pop_handle<double>();
251252
// Only a mechanism POINTER range variable can be a setpointer target; the
252253
// slot it wires lives in the mechanism's dparam array (mirrors the guard in
253254
// nrn_pointer_assign / nrnpy_nrn.cpp).
@@ -266,11 +267,10 @@ int nrn_setpointer(Symbol* pointer_sym,
266267
if (!prop) {
267268
return fail("the POINTER's mechanism is not present at the target segment");
268269
}
269-
// Wire the POINTER to the source variable's storage. nrn_rangepointer yields
270-
// a data handle (stable across data permutation), which is exactly what the
271-
// dparam slot stores -- the same assignment nrn_pointer_assign performs,
272-
// without the PyObject* source.
273-
prop->dparam[pointer_sym->u.rng.index] = nrn_rangepointer(src_sec, src_sym, src_x);
270+
// Wire the POINTER to the popped source handle (stable across data
271+
// permutation), the same assignment nrn_pointer_assign performs without the
272+
// PyObject* source.
273+
prop->dparam[pointer_sym->u.rng.index] = src;
274274
return 0;
275275
}
276276

src/nrniv/neuronapi.h

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,11 @@ int nrn_segment_node_index(Section* sec, double x);
7272
void nrn_rangevar_push(Symbol* sym, Section* sec, double x);
7373
double nrn_rangevar_get(Symbol* sym, Section* sec, double x);
7474
void nrn_rangevar_set(Symbol* sym, Section* sec, double x, double value);
75-
int nrn_setpointer(Symbol* pointer_sym,
76-
Section* sec,
77-
double x,
78-
Symbol* src_sym,
79-
Section* src_sec,
80-
double src_x,
81-
char* error_msg,
82-
size_t error_msg_size);
75+
int nrn_setpointer_pop(Symbol* pointer_sym,
76+
Section* sec,
77+
double x,
78+
char* error_msg,
79+
size_t error_msg_size);
8380

8481
/****************************************
8582
* Functions, objects, and the stack

test/api/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ foreach(
2323
"CURRENT_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}")
2424
endforeach()
2525

26-
# The nrn_setpointer test needs a mechanism with a POINTER, which no built-in provides. Generate one
26+
# The nrn_setpointer_pop test needs a mechanism with a POINTER, which no built-in provides. Generate one
2727
# from ptrtest.mod with nocmodl and compile it into the test executable, the same codegen path nrniv
2828
# uses for its built-in mechanisms. NRN_NOCMODL_SANITIZER_ENVIRONMENT disables LeakSanitizer for the
2929
# nocmodl run (nocmodl leaks, which would otherwise fail the generation step under an asan build),

test/api/ptrtest.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
TITLE minimal POINTER density mechanism for the nrn_setpointer api test
1+
TITLE minimal POINTER density mechanism for the nrn_setpointer_pop api test
22

33
: A density mechanism with a single POINTER (src) and a plain range variable
44
: (out). INITIAL copies the pointed-to value into out, so a test can wire src
5-
: with nrn_setpointer, call finitialize, and read out back to confirm the
5+
: with nrn_setpointer_pop, call finitialize, and read out back to confirm the
66
: POINTER now aliases the source's storage.
77

88
NEURON {

test/api/setpointer.cpp

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
// NOTE: this assumes neuronapi.h is on your CPLUS_INCLUDE_PATH
2-
// Exercises nrn_setpointer, which wires an NMODL POINTER range variable to a
3-
// source variable's storage (the Python-free form of the HOC `setpointer`
4-
// statement / nrn_pointer_assign). The ptrtest density mechanism copies its
5-
// POINTER `src` into the range variable `out` in INITIAL, so after wiring
6-
// src -> a source double and calling finitialize, reading out confirms the
7-
// POINTER now aliases the source. modl_reg registers ptrtest (its generated
8-
// _ptrtest_reg is compiled into this test), the way nrniv registers built-ins.
2+
// Exercises nrn_setpointer_pop, which wires an NMODL POINTER range variable to
3+
// the source pointer the caller has pushed onto the stack (the Python-free form
4+
// of the HOC `setpointer` statement / nrn_pointer_assign). Pushing the source
5+
// rather than naming it reuses every existing way of obtaining a pointer. The
6+
// ptrtest density mechanism copies its POINTER `src` into the range variable
7+
// `out` in INITIAL, so after wiring src -> a source double and calling
8+
// finitialize, reading out confirms the POINTER now aliases the source.
9+
// modl_reg registers ptrtest (its generated _ptrtest_reg is compiled into this
10+
// test), the way nrniv registers built-ins.
911
#include <array>
1012
#include <cstring>
1113
#include <iostream>
@@ -58,10 +60,12 @@ int main(void) {
5860
ok &= check(src_sym != nullptr && out_sym != nullptr && v_sym != nullptr,
5961
"ptrtest range-variable symbols resolve");
6062

61-
// Wire dend(0.5).ptrtest.src -> soma(0.5).v
63+
// Wire dend(0.5).ptrtest.src -> soma(0.5).v. Push the source pointer, then
64+
// let setpointer_pop consume it and assign it to the POINTER's dparam slot.
6265
char err[256];
63-
int rc = nrn_setpointer(src_sym, dend, 0.5, v_sym, soma, 0.5, err, sizeof(err));
64-
ok &= check(rc == 0, "nrn_setpointer succeeds for a valid POINTER wiring");
66+
nrn_rangevar_push(v_sym, soma, 0.5);
67+
int rc = nrn_setpointer_pop(src_sym, dend, 0.5, err, sizeof(err));
68+
ok &= check(rc == 0, "nrn_setpointer_pop succeeds for a valid POINTER wiring");
6569
if (rc != 0) {
6670
cerr << " error_msg: " << err << endl;
6771
}
@@ -76,15 +80,25 @@ int main(void) {
7680
"POINTER reads the wired source's value after finitialize");
7781

7882
// Error path 1: a non-POINTER target (out is a plain RANGE var) is rejected
79-
// with a nonzero return and a message, not a crash.
80-
int rc_bad = nrn_setpointer(out_sym, dend, 0.5, v_sym, soma, 0.5, err, sizeof(err));
83+
// with a nonzero return and a message, not a crash. The pushed source is
84+
// still consumed (popped) before the validation fails, so the stack stays
85+
// balanced.
86+
nrn_rangevar_push(v_sym, soma, 0.5);
87+
int rc_bad = nrn_setpointer_pop(out_sym, dend, 0.5, err, sizeof(err));
8188
ok &= check(rc_bad != 0, "non-POINTER target is rejected");
8289
ok &= check(err[0] != '\0', "rejection fills the error message");
8390

8491
// Error path 2: the POINTER's mechanism is absent at the target segment
8592
// (soma has no ptrtest). Rejected, not a crash.
86-
int rc_absent = nrn_setpointer(src_sym, soma, 0.5, v_sym, soma, 0.5, err, sizeof(err));
93+
nrn_rangevar_push(v_sym, soma, 0.5);
94+
int rc_absent = nrn_setpointer_pop(src_sym, soma, 0.5, err, sizeof(err));
8795
ok &= check(rc_absent != 0, "POINTER target on a segment without the mechanism is rejected");
8896

97+
// Stack hygiene: every call above (success and error paths) consumed
98+
// exactly the one source it was given. Push a sentinel and confirm it comes
99+
// straight back, proving nothing was left on or over-popped from the stack.
100+
nrn_double_push(17.0);
101+
ok &= eq(nrn_double_pop(), 17.0, "the stack is balanced after the setpointer_pop calls");
102+
89103
return ok ? 0 : 1;
90104
}

0 commit comments

Comments
 (0)