Skip to content

Commit 40a276b

Browse files
authored
Fail fast on invalid ROS arguments (ros2#493)
* Fail fast on invalid ROS arguments. Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com> * Address peer review comments. Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com> * Fix warning on Windows. Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
1 parent 2740a43 commit 40a276b

File tree

4 files changed

+91
-47
lines changed

4 files changed

+91
-47
lines changed

rcl/include/rcl/arguments.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ rcl_get_zero_initialized_arguments(void);
8080
* `warn`, not case sensitive.
8181
* If multiple of these rules are found, the last one parsed will be used.
8282
*
83+
* If an argument does not appear to be a valid ROS argument e.g. a `-r/--remap` flag followed by
84+
* anything but a valid remap rule, parsing will fail immediately.
85+
*
8386
* If an argument does not appear to be a known ROS argument, then it is skipped and left unparsed.
8487
*
8588
* \sa rcl_arguments_get_count_unparsed_ros()
@@ -104,6 +107,7 @@ rcl_get_zero_initialized_arguments(void);
104107
* \param[out] args_output A structure that will contain the result of parsing.
105108
* Must be zero initialized before use.
106109
* \return `RCL_RET_OK` if the arguments were parsed successfully, or
110+
* \return `RCL_RET_INVALID_ROS_ARGS` if an invalid ROS argument is found, or
107111
* \return `RCL_RET_INVALID_ARGUMENT` if any function arguments are invalid, or
108112
* \return `RCL_RET_BAD_ALLOC` if allocating memory failed, or
109113
* \return `RCL_RET_ERROR` if an unspecified error occurs.
@@ -190,9 +194,9 @@ int
190194
rcl_arguments_get_count_unparsed_ros(
191195
const rcl_arguments_t * args);
192196

193-
/// Return a list of indices to ROS specific arguments that were not successfully parsed.
197+
/// Return a list of indices to unknown ROS specific arguments that were left unparsed.
194198
/**
195-
* Some ROS specific arguments may not have been successfully parsed, or were not intended to be
199+
* Some ROS specific arguments may not have been recognized, or were not intended to be
196200
* parsed by rcl.
197201
* This function populates an array of indices to these arguments in the original argv array.
198202
*

rcl/include/rcl/types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,8 @@ typedef rmw_ret_t rcl_ret_t;
9696
#define RCL_RET_INVALID_REMAP_RULE 1001
9797
/// Expected one type of lexeme but got another
9898
#define RCL_RET_WRONG_LEXEME 1002
99+
/// Found invalid ros argument while parsing
100+
#define RCL_RET_INVALID_ROS_ARGS 1003
99101
/// Argument is not a valid parameter rule
100102
#define RCL_RET_INVALID_PARAM_RULE 1010
101103
/// Argument is not a valid log level rule

rcl/src/rcl/arguments.c

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -310,18 +310,22 @@ rcl_parse_arguments(
310310
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME, "param override rule : %s\n", argv[i + 1]);
311311
i += 1; // Skip flag here, for loop will skip rule.
312312
continue;
313-
} else {
314-
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
315-
"Couldn't parse arg %d (%s) as parameter override rule. Error: %s", i + 1,
316-
argv[i + 1], rcl_get_error_string().str);
317313
}
314+
rcl_error_string_t prev_error_string = rcl_get_error_string();
315+
rcl_reset_error();
316+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
317+
"Couldn't parse parameter override rule: '%s %s'. Error: %s", argv[i], argv[i + 1],
318+
prev_error_string.str);
318319
} else {
319-
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
320-
"Couldn't parse arg %d (%s) as parameter override flag. No rule found.\n",
321-
i, argv[i]);
320+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
321+
"Couldn't parse trailing parameter override rule: '%s'. No rule found.", argv[i]);
322322
}
323-
rcl_reset_error();
323+
ret = RCL_RET_INVALID_ROS_ARGS;
324+
goto fail;
324325
}
326+
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
327+
"Couldn't parse arg %d (%s) as parameter override rule. Not a '%s' or '%s' flag.", i,
328+
argv[i], RCL_PARAM_FLAG, RCL_SHORT_PARAM_FLAG);
325329

326330
// Attempt to parse argument as remap rule flag
327331
if (strcmp(RCL_REMAP_FLAG, argv[i]) == 0 || strcmp(RCL_SHORT_REMAP_FLAG, argv[i]) == 0) {
@@ -335,16 +339,21 @@ rcl_parse_arguments(
335339
i += 1; // Skip flag here, for loop will skip rule.
336340
continue;
337341
}
338-
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
339-
"Couldn't parse arg %d (%s) as remap rule. Error: %s", i + 1, argv[i + 1],
340-
rcl_get_error_string().str);
342+
rcl_error_string_t prev_error_string = rcl_get_error_string();
341343
rcl_reset_error();
344+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
345+
"Couldn't parse remap rule: '%s %s'. Error: %s", argv[i], argv[i + 1],
346+
prev_error_string.str);
342347
} else {
343-
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
344-
"Couldn't parse arg %d (%s) as remap rule flag. No rule found.\n",
345-
i, argv[i]);
348+
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
349+
"Couldn't parse trailing remap rule: '%s'. No rule found.", argv[i]);
346350
}
351+
ret = RCL_RET_INVALID_ROS_ARGS;
352+
goto fail;
347353
}
354+
RCUTILS_LOG_DEBUG_NAMED(ROS_PACKAGE_NAME,
355+
"Couldn't parse arg %d (%s) as remap rule. Not a '%s' or '%s' flag.", i,
356+
argv[i], RCL_REMAP_FLAG, RCL_SHORT_REMAP_FLAG);
348357

349358
// Attempt to parse argument as parameter file rule
350359
args_impl->parameter_files[args_impl->num_param_files_args] = NULL;

rcl/test/rcl/test_arguments.cpp

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,37 +145,11 @@ TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), check_known_vs_unkno
145145
EXPECT_TRUE(are_known_ros_args({"--ros-args", "-p", "/foo123:=/bar123"}));
146146
EXPECT_TRUE(are_known_ros_args({"--ros-args", "__params:=file_name.yaml"}));
147147

148-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r"}));
149-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "--remap"}));
150-
151-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", ":="}));
152-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "foo:="}));
153-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", ":=bar"}));
154-
155-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p"}));
156-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "--param"}));
157-
158-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p", ":="}));
159-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p", "foo:="}));
160-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p", ":=bar"}));
161-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "__ns:="}));
162-
163-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "__node:="}));
164-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "__node:=/foo/bar"}));
165-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "__ns:=foo"}));
166-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", ":__node:=nodename"}));
167-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "~:__node:=nodename"}));
168-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "}foo:=/bar"}));
169-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "f oo:=/bar"}));
170-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "foo:=/b ar"}));
171-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "f{oo:=/bar"}));
172-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "foo:=/b}ar"}));
173-
174-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p", "}foo:=/bar"}));
175-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-p", "f oo:=/bar"}));
176-
177-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "rostopic://:=rosservice"}));
178-
EXPECT_FALSE(are_known_ros_args({"--ros-args", "-r", "rostopic::=rosservice"}));
148+
EXPECT_FALSE(are_known_ros_args({"--ros-args", "--custom-ros-arg"}));
149+
EXPECT_FALSE(are_known_ros_args({"--ros-args", "__node:=node_name"}));
150+
EXPECT_FALSE(are_known_ros_args({"--ros-args", "old_name:__node:=node_name"}));
151+
EXPECT_FALSE(are_known_ros_args({"--ros-args", "/foo/bar:=bar"}));
152+
EXPECT_FALSE(are_known_ros_args({"--ros-args", "foo:=/bar"}));
179153
EXPECT_FALSE(are_known_ros_args({"--ros-args", "__param:=file_name.yaml"}));
180154

181155
// Setting logger level
@@ -194,6 +168,61 @@ TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), check_known_vs_unkno
194168
EXPECT_FALSE(are_known_ros_args({"--ros-args", "__log_level:=foo"}));
195169
}
196170

171+
bool
172+
are_valid_ros_args(std::vector<const char *> argv)
173+
{
174+
const int argc = static_cast<int>(argv.size());
175+
rcl_arguments_t parsed_args = rcl_get_zero_initialized_arguments();
176+
rcl_ret_t ret = rcl_parse_arguments(
177+
argc, argv.data(), rcl_get_default_allocator(), &parsed_args);
178+
if (RCL_RET_OK != ret) {
179+
EXPECT_EQ(ret, RCL_RET_INVALID_ROS_ARGS) << rcl_get_error_string().str;
180+
rcl_reset_error();
181+
return false;
182+
}
183+
EXPECT_EQ(RCL_RET_OK, rcl_arguments_fini(&parsed_args));
184+
return true;
185+
}
186+
187+
TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), check_valid_vs_invalid_args) {
188+
EXPECT_TRUE(are_valid_ros_args({"--ros-args", "-p", "foo:=bar", "-r", "__node:=node_name"}));
189+
190+
// ROS args unknown to rcl are not (necessarily) invalid
191+
EXPECT_TRUE(are_valid_ros_args({"--ros-args", "--custom-ros-arg"}));
192+
193+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r"}));
194+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "--remap"}));
195+
196+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", ":="}));
197+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "foo:="}));
198+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", ":=bar"}));
199+
200+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p"}));
201+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "--param"}));
202+
203+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p", ":="}));
204+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p", "foo:="}));
205+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p", ":=bar"}));
206+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "__ns:="}));
207+
208+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "__node:="}));
209+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "__node:=/foo/bar"}));
210+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "__ns:=foo"}));
211+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", ":__node:=nodename"}));
212+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "~:__node:=nodename"}));
213+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "}foo:=/bar"}));
214+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "f oo:=/bar"}));
215+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "foo:=/b ar"}));
216+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "f{oo:=/bar"}));
217+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "foo:=/b}ar"}));
218+
219+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p", "}foo:=/bar"}));
220+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-p", "f oo:=/bar"}));
221+
222+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "rostopic://:=rosservice"}));
223+
EXPECT_FALSE(are_valid_ros_args({"--ros-args", "-r", "rostopic::=rosservice"}));
224+
}
225+
197226
TEST_F(CLASSNAME(TestArgumentsFixture, RMW_IMPLEMENTATION), test_no_args) {
198227
rcl_arguments_t parsed_args = rcl_get_zero_initialized_arguments();
199228
rcl_ret_t ret = rcl_parse_arguments(0, NULL, rcl_get_default_allocator(), &parsed_args);

0 commit comments

Comments
 (0)