Hi,
Please find the latest report on new defect(s) introduced to skupper-router found with Coverity Scan.
19 new defect(s) introduced to skupper-router found with Coverity Scan.
9 defect(s), reported by Coverity Scan earlier, were marked fixed in the recent build analyzed by Coverity Scan.
New defect(s) Reported-by: Coverity Scan
Showing 19 of 19 defect(s)
** CID 460515: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/alloc_pool.c: 596 in qd_alloc_finalize()
________________________________________________________________________________________________________
*** CID 460515: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/alloc_pool.c: 596 in qd_alloc_finalize()
590 tpool = DEQ_HEAD(desc->tpool_list);
591 }
592
593 //
594 // Check the stats for lost items
595 //
>>> CID 460515: Concurrent data access violations (MISSING_LOCK)
>>> Accessing "desc->stats.total_alloc_from_heap" without holding lock "sys_mutex_t.mutex". Elsewhere, "qd_alloc_stats_t.total_alloc_from_heap" is written to with "sys_mutex_t.mutex" held 1 out of 1 times.
596 if (dump_file && desc->stats.total_free_to_heap < desc->stats.total_alloc_from_heap) {
597 bool suppressed = false;
598 for (int i = 0; leaking_types[i]; ++i) {
599 if (strcmp(desc->type_name, leaking_types[i]) == 0) {
600 suppressed = true;
601 break;
** CID 460514: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/adaptors/tcp/tcp_adaptor.c: 567 in close_connection_XSIDE_IO()
________________________________________________________________________________________________________
*** CID 460514: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/adaptors/tcp/tcp_adaptor.c: 567 in close_connection_XSIDE_IO()
561 drain_write_buffers_XSIDE_IO(conn->raw_conn);
562
563 // note: this disables the raw connection event handler. No further PN_RAW_CONNECTION_* events will occur,
564 // including DISCONNECTED!
565 sys_mutex_lock(&conn->activation_lock);
566 pn_raw_connection_set_context(conn->raw_conn, 0);
>>> CID 460514: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "raw_conn" to a new value. Now the two threads have an inconsistent view of "raw_conn" and updates to fields of "raw_conn" or fields correlated with "raw_conn" may be lost.
567 conn->raw_conn = 0;
568 sys_mutex_unlock(&conn->activation_lock);
569 }
570
571 free(conn->reply_to);
572
** CID 460513: (LOCK_EVASION)
/skupper-router/src/message.c: 2177 in qd_message_send()
/skupper-router/src/message.c: 2178 in qd_message_send()
________________________________________________________________________________________________________
*** CID 460513: (LOCK_EVASION)
/skupper-router/src/message.c: 2177 in qd_message_send()
2171 content->q2_input_holdoff = false;
2172 q2_unblock = content->q2_unblocker;
2173 }
2174 }
2175 } // end free buffer
2176
>>> CID 460513: (LOCK_EVASION)
>>> Thread1 sets "buffer" to a new value. Now the two threads have an inconsistent view of "buffer" and updates to fields of "buffer" or fields correlated with "buffer" may be lost.
2177 msg->cursor.buffer = next_buf;
2178 msg->cursor.cursor = (next_buf) ? qd_buffer_base(next_buf) : 0;
2179
2180 SET_ATOMIC_BOOL(&msg->send_complete, (complete && !next_buf && !IS_ATOMIC_FLAG_SET(&content->uct_enabled)));
2181 }
2182
/skupper-router/src/message.c: 2178 in qd_message_send()
2172 q2_unblock = content->q2_unblocker;
2173 }
2174 }
2175 } // end free buffer
2176
2177 msg->cursor.buffer = next_buf;
>>> CID 460513: (LOCK_EVASION)
>>> Thread1 sets "cursor" to a new value. Now the two threads have an inconsistent view of "cursor" and updates to fields correlated with "cursor" may be lost.
2178 msg->cursor.cursor = (next_buf) ? qd_buffer_base(next_buf) : 0;
2179
2180 SET_ATOMIC_BOOL(&msg->send_complete, (complete && !next_buf && !IS_ATOMIC_FLAG_SET(&content->uct_enabled)));
2181 }
2182
2183 buf = next_buf;
** CID 460512: (ATOMICITY)
/skupper-router/src/timer.c: 328 in qd_timer_visit()
/skupper-router/src/timer.c: 328 in qd_timer_visit()
________________________________________________________________________________________________________
*** CID 460512: (ATOMICITY)
/skupper-router/src/timer.c: 328 in qd_timer_visit()
322 // expect blocked caller sets timer->state
323 } else if (timer->state == QD_TIMER_STATE_RUNNING) {
324 timer->state = QD_TIMER_STATE_IDLE;
325 }
326
327 // now drop scheduled_timers reference:
>>> CID 460512: (ATOMICITY)
>>> Using an unreliable value of "timer" inside the second locked section. If the data that "timer" depends on was changed by another thread, this use might be incorrect.
328 timer_decref_LH(timer);
329 timer = DEQ_HEAD(scheduled_timers);
330 }
331 qd_timer_t *first = DEQ_HEAD(scheduled_timers);
332 if (first) {
333 qd_server_timeout(first->server, first->delta_time);
334 }
335 callback_thread = 0;
336 sys_mutex_unlock(&lock);
/skupper-router/src/timer.c: 328 in qd_timer_visit()
322 // expect blocked caller sets timer->state
323 } else if (timer->state == QD_TIMER_STATE_RUNNING) {
324 timer->state = QD_TIMER_STATE_IDLE;
325 }
326
327 // now drop scheduled_timers reference:
>>> CID 460512: (ATOMICITY)
>>> Using an unreliable value of "timer" inside the second locked section. If the data that "timer" depends on was changed by another thread, this use might be incorrect.
328 timer_decref_LH(timer);
329 timer = DEQ_HEAD(scheduled_timers);
330 }
331 qd_timer_t *first = DEQ_HEAD(scheduled_timers);
332 if (first) {
333 qd_server_timeout(first->server, first->delta_time);
334 }
335 callback_thread = 0;
336 sys_mutex_unlock(&lock);
** CID 460511: Performance inefficiencies (COPY_INSTEAD_OF_MOVE)
/skupper-router/tests/cpp/doctest/doctest.h: 6662 in doctest::Context::parseArgs(int, const char *const *, bool)()
________________________________________________________________________________________________________
*** CID 460511: Performance inefficiencies (COPY_INSTEAD_OF_MOVE)
/skupper-router/tests/cpp/doctest/doctest.h: 6662 in doctest::Context::parseArgs(int, const char *const *, bool)()
6656 parseOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", &strRes, default) || \
6657 withDefaults) \
6658 p->var = strRes
6659
6660 // clang-format off
6661 DOCTEST_PARSE_STR_OPTION("out", "o", out, "");
>>> CID 460511: Performance inefficiencies (COPY_INSTEAD_OF_MOVE)
>>> "strRes" is copied in a call to copy assignment "operator =", when it could be moved instead.
6662 DOCTEST_PARSE_STR_OPTION("order-by", "ob", order_by, "file");
6663 DOCTEST_PARSE_INT_OPTION("rand-seed", "rs", rand_seed, 0);
6664
6665 DOCTEST_PARSE_INT_OPTION("first", "f", first, 0);
6666 DOCTEST_PARSE_INT_OPTION("last", "l", last, UINT_MAX);
6667
** CID 460510: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/router_core/transfer.c: 971 in qdr_link_issue_credit_CT()
________________________________________________________________________________________________________
*** CID 460510: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/router_core/transfer.c: 971 in qdr_link_issue_credit_CT()
965 sys_mutex_unlock(&conn->work_lock);
966
967 // need a new work flow item
968 work = qdr_link_work(QDR_LINK_WORK_FLOW);
969 work->value = credit;
970 if (drain_changed)
>>> CID 460510: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "drain_action" to a new value. Now the two threads have an inconsistent view of "drain_action" and updates to fields correlated with "drain_action" may be lost.
971 work->drain_action = drain_action;
972 qdr_link_enqueue_work_CT(core, link, work);
973 }
974 }
975
976
** CID 460509: Memory - illegal accesses (BUFFER_SIZE)
/skupper-router/src/adaptors/amqp/qd_connector.c: 269 in qd_connector_handle_transport_error()
________________________________________________________________________________________________________
*** CID 460509: Memory - illegal accesses (BUFFER_SIZE)
/skupper-router/src/adaptors/amqp/qd_connector.c: 269 in qd_connector_handle_transport_error()
263 // This is done so we don't flood the log with connection failure error messages
264 // Even though we restrict the number of times the error message is displayed, the
265 // router will still keep trying to connect to the host/port specified in the connector.
266 //
267 bool log_error_message = false;
268 if (strcmp(connector->conn_msg, conn_msg_1) != 0) {
>>> CID 460509: Memory - illegal accesses (BUFFER_SIZE)
>>> Calling "strncpy" with a maximum size argument of 300 bytes on destination array "connector->conn_msg" of size 300 bytes might leave the destination string unterminated.
269 strncpy(connector->conn_msg, conn_msg_1, QD_CXTR_CONN_MSG_BUF_SIZE);
270 log_error_message = true;
271 }
272 sys_mutex_unlock(&connector->lock);
273 if (log_error_message) {
274 qd_log(LOG_SERVER, QD_LOG_ERROR, "%s", conn_msg);
** CID 460508: (LOCK_EVASION)
/skupper-router/src/message.c: 3021 in qd_message_stream_data_release()
/skupper-router/src/message.c: 3021 in qd_message_stream_data_release()
________________________________________________________________________________________________________
*** CID 460508: (LOCK_EVASION)
/skupper-router/src/message.c: 3021 in qd_message_stream_data_release()
3015 //
3016 while (start_buf != stop_buf) {
3017 qd_buffer_t *next = DEQ_NEXT(start_buf);
3018 uint32_t refcnt = (fanout) ? qd_buffer_dec_fanout(start_buf) : 1;
3019 assert(refcnt > 0);
3020 if (refcnt == 1) {
>>> CID 460508: (LOCK_EVASION)
>>> Thread1 sets "next" to a new value. Now the two threads have an inconsistent view of "next" and updates to fields of "next" or fields correlated with "next" may be lost.
3021 DEQ_REMOVE(content->buffers, start_buf);
3022 qd_buffer_free(start_buf);
3023 }
3024 start_buf = next;
3025 }
3026
/skupper-router/src/message.c: 3021 in qd_message_stream_data_release()
3015 //
3016 while (start_buf != stop_buf) {
3017 qd_buffer_t *next = DEQ_NEXT(start_buf);
3018 uint32_t refcnt = (fanout) ? qd_buffer_dec_fanout(start_buf) : 1;
3019 assert(refcnt > 0);
3020 if (refcnt == 1) {
>>> CID 460508: (LOCK_EVASION)
>>> Thread1 sets "next" to a new value. Now the two threads have an inconsistent view of "next" and updates to fields of "next" or fields correlated with "next" may be lost.
3021 DEQ_REMOVE(content->buffers, start_buf);
3022 qd_buffer_free(start_buf);
3023 }
3024 start_buf = next;
3025 }
3026
** CID 460507: Error handling issues (UNCAUGHT_EXCEPT)
/skupper-router/tests/cpp/helpers/c_unittests_main.cpp: 59 in main()
________________________________________________________________________________________________________
*** CID 460507: Error handling issues (UNCAUGHT_EXCEPT)
/skupper-router/tests/cpp/helpers/c_unittests_main.cpp: 59 in main()
53 }
54
55 return true;
56 }
57
58 // https://u15810271.ct.sendgrid.net/ls/click?upn=u001.AxU2LYlgjL6eX23u9ErQyyNSspTYyd4yjF3xjE2jxk4imLjDWJ1OKfEUVq9JhyGZWwMLaQ6Lso6t1JWIRqt9ejaHdvkduZ0eK-2B-2BLdCVHXnw-3DsS3l_Pag-2BHLSuBz-2FzNbiMtAiYoz0cUoMkpXNw-2FNhIOeq9RVQtG8uBeltJbARXziJ0zhyxEC-2FXl5uCEa-2BWm70ntRsdjQb9y2jMdJQM0tIa1jioNh7OgErpzai2Wfk3M3Ytu5I0x9mj1cbK5Tk1Ly9PwqPcghtHdrenIBe0v0QqYzcaT3ICz9aeI4pPMsnEGCFx4tnD2OIfce67-2Bx4G4Wd3I5azEw-3D-3D
>>> CID 460507: Error handling issues (UNCAUGHT_EXCEPT)
>>> In function "main(int, char **)" an exception of type "char const *" is thrown and never caught.
59 int main(int argc, char** argv)
60 {
61 doctest::Context context;
62
63 if (!check_stubbing_works()) {
64 #ifdef QD_REQUIRE_STUBBING_WORKS
** CID 460506: Concurrent data access violations (MISSING_LOCK)
/skupper-router/tests/cpp/helpers/helpers.hpp: 170 in RouterStartupLatch::wait_for_qdr(qd_dispatch_t *)::[lambda() (instance 1)]::operator ()() const()
________________________________________________________________________________________________________
*** CID 460506: Concurrent data access violations (MISSING_LOCK)
/skupper-router/tests/cpp/helpers/helpers.hpp: 170 in RouterStartupLatch::wait_for_qdr(qd_dispatch_t *)::[lambda() (instance 1)]::operator ()() const()
164 };
165 qdr_action_t *action = qdr_action(action_handler, "RouterStartupLatch action");
166 action->args.general.context_1 = this;
167 qdr_action_enqueue(qd->router->router_core, action);
168
169 std::unique_lock<std::mutex> lock(mut);
>>> CID 460506: Concurrent data access violations (MISSING_LOCK)
>>> Accessing "this->this->done" without holding lock "RouterStartupLatch.mut". Elsewhere, "RouterStartupLatch.done" is written to with "RouterStartupLatch.mut" held 1 out of 1 times (1 of these accesses strongly imply that it is necessary).
170 cv.wait(lock, [this] { return done; }); // wait for action_handler to notify us
171 }
172 };
173
174 inline std::string get_env(std::string const &key)
175 {
** CID 460505: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/message.c: 1835 in qd_message_receive()
________________________________________________________________________________________________________
*** CID 460505: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/message.c: 1835 in qd_message_receive()
1829 // Pending buffer exists
1830 if (qd_buffer_capacity(content->pending) == 0) {
1831 // Pending buffer is full
1832 LOCK(&content->lock);
1833 qd_buffer_set_fanout(content->pending, content->fanout);
1834 DEQ_INSERT_TAIL(content->buffers, content->pending);
>>> CID 460505: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "pending" to a new value. Now the two threads have an inconsistent view of "pending" and updates to fields of "pending" or fields correlated with "pending" may be lost.
1835 content->pending = 0;
1836 if (_Q2_holdoff_should_block_LH(content)) {
1837 if (!qd_link_is_q2_limit_unbounded(qdl)) {
1838 content->q2_input_holdoff = true;
1839 UNLOCK(&content->lock);
1840 break;
** CID 460504: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/vanflow.c: 1318 in _vflow_thread_TH()
________________________________________________________________________________________________________
*** CID 460504: Data race undermines locking (LOCK_EVASION)
/skupper-router/src/vanflow.c: 1318 in _vflow_thread_TH()
1312
1313 //
1314 // Process the local work list with the lock not held
1315 //
1316 vflow_work_t *work = DEQ_HEAD(local_work_list);
1317 while (work) {
>>> CID 460504: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "head" to a new value. Now the two threads have an inconsistent view of "head" and updates to fields of "head" or fields correlated with "head" may be lost.
1318 DEQ_REMOVE_HEAD(local_work_list);
1319 if (!!work->handler) {
1320 work->handler(work, !running);
1321 } else {
1322 //
1323 // The thread is signalled to exit by posting work with a null handler.
** CID 460503: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/adaptors/amqp/qd_connector.c: 108 in try_open_cb()
________________________________________________________________________________________________________
*** CID 460503: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/adaptors/amqp/qd_connector.c: 108 in try_open_cb()
102 // CONNECTOR - ENTITY_CACHE lock inversion deadlock window.
103 qd_connection_t *ctx = new_qd_connection_t();
104 if (!ctx) {
105 qd_log(LOG_SERVER, QD_LOG_CRITICAL, "Allocation failure connecting to %s", ct->config.host_port);
106 ct->delay = 10000;
107 ct->state = CXTR_STATE_CONNECTING;
>>> CID 460503: Concurrent data access violations (MISSING_LOCK)
>>> Accessing "ct->timer" without holding lock "sys_mutex_t.mutex". Elsewhere, "qd_connector_t.timer" is written to with "sys_mutex_t.mutex" held 1 out of 1 times.
108 qd_timer_schedule(ct->timer, ct->delay);
109 return;
110 }
111
112 sys_mutex_lock(&ct->lock);
113
** CID 460502: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/adaptors/amqp/connection_manager.c: 483 in qd_connection_manager_free()
________________________________________________________________________________________________________
*** CID 460502: Concurrent data access violations (MISSING_LOCK)
/skupper-router/src/adaptors/amqp/connection_manager.c: 483 in qd_connection_manager_free()
477 // will not initiate a re-connect once we drop
478 // the lock
479 connector->state = CXTR_STATE_DELETED;
480 sys_mutex_unlock(&connector->lock);
481 // cannot cancel timer while holding lock since the
482 // callback takes the lock
>>> CID 460502: Concurrent data access violations (MISSING_LOCK)
>>> Accessing "connector->timer" without holding lock "sys_mutex_t.mutex". Elsewhere, "qd_connector_t.timer" is written to with "sys_mutex_t.mutex" held 1 out of 1 times.
483 qd_timer_cancel(connector->timer);
484 qd_connector_decref(connector);
485
486 connector = DEQ_HEAD(to_free);
487 }
488
** CID 460501: Data race undermines locking (LOCK_EVASION)
/skupper-router/tests/message_test.c: 1758 in test_q2_ignore_headers()
________________________________________________________________________________________________________
*** CID 460501: Data race undermines locking (LOCK_EVASION)
/skupper-router/tests/message_test.c: 1758 in test_q2_ignore_headers()
1752 }
1753
1754 // now remove buffers until Q2 is relieved
1755 sys_mutex_lock(&content->lock);
1756 while (!_Q2_holdoff_should_unblock_LH(content)) {
1757 qd_buffer_t *buffy = DEQ_TAIL(content->buffers);
>>> CID 460501: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "size" to a new value. Now the two threads have an inconsistent view of "size" and updates to fields correlated with "size" may be lost.
1758 DEQ_REMOVE_TAIL(content->buffers);
1759 qd_buffer_free(buffy);
1760 }
1761 sys_mutex_unlock(&content->lock);
1762
1763 // expect: Q2 deactivates when the non-header buffer count falls below QD_QLIMIT_Q2_LOWER
** CID 460500: Null pointer dereferences (FORWARD_NULL)
/skupper-router/src/adaptors/adaptor_tls.c: 1385 in qd_tls_do_io2()
________________________________________________________________________________________________________
*** CID 460500: Null pointer dereferences (FORWARD_NULL)
/skupper-router/src/adaptors/adaptor_tls.c: 1385 in qd_tls_do_io2()
1379 } else {
1380 qd_buffer_free(abuf);
1381 }
1382 }
1383 if (taken) {
1384 work = true; // more capacity for decrypted output
>>> CID 460500: Null pointer dereferences (FORWARD_NULL)
>>> Dereferencing null pointer "input_data_count".
1385 *input_data_count += total_octets;
1386 qd_log(tls->log_module, QD_LOG_DEBUG,
1387 "[C%" PRIu64 "] %" PRIu64 " decrypted bytes taken from TLS for adaptor input (%zu buffers)",
1388 tls->conn_id, total_octets, taken);
1389 }
1390 }
** CID 423559: Concurrent data access violations (MISSING_LOCK)
/usr/include/c++/13/bits/unique_lock.h: 137 in std::unique_lock<std::mutex>::lock()()
________________________________________________________________________________________________________
*** CID 423559: Concurrent data access violations (MISSING_LOCK)
/usr/include/c++/13/bits/unique_lock.h: 137 in std::unique_lock<std::mutex>::lock()()
131
132 void
133 lock()
134 {
135 if (!_M_device)
136 __throw_system_error(int(errc::operation_not_permitted));
>>> CID 423559: Concurrent data access violations (MISSING_LOCK)
>>> Accessing "this->_M_owns" without holding lock "std::unique_lock<std::mutex>._M_device". Elsewhere, "std::unique_lock<std::mutex>._M_owns" is written to with "unique_lock._M_device" held 1 out of 2 times (1 of these accesses strongly imply that it is necessary).
137 else if (_M_owns)
138 __throw_system_error(int(errc::resource_deadlock_would_occur));
139 else
140 {
141 _M_device->lock();
142 _M_owns = true;
** CID 423547: Data race undermines locking (LOCK_EVASION)
/usr/include/c++/13/bits/unique_lock.h: 142 in std::unique_lock<std::mutex>::lock()()
________________________________________________________________________________________________________
*** CID 423547: Data race undermines locking (LOCK_EVASION)
/usr/include/c++/13/bits/unique_lock.h: 142 in std::unique_lock<std::mutex>::lock()()
136 __throw_system_error(int(errc::operation_not_permitted));
137 else if (_M_owns)
138 __throw_system_error(int(errc::resource_deadlock_would_occur));
139 else
140 {
141 _M_device->lock();
>>> CID 423547: Data race undermines locking (LOCK_EVASION)
>>> Thread1 sets "_M_owns" to a new value. Now the two threads have an inconsistent view of "_M_owns" and updates to fields correlated with "_M_owns" may be lost.
142 _M_owns = true;
143 }
144 }
145
146 _GLIBCXX_NODISCARD
147 bool
** CID 420489: Uninitialized variables (USE_AFTER_MOVE)
/usr/include/c++/13/bits/hashtable.h: 904 in std::_Hashtable<unsigned long long, unsigned long long, std::allocator<unsigned long long>, std::__detail::_Identity, std::equal_to<unsigned long long>, std::hash<unsigned long long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<(bool)0, (bool)1, (bool)1>>::_M_insert_unique_aux<unsigned long long, std::__detail::_AllocNode<std::allocator<std::__detail::_Hash_node<unsigned long long, (bool)0>>>>(T1 &&, const T2 &)()
________________________________________________________________________________________________________
*** CID 420489: Uninitialized variables (USE_AFTER_MOVE)
/usr/include/c++/13/bits/hashtable.h: 904 in std::_Hashtable<unsigned long long, unsigned long long, std::allocator<unsigned long long>, std::__detail::_Identity, std::equal_to<unsigned long long>, std::hash<unsigned long long>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<(bool)0, (bool)1, (bool)1>>::_M_insert_unique_aux<unsigned long long, std::__detail::_AllocNode<std::allocator<std::__detail::_Hash_node<unsigned long long, (bool)0>>>>(T1 &&, const T2 &)()
898 { return std::move(__k); }
899
900 template<typename _Arg, typename _NodeGenerator>
901 std::pair<iterator, bool>
902 _M_insert_unique_aux(_Arg&& __arg, const _NodeGenerator& __node_gen)
903 {
>>> CID 420489: Uninitialized variables (USE_AFTER_MOVE)
>>> "__arg" is used after it has been already moved.
904 return _M_insert_unique(
905 _S_forward_key(_ExtractKey{}(std::forward<_Arg>(__arg))),
906 std::forward<_Arg>(__arg), __node_gen);
907 }
908
909 template<typename _Arg, typename _NodeGenerator>