Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/rcl_context_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ Napi::Value Init(const Napi::CallbackInfo& info) {
rcl_init(argc, argc > 0 ? argv : nullptr, &init_options, context),
rcl_get_error_string().str);

ThrowIfUnparsedROSArgs(env, jsArgv, context->global_arguments);
if (env.IsExceptionPending()) {
return env.Undefined();
}

THROW_ERROR_IF_NOT_EQUAL(
RCL_RET_OK, rcl_logging_configure(&context->global_arguments, &allocator),
rcl_get_error_string().str);
Expand Down
7 changes: 6 additions & 1 deletion src/rcl_node_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -193,12 +193,17 @@ Napi::Value CreateNode(const Napi::CallbackInfo& info) {
rcl_arguments_t arguments = rcl_get_zero_initialized_arguments();
rcl_ret_t ret =
rcl_parse_arguments(argc, argv, rcl_get_default_allocator(), &arguments);
if ((ret != RCL_RET_OK) || HasUnparsedROSArgs(arguments)) {
if (ret != RCL_RET_OK) {
Napi::Error::New(env, "failed to parse arguments")
.ThrowAsJavaScriptException();
return env.Undefined();
}

ThrowIfUnparsedROSArgs(env, jsArgv, arguments);
if (env.IsExceptionPending()) {
return env.Undefined();
}

RCPPUTILS_SCOPE_EXIT({
if (RCL_RET_OK != rcl_arguments_fini(&arguments)) {
Napi::Error::New(env, "failed to fini arguments")
Expand Down
46 changes: 44 additions & 2 deletions src/rcl_utilities.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include <cstdio>
#include <memory>
#include <rcpputils/scope_exit.hpp>
#include <string>

namespace {
Expand Down Expand Up @@ -344,9 +345,50 @@ void FreeArgs(char** argv, size_t argc) {
}
}

bool HasUnparsedROSArgs(const rcl_arguments_t& rcl_args) {
void ThrowIfUnparsedROSArgs(Napi::Env env, const Napi::Array& jsArgv,
const rcl_arguments_t& rcl_args) {
int unparsed_ros_args_count = rcl_arguments_get_count_unparsed_ros(&rcl_args);
return unparsed_ros_args_count != 0;

if (unparsed_ros_args_count < 0) {
Napi::Error::New(env, "Failed to count unparsed arguments")
.ThrowAsJavaScriptException();
return;
}
if (0 == unparsed_ros_args_count) {
return;
}

rcl_allocator_t allocator = rcl_get_default_allocator();
int* unparsed_indices_c = nullptr;
rcl_ret_t ret =
rcl_arguments_get_unparsed_ros(&rcl_args, allocator, &unparsed_indices_c);
if (RCL_RET_OK != ret) {
Napi::Error::New(env, "Failed to get unparsed arguments")
.ThrowAsJavaScriptException();
return;
}

RCPPUTILS_SCOPE_EXIT(
{ allocator.deallocate(unparsed_indices_c, allocator.state); });

std::string unparsed_args_str = "[";
for (int i = 0; i < unparsed_ros_args_count; ++i) {
int index = unparsed_indices_c[i];
if (index < 0 || static_cast<size_t>(index) >= jsArgv.Length()) {
Napi::Error::New(env, "Got invalid unparsed ROS arg index")
.ThrowAsJavaScriptException();
return;
}
std::string arg = jsArgv.Get(index).As<Napi::String>().Utf8Value();
unparsed_args_str += "'" + arg + "'";
if (i < unparsed_ros_args_count - 1) {
unparsed_args_str += ", ";
}
}
unparsed_args_str += "]";

Napi::Error::New(env, "Unknown ROS arguments: " + unparsed_args_str)
.ThrowAsJavaScriptException();
}

} // namespace rclnodejs
3 changes: 2 additions & 1 deletion src/rcl_utilities.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ char** AbstractArgsFromNapiArray(const Napi::Array& jsArgv);
// `AbstractArgsFromNapiArray` and `FreeArgs` must be called in pairs.
void FreeArgs(char** argv, size_t argc);

bool HasUnparsedROSArgs(const rcl_arguments_t& rcl_args);
void ThrowIfUnparsedROSArgs(Napi::Env env, const Napi::Array& jsArgv,
const rcl_arguments_t& rcl_args);

} // namespace rclnodejs

Expand Down
59 changes: 59 additions & 0 deletions test/test-unparsed-args.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) 2025, The Robot Web Tools Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

const assert = require('assert');
const rclnodejs = require('../index.js');

describe('rclnodejs unparsed ROS args', function () {
this.timeout(10 * 1000);

beforeEach(function () {
rclnodejs.shutdown();
});

afterEach(function () {
rclnodejs.shutdown();
});

it('should throw error when initializing context with unknown ROS args', async function () {
const args = ['--ros-args', '--unknown-arg'];
try {
await rclnodejs.init(rclnodejs.Context.defaultContext(), args);
assert.fail('Should have thrown an error');
} catch (e) {
assert.ok(e.message.includes('Unknown ROS arguments'));
assert.ok(e.message.includes('--unknown-arg'));
}
});

it('should throw error when creating node with unknown ROS args', async function () {
await rclnodejs.init();
const args = ['--ros-args', '--unknown-arg'];
try {
rclnodejs.createNode(
'test_node',
'',
rclnodejs.Context.defaultContext(),
rclnodejs.NodeOptions.defaultOptions,
args
);
assert.fail('Should have thrown an error');
} catch (e) {
assert.ok(e.message.includes('Unknown ROS arguments'));
assert.ok(e.message.includes('--unknown-arg'));
}
});
});
Loading