Operating System:
Linux 6.8.0-111-generic #111-Ubuntu
ROS version or commit hash:
Kilted
RMW implementation (if applicable):
rmw_cyclonedds_cpp
Steps to reproduce issue
We produced a minimal working example you can pull and run to see the difference in parsing behaviour between composed/standalone nodes here: https://github.com/botsandus/yaml_nan_type_bug
The bare basics of what the above is doing is as follows:
- We ceate a parameter yaml containing a bare nan value:
node_name:
ros__parameters:
some_param: nan
- We create a node that declares a parameter and reads it as a double:
node->declare_parameter("some_param", 0.0,
rcl_interfaces::msg::ParameterDescriptor().set__dynamic_typing(true));
auto val = node->get_parameter("some_param").as_double();
- Launch it as a standalone node with --params-file:
ros2 launch yaml_nan_type_bug standalone.launch.py
The node starts successfully. The parameter is loaded as a double (NaN).
- Launch the same node as a composable node via LoadComposableNodes into a component_container:
ros2 launch yaml_nan_type_bug composed.launch.py
The node throws InvalidParameterTypeException: parameter 'some_param' has invalid type: expected [double] got [string] and the container crashes.
Expected behavior
We would expect that both launch methods should produce the same parameter type for the same yaml input. The nan/inf should be treated consistently, either as a double in both paths or as a string in both paths.
We discovered that in the yaml 1.1 spec, only dot-prefixed forms (like .nan, .NaN, .NAN, .inf, .Inf, .INF) are valid float literals, and bare nan is not a valid yaml float.
Actual behavior
- Standalone node:
rcl_yaml_param_parser parses bare nan as a double via strtod() fallback
- Composable node:
launch_ros uses yaml.safe_load() (part of pyyaml), which correctly treats bare nan as a string per the yaml 1.1 spec.
This means migrating a node from standalone to composable can silently change parameter types, causing crashes or behavioural differences. This is how we discovered the issue originally 🙀
Additional information
Root cause: In rcl_yaml_param_parser/src/parse.c, the float detecton in get_value() explicitly checks for yaml 1.1 dot-prefixed float forms, then falls through to strtod() for all other values. This makes the C parser accidentally more permissive than we might expect from the spec
How the two paths are different: Standalone nodes load parameters via --params-file, parsed by the C-level rcl_yaml_param_parser within the node process. Composable nodes can't do this because the container process is already running, so instead, launch_ros reads the yaml using yaml.safe_load(), and serialises the the values into the composition_interfaces/srv/LoadNode service request, and sends to the container. These two parsers disagree on whether bare nan/inf are floats or strings.
We investigated a bit more on this, and we do have a proposed fix, but I wanted to open this issue first so there could be some feedback, but what we think we can do is this:
Proposed fix: Reject strtod() results that are nan or inf, since these can only come from bare nan/inf (the valid dot-prefixed forms are already handled above). Approximately 2 lines added around here in rcl_yaml_param_parser we could do this:
dval = strtod(value, &endptr);
if (endptr != value && (isnan(dval) || isinf(dval))) {
endptr = (char *)value;
}
Operating System:
Linux 6.8.0-111-generic #111-UbuntuROS version or commit hash:
Kilted
RMW implementation (if applicable):
rmw_cyclonedds_cpp
Steps to reproduce issue
We produced a minimal working example you can pull and run to see the difference in parsing behaviour between composed/standalone nodes here: https://github.com/botsandus/yaml_nan_type_bug
The bare basics of what the above is doing is as follows:
The node starts successfully. The parameter is loaded as a double (NaN).
The node throws
InvalidParameterTypeException: parameter 'some_param' has invalid type: expected [double] got [string]and the container crashes.Expected behavior
We would expect that both launch methods should produce the same parameter type for the same yaml input. The nan/inf should be treated consistently, either as a double in both paths or as a string in both paths.
We discovered that in the yaml 1.1 spec, only dot-prefixed forms (like
.nan,.NaN,.NAN,.inf,.Inf,.INF) are valid float literals, and bare nan is not a valid yaml float.Actual behavior
rcl_yaml_param_parserparses barenanas a double viastrtod()fallbacklaunch_rosusesyaml.safe_load()(part of pyyaml), which correctly treats bare nan as a string per the yaml 1.1 spec.This means migrating a node from standalone to composable can silently change parameter types, causing crashes or behavioural differences. This is how we discovered the issue originally 🙀
Additional information
Root cause: In
rcl_yaml_param_parser/src/parse.c, the float detecton inget_value()explicitly checks for yaml 1.1 dot-prefixed float forms, then falls through tostrtod()for all other values. This makes the C parser accidentally more permissive than we might expect from the specHow the two paths are different: Standalone nodes load parameters via --params-file, parsed by the C-level
rcl_yaml_param_parserwithin the node process. Composable nodes can't do this because the container process is already running, so instead,launch_rosreads the yaml usingyaml.safe_load(), and serialises the the values into thecomposition_interfaces/srv/LoadNodeservice request, and sends to the container. These two parsers disagree on whether bare nan/inf are floats or strings.We investigated a bit more on this, and we do have a proposed fix, but I wanted to open this issue first so there could be some feedback, but what we think we can do is this:
Proposed fix: Reject strtod() results that are nan or inf, since these can only come from bare nan/inf (the valid dot-prefixed forms are already handled above). Approximately 2 lines added around here in
rcl_yaml_param_parserwe could do this: