From 1ca9ecb38438b3b631fede5d11af28793275da21 Mon Sep 17 00:00:00 2001 From: Pavel Pletenev Date: Mon, 4 Jul 2022 14:34:21 +0300 Subject: [PATCH 01/20] Fix missing array capacity constant generated --- src/nunavut/lang/cpp/templates/_fields.j2 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index 712fed04..57ad7e97 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -11,4 +11,7 @@ {% endif -%} {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} {{ field.data_type | declaration }} {{ field | id }}; + {%- if f.data_type is VariableLengthArrayType %} + static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ f.data_type.capacity }}U; + {%- endif %} {%- endfor -%} From 2561e1c52bf67847b803efb6a25a87e2d2a946b8 Mon Sep 17 00:00:00 2001 From: Pavel Pletenev Date: Mon, 4 Jul 2022 15:26:51 +0300 Subject: [PATCH 02/20] Fix typo --- src/nunavut/lang/cpp/templates/_fields.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index 57ad7e97..d84d7940 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -11,7 +11,7 @@ {% endif -%} {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} {{ field.data_type | declaration }} {{ field | id }}; - {%- if f.data_type is VariableLengthArrayType %} + {%- if field.data_type is VariableLengthArrayType %} static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ f.data_type.capacity }}U; {%- endif %} {%- endfor -%} From 366268a020a7179745e2d7e018d058d59533b7e5 Mon Sep 17 00:00:00 2001 From: Pavel Pletenev Date: Mon, 4 Jul 2022 15:32:14 +0300 Subject: [PATCH 03/20] Fix typo[2] --- src/nunavut/lang/cpp/templates/_fields.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index d84d7940..5741e8ee 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -12,6 +12,6 @@ {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} {{ field.data_type | declaration }} {{ field | id }}; {%- if field.data_type is VariableLengthArrayType %} - static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ f.data_type.capacity }}U; + static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ field.data_type.capacity }}U; {%- endif %} {%- endfor -%} From 1a14a4170e50a2a167343c8468f1bbf1ff214591 Mon Sep 17 00:00:00 2001 From: Pavel Pletenev Date: Mon, 4 Jul 2022 19:10:44 +0300 Subject: [PATCH 04/20] Update src/nunavut/lang/cpp/templates/_fields.j2 Merge suggestion by @pavel-kirienko Co-authored-by: Pavel Kirienko --- src/nunavut/lang/cpp/templates/_fields.j2 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index 5741e8ee..a8932c84 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -11,7 +11,7 @@ {% endif -%} {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} {{ field.data_type | declaration }} {{ field | id }}; - {%- if field.data_type is VariableLengthArrayType %} + {%- if field.data_type is ArrayType %} static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ field.data_type.capacity }}U; {%- endif %} {%- endfor -%} From 74b0e3f8fc8905a594299084c153841dcaa0d637 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Wed, 17 Aug 2022 22:49:57 -0700 Subject: [PATCH 05/20] Proper support for variable_length_array MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change fixes the support for a built-in, default variable length array implementation for C++ types that can be overridden by the user. To accomplish this the concept of “support resource type” was added to allow different behavior for serialization support resource than for datatype support resources. --- .vscode/settings.json | 3 +- CONTRIBUTING.rst | 1 + conf.py | 2 +- docs/index.rst | 1 + docs/languages.rst | 7 + src/nunavut/_utilities.py | 33 +++- src/nunavut/jinja/__init__.py | 39 ++--- src/nunavut/lang/__init__.py | 24 ++- src/nunavut/lang/_common.py | 11 +- src/nunavut/lang/c/support/__init__.py | 12 +- src/nunavut/lang/cpp/__init__.py | 156 ++++++++++++++++-- src/nunavut/lang/cpp/support/__init__.py | 31 +++- src/nunavut/lang/cpp/templates/_fields.j2 | 3 - .../lang/cpp/templates/deserialization.j2 | 2 +- src/nunavut/lang/properties.yaml | 4 +- test/gentest_arrays/test_arrays.py | 2 +- .../dsdl/vla/uses_vla.0.1.uavcan | 2 + test/gentest_filters/test_filters.py | 10 ++ tox.ini | 5 +- 19 files changed, 273 insertions(+), 75 deletions(-) create mode 100644 docs/languages.rst create mode 100644 test/gentest_filters/dsdl/vla/uses_vla.0.1.uavcan diff --git a/.vscode/settings.json b/.vscode/settings.json index 792b022c..866a9697 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,5 @@ { - "python.pythonPath": ".tox/local/bin/python", + "python.defaultInterpreterPath": "${workspaceFolder}/.tox/local", "python.linting.flake8Enabled": true, "python.linting.pylintEnabled": false, "python.linting.mypyEnabled": true, @@ -11,7 +11,6 @@ ], "python.testing.cwd": "${workspaceFolder}", "python.testing.unittestEnabled": false, - "python.testing.nosetestsEnabled": false, "python.testing.pytestEnabled": true, "python.formatting.provider": "black", "python.formatting.blackArgs": [ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 61aa8b79..21a6303a 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -82,6 +82,7 @@ To run the language verification build you'll need to use a different docker con docker pull uavcan/c_cpp:ubuntu-20.04 docker run --rm -it -v $PWD:/repo uavcan/c_cpp:ubuntu-20.04 + cd /repo ./.github/verify.py -l c ./.github/verify.py -l cpp diff --git a/conf.py b/conf.py index 5efd8178..b416737a 100644 --- a/conf.py +++ b/conf.py @@ -77,7 +77,7 @@ # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = "en" # The name of the Pygments (syntax highlighting) style to use. pygments_style = "monokai" diff --git a/docs/index.rst b/docs/index.rst index 5aa90cc0..9bd26fab 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,6 +4,7 @@ :hidden: api/library + languages templates CLI (nnvg) dev diff --git a/docs/languages.rst b/docs/languages.rst new file mode 100644 index 00000000..f9832e36 --- /dev/null +++ b/docs/languages.rst @@ -0,0 +1,7 @@ +################################################ +Software Language Generation Guide +################################################ + +.. note :: + This is a placeholder for documentation this project owes you, the user, for how to integrate nnvg with build + systems and how to tune and optimize source code generation for each supported language. diff --git a/src/nunavut/_utilities.py b/src/nunavut/_utilities.py index 0bd27b01..38ba1576 100644 --- a/src/nunavut/_utilities.py +++ b/src/nunavut/_utilities.py @@ -93,6 +93,18 @@ def test_truth(cls, ynd_value: "YesNoDefault", default_value: bool) -> bool: DEFAULT = 2 +@enum.unique +class ResourceType(enum.Enum): + """ + Common Nunavut classifications for Python package resources. + """ + + ANY = 0 + CONFIGURATION = 1 + SERIALIZATION_SUPPORT = 2 + TYPE_SUPPORT = 3 + + def iter_package_resources(pkg_name: str, *suffix_filters: str) -> Generator[pathlib.Path, None, None]: """ >>> from nunavut._utilities import iter_package_resources @@ -104,5 +116,22 @@ def iter_package_resources(pkg_name: str, *suffix_filters: str) -> Generator[pat """ for resource in importlib_resources.files(pkg_name).iterdir(): - if any(suffix == resource.suffix for suffix in suffix_filters): # type: ignore - yield cast(pathlib.Path, resource) + if resource.is_file() and isinstance(resource, pathlib.Path): + # Not sure why this works but it's seemed to so far. importlib_resources.as_file(resource) + # may be more correct but this can create temporary files which would disappear after the iterator + # had copied their paths. If you are reading this because this method isn't working for some packaging + # scheme then we may need to use importlib_resources.as_file(resource) to create a runtime cache of + # temporary objects that live for a given nunavut session. This, of course, wouldn't help across sessions + # which is a common use case when integrating Nunavut with build systems. So...here be dragons. + file_resource = cast(pathlib.Path, resource) + if any(suffix == file_resource.suffix for suffix in suffix_filters): + yield file_resource + + +def empty_list_support_files() -> Generator[pathlib.Path, None, None]: + """ + Helper for implementing the list_support_files method in language support packages. This provides an empty + iterator with the correct type annotations. + """ + # works in Python 3.3 and newer. Thanks https://stackoverflow.com/a/13243870 + yield from () diff --git a/src/nunavut/jinja/__init__.py b/src/nunavut/jinja/__init__.py index ca34158c..03318736 100644 --- a/src/nunavut/jinja/__init__.py +++ b/src/nunavut/jinja/__init__.py @@ -19,7 +19,7 @@ import nunavut.lang import nunavut.postprocessors import pydsdl -from nunavut._utilities import YesNoDefault +from nunavut._utilities import ResourceType, YesNoDefault from yaml import Dumper as YamlDumper from yaml import dump as yaml_dump @@ -779,9 +779,11 @@ def __init__(self, namespace: nunavut.Namespace, **kwargs: typing.Any): target_language = self.language_context.get_target_language() self._sub_folders = None # type: typing.Optional[pathlib.Path] - self._support_enabled = False # If not enabled then we remove any support files found + self._serialization_support_enabled = ( + False # If not enabled then we remove any serialization support files found + ) if target_language is not None: - self._support_enabled = not target_language.omit_serialization_support + self._serialization_support_enabled = not target_language.omit_serialization_support # Create the sub-folder to copy-to based on the support namespace. self._sub_folders = pathlib.Path("") @@ -794,11 +796,11 @@ def __init__(self, namespace: nunavut.Namespace, **kwargs: typing.Any): # +-----------------------------------------------------------------------+ def get_templates(self) -> typing.Iterable[pathlib.Path]: files = [] - target_language = self.language_context.get_target_language() - - if target_language is not None: - for resource in target_language.support_files: + if self._serialization_support_enabled: + for resource in self._get_templates_by_support_type(ResourceType.SERIALIZATION_SUPPORT): files.append(resource) + for resource in self._get_templates_by_support_type(ResourceType.TYPE_SUPPORT): + files.append(resource) return files def generate_all(self, is_dryrun: bool = False, allow_overwrite: bool = True) -> typing.Iterable[pathlib.Path]: @@ -812,6 +814,14 @@ def generate_all(self, is_dryrun: bool = False, allow_overwrite: bool = True) -> # +-----------------------------------------------------------------------+ # | Private # +-----------------------------------------------------------------------+ + def _get_templates_by_support_type(self, resource_type: ResourceType) -> typing.Iterable[pathlib.Path]: + files = [] + target_language = self.language_context.get_target_language() + + if target_language is not None: + for resource in target_language.get_support_files(resource_type): + files.append(resource) + return files def _generate_all( self, target_language: nunavut.lang.Language, sub_folders: pathlib.Path, is_dryrun: bool, allow_overwrite: bool @@ -833,9 +843,7 @@ def _generate_all( for resource in self.get_templates(): target = (target_path / resource.name).with_suffix(target_language.extension) logger.info("Generating support file: %s", target) - if not self._support_enabled: - self._remove_header(target, is_dryrun, allow_overwrite) - elif resource.suffix == TEMPLATE_SUFFIX: + if resource.suffix == TEMPLATE_SUFFIX: self._generate_header(resource, target, is_dryrun, allow_overwrite) generated.append(target) else: @@ -843,17 +851,6 @@ def _generate_all( generated.append(target) return generated - def _remove_header(self, target: pathlib.Path, is_dryrun: bool, allow_overwrite: bool) -> None: - if not is_dryrun: - if not allow_overwrite and target.exists(): - raise PermissionError("{} exists. Refusing to remove.".format(str(target))) - try: - target.unlink() - except FileNotFoundError: - # missing_ok was added in python 3.8 so this try/except statement will - # go away someday when python 3.7 support is dropped. - pass - def _generate_header( self, template_path: pathlib.Path, output_path: pathlib.Path, is_dryrun: bool, allow_overwrite: bool ) -> pathlib.Path: diff --git a/src/nunavut/lang/__init__.py b/src/nunavut/lang/__init__.py index c741e0fa..2d75ca22 100644 --- a/src/nunavut/lang/__init__.py +++ b/src/nunavut/lang/__init__.py @@ -18,8 +18,8 @@ import pydsdl +from .._utilities import ResourceType, YesNoDefault, iter_package_resources, empty_list_support_files from ..dependencies import Dependencies, DependencyBuilder -from .._utilities import YesNoDefault, iter_package_resources from ._config import LanguageConfig, VersionReader logger = logging.getLogger(__name__) @@ -467,10 +467,13 @@ def omit_serialization_support(self) -> bool: """ return self._omit_serialization_support - @property - def support_files(self) -> typing.Generator[pathlib.Path, None, None]: + def get_support_files( + self, resource_type: ResourceType = ResourceType.ANY + ) -> typing.Generator[pathlib.Path, None, None]: """ - Iterates over non-templated supporting files embedded within the Nunavut distribution. + Iterates over supporting files embedded within the Nunavut distribution. + + :param resource_type: The type of support resources to enumerate. .. invisible-code-block: python @@ -483,7 +486,7 @@ def support_files(self) -> typing.Generator[pathlib.Path, None, None]: my_lang = _GenericLanguage(mock_module, mock_config, True) my_lang._section = "nunavut.lang.not_a_language_really_not_a_language" - for support_file in my_lang.support_files: + for support_file in my_lang.get_support_files(): # if the module doesn't exist it shouldn't have any support files. assert False @@ -495,15 +498,10 @@ def support_files(self) -> typing.Generator[pathlib.Path, None, None]: # to allow the copy generator access to the packaged support files. list_support_files = getattr( module, "list_support_files" - ) # type: typing.Callable[[], typing.Generator[pathlib.Path, None, None]] - return list_support_files() + ) # type: typing.Callable[[ResourceType], typing.Generator[pathlib.Path, None, None]] + return list_support_files(resource_type) else: - # No serialization support for this language - def list_support_files() -> typing.Generator[pathlib.Path, None, None]: - # This makes both MyPy and sonarqube happy. - return typing.cast(typing.Generator[pathlib.Path, None, None], iter(())) - - return list_support_files() + return empty_list_support_files() def get_option( self, option_key: str, default_value: typing.Union[typing.Mapping[str, typing.Any], str, None] = None diff --git a/src/nunavut/lang/_common.py b/src/nunavut/lang/_common.py index 956047bd..a85e927f 100644 --- a/src/nunavut/lang/_common.py +++ b/src/nunavut/lang/_common.py @@ -14,6 +14,7 @@ import typing import pydsdl +from nunavut._utilities import ResourceType from . import Language @@ -30,13 +31,13 @@ def generate_include_filepart_list(self, output_extension: str, sort: bool) -> t self.make_path(dt, self._language, output_extension).as_posix() for dt in dep_types.composite_types ] + namespace_path = pathlib.Path("") + for namespace_part in self._language.support_namespace: + namespace_path = namespace_path / pathlib.Path(namespace_part) if not self._language.omit_serialization_support: - namespace_path = pathlib.Path("") - for namespace_part in self._language.support_namespace: - namespace_path = namespace_path / pathlib.Path(namespace_part) path_list += [ (namespace_path / pathlib.Path(p.name).with_suffix(output_extension)).as_posix() - for p in self._language.support_files + for p in self._language.get_support_files(ResourceType.SERIALIZATION_SUPPORT) ] prefer_system_includes = self._language.get_config_value_as_bool("prefer_system_includes", False) @@ -46,7 +47,7 @@ def generate_include_filepart_list(self, output_extension: str, sort: bool) -> t path_list_with_punctuation = ['"{}"'.format(p) for p in path_list] if sort: - return sorted(path_list_with_punctuation) + self._language.get_includes(dep_types) + return sorted(path_list_with_punctuation + self._language.get_includes(dep_types)) else: return path_list_with_punctuation + self._language.get_includes(dep_types) diff --git a/src/nunavut/lang/c/support/__init__.py b/src/nunavut/lang/c/support/__init__.py index 0d219500..f85cf93b 100644 --- a/src/nunavut/lang/c/support/__init__.py +++ b/src/nunavut/lang/c/support/__init__.py @@ -8,15 +8,17 @@ """ import pathlib import typing -from nunavut._utilities import iter_package_resources + +from nunavut._utilities import ResourceType, empty_list_support_files, iter_package_resources __version__ = "1.0.0" """Version of the c support headers.""" -def list_support_files() -> typing.Generator[pathlib.Path, None, None]: +def list_support_files(resource_type: ResourceType = ResourceType.ANY) -> typing.Generator[pathlib.Path, None, None]: """ Get a list of C support headers embedded in this package. + :param resource_type: A type of support file to list. .. invisible-code-block: python @@ -37,4 +39,8 @@ def list_support_files() -> typing.Generator[pathlib.Path, None, None]: :return: A list of C support header resources. """ - return iter_package_resources(__name__, ".h", ".j2") + # The c support only has serialization support resources + if resource_type not in (ResourceType.ANY, ResourceType.SERIALIZATION_SUPPORT): + return empty_list_support_files() + else: + return iter_package_resources(__name__, ".h", ".j2") diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index 9cbab7be..e358647a 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -8,25 +8,24 @@ module will be available in the template's global namespace as ``cpp``. """ -import functools import fractions +import functools import io +import pathlib import re import textwrap import typing import pydsdl +from ..._utilities import YesNoDefault, ResourceType from ...templates import template_language_filter, template_language_list_filter, template_language_test from .. import Dependencies from .. import Language as BaseLanguage from .._common import IncludeGenerator, TokenEncoder, UniqueNameGenerator -from ..._utilities import YesNoDefault from ..c import _CFit from ..c import filter_literal as c_filter_literal -DEFAULT_ARRAY_TYPE = "std::array<{TYPE},{MAX_SIZE}>" - class Language(BaseLanguage): """ @@ -114,24 +113,146 @@ def _has_variant(self) -> bool: """ return self._standard_version() >= 17 + @functools.lru_cache() + def _get_variable_length_array_path(self) -> typing.Optional[pathlib.Path]: + """ + Returns a releative path, suitable for include statements, to the built-in default Variable Length Array (VLA) + support type. + """ + for support_file in self.get_support_files(ResourceType.TYPE_SUPPORT): + if support_file.stem == "variable_length_array": + return (pathlib.Path("/".join(self.support_namespace)) / support_file.name).with_suffix(self.extension) + + return None + + @functools.lru_cache() + def _get_default_vla_template(self) -> str: + """ + Returns a template to the built-in Variable Length Array (VLA) implementation. This is used when no override + was provided. + + config_no_namespace = { + 'nunavut.lang.cpp': + { + 'support_namesapce': '' + } + } + + lctx = configurable_language_context_factory(config_no_namespace, 'cpp') + template_w_no_namespace = lctx.get_target_language()._get_default_vla_template() + assert template_w_no_namespace.startswith("variable_length_array") + + config_w_namespace = { + 'nunavut.lang.cpp': + { + 'support_namesapce': 'foo.bar' + } + } + + lctx = configurable_language_context_factory(config_w_namespace, 'cpp') + template_w_namespace = lctx.get_target_language()._get_default_vla_template() + assert template_w_namespace.startswith("foo::bar::variable_length_array") + + """ + base_template = "variable_length_array<{TYPE}, {MAX_SIZE}>" + if len(self.support_namespace) > 0: + return "::".join(self.support_namespace) + "::" + base_template + else: + return base_template + def get_includes(self, dep_types: Dependencies) -> typing.List[str]: + """ + Get includes for c++ source. + + .. invisible-code-block: python + + from nunavut.lang import Language + from nunavut.dependencies import Dependencies + + def do_includes_test(use_foobar, extension): + + vla_header_name = "nunavut/support/variable_length_array{}".format(extension) + foobar_header_name = "foobar.h" + include_value = '' if not use_foobar else foobar_header_name + + config = { + 'nunavut.lang.cpp': + { + 'variable_array_type_include': include_value, + 'extension': extension + } + } + + lctx = configurable_language_context_factory(config, 'cpp') + lang_cpp = lctx.get_target_language() + assert extension == lang_cpp.extension + + test_dependencies = Dependencies() + test_dependencies.uses_variable_length_array = True + + # If we override the include we should not provide the built-in default + # variable array implementation. + + found_foobar = False + found_vla_header_name = False + for include in lang_cpp.get_includes(test_dependencies): + if vla_header_name in include: + found_vla_header_name = True + if foobar_header_name in include: + found_foobar = True + + if use_foobar: + assert found_foobar + assert not found_vla_header_name + else: + assert not found_foobar + assert found_vla_header_name + + do_includes_test(True, ".hpp") + do_includes_test(False, ".hpp") + do_includes_test(True, ".h") + do_includes_test(False, ".h") + """ std_includes = [] # type: typing.List[str] if self.get_config_value_as_bool("use_standard_types"): if dep_types.uses_integer: std_includes.append("cstdint") if dep_types.uses_array: std_includes.append("array") - if dep_types.uses_variable_length_array: - std_includes.append("vector") - if dep_types.uses_union and self._has_variant(): - std_includes.append("variant") - return ["<{}>".format(include) for include in sorted(std_includes)] + if dep_types.uses_union and self._has_variant(): + std_includes.append("variant") + includes_formatted = ["<{}>".format(include) for include in sorted(std_includes)] + + if dep_types.uses_variable_length_array: + vla_include = None # type: typing.Optional[str] + variable_array_include = self.get_config_value("variable_array_type_include", "") + if variable_array_include != "": + vla_include = variable_array_include + else: + vla_path = self._get_variable_length_array_path() + if vla_path is not None: + vla_include = vla_path.as_posix() + if vla_include is not None: + includes_formatted.append('"{}"'.format(vla_include)) + + return includes_formatted def filter_id(self, instance: typing.Any, id_type: str = "any") -> str: raw_name = self.default_filter_id_for_target(instance) return self._get_token_encoder().strop(raw_name, id_type) + def create_array_decl(self, type: str, max_size: int) -> str: + return "std::array<{TYPE},{MAX_SIZE}>".format(TYPE=type, MAX_SIZE=max_size) + + def create_vla_decl(self, type: str, max_size: int) -> str: + variable_array_type_template = self.get_option("variable_array_type_template") + + if not isinstance(variable_array_type_template, str) or len(variable_array_type_template) == 0: + variable_array_type_template = self._get_default_vla_template() + + return variable_array_type_template.format(TYPE=type, MAX_SIZE=max_size) + @template_language_test(__name__) def uses_std_variant(language: Language) -> bool: @@ -742,17 +863,16 @@ def filter_declaration(language: Language, instance: pydsdl.Any) -> str: if isinstance(instance, pydsdl.PrimitiveType) or isinstance(instance, pydsdl.VoidType): return filter_type_from_primitive(language, instance) elif isinstance(instance, pydsdl.VariableLengthArrayType): - variable_array_type = language.get_option("variable_array_type") + variable_array_type_template = language.get_option("variable_array_type_template") - if not isinstance(variable_array_type, str): - raise RuntimeError("variable_array_type language option was missing or invalid.") - return variable_array_type.format( - TYPE=filter_declaration(language, instance.element_type), MAX_SIZE=instance.capacity - ) + if not isinstance(variable_array_type_template, str) or len(variable_array_type_template) == 0: + return language.create_vla_decl(filter_declaration(language, instance.element_type), instance.capacity) + else: + return variable_array_type_template.format( + TYPE=filter_declaration(language, instance.element_type), MAX_SIZE=instance.capacity + ) elif isinstance(instance, pydsdl.ArrayType): - return DEFAULT_ARRAY_TYPE.format( - TYPE=filter_declaration(language, instance.element_type), MAX_SIZE=instance.capacity - ) + return language.create_array_decl(filter_declaration(language, instance.element_type), instance.capacity) else: return filter_full_reference_name(language, instance) diff --git a/src/nunavut/lang/cpp/support/__init__.py b/src/nunavut/lang/cpp/support/__init__.py index 9cc65218..b4531986 100644 --- a/src/nunavut/lang/cpp/support/__init__.py +++ b/src/nunavut/lang/cpp/support/__init__.py @@ -10,13 +10,13 @@ import pathlib import typing -from nunavut._utilities import iter_package_resources +from nunavut._utilities import ResourceType, empty_list_support_files, iter_package_resources __version__ = "1.0.0" """Version of the c++ support headers.""" -def list_support_files() -> typing.Generator[pathlib.Path, None, None]: +def list_support_files(resource_type: ResourceType = ResourceType.ANY) -> typing.Generator[pathlib.Path, None, None]: """ Get a list of C++ support headers embedded in this package. @@ -37,6 +37,31 @@ def list_support_files() -> typing.Generator[pathlib.Path, None, None]: assert support_file_count > 0 + support_file_count = 0 + for path in list_support_files(ResourceType.CONFIGURATION): + support_file_count +=1 + assert support_file_count == 0 + + support_file_count = 0 + for path in list_support_files(ResourceType.SERIALIZATION_SUPPORT): + support_file_count +=1 + assert support_file_count > 0 + + support_file_count = 0 + for path in list_support_files(ResourceType.TYPE_SUPPORT): + support_file_count +=1 + assert support_file_count > 0 + :return: A list of C++ support header resources. """ - return iter_package_resources(__name__, ".hpp", ".j2") + + # for now we say all .hpp resources are type support and all .j2 are serialization support. + # We are allowed to change this logic anyway we want without breaking changes. + if resource_type is ResourceType.SERIALIZATION_SUPPORT: + return iter_package_resources(__name__, ".j2") + if resource_type is ResourceType.TYPE_SUPPORT: + return iter_package_resources(__name__, ".hpp") + if resource_type is ResourceType.ANY: + return iter_package_resources(__name__, ".hpp", ".j2") + else: + return empty_list_support_files() diff --git a/src/nunavut/lang/cpp/templates/_fields.j2 b/src/nunavut/lang/cpp/templates/_fields.j2 index a8932c84..712fed04 100644 --- a/src/nunavut/lang/cpp/templates/_fields.j2 +++ b/src/nunavut/lang/cpp/templates/_fields.j2 @@ -11,7 +11,4 @@ {% endif -%} {{ field.doc | block_comment('cpp-doxygen', 4, 120) }} {{ field.data_type | declaration }} {{ field | id }}; - {%- if field.data_type is ArrayType %} - static constexpr {{ typename_unsigned_length }} {{ field | id }}_ArrayCapacity = {{ field.data_type.capacity }}U; - {%- endif %} {%- endfor -%} diff --git a/src/nunavut/lang/cpp/templates/deserialization.j2 b/src/nunavut/lang/cpp/templates/deserialization.j2 index b9670def..3206bdc7 100644 --- a/src/nunavut/lang/cpp/templates/deserialization.j2 +++ b/src/nunavut/lang/cpp/templates/deserialization.j2 @@ -187,7 +187,7 @@ { return -nunavut::support::Error::REPRESENTATION_BAD_ARRAY_LENGTH; } - {{ reference }}.resize({{ ref_size }}); + {{ reference }}.reserve({{ ref_size }}); {# COMPUTE THE ARRAY ELEMENT OFFSETS #} {# NOTICE: The offset is no longer valid at this point because we just emitted the array length prefix. #} diff --git a/src/nunavut/lang/properties.yaml b/src/nunavut/lang/properties.yaml index 8c9537c3..0b9b9f53 100644 --- a/src/nunavut/lang/properties.yaml +++ b/src/nunavut/lang/properties.yaml @@ -312,7 +312,9 @@ nunavut.lang.cpp: enable_serialization_asserts: false enable_override_variable_array_capacity: false std: c++14 - variable_array_type: "std::vector<{TYPE},std::allocator<{TYPE}>>" + # Provide non-empty values to override the type used for variable-length arrays in C++ types. + variable_array_type_template: "" + variable_array_type_include: "" cast_format: "static_cast<{type}>({value})" enable_allocator_support: false diff --git a/test/gentest_arrays/test_arrays.py b/test/gentest_arrays/test_arrays.py index 9acffe1a..ab1af8cc 100644 --- a/test/gentest_arrays/test_arrays.py +++ b/test/gentest_arrays/test_arrays.py @@ -61,7 +61,7 @@ def test_var_array_override_cpp(gen_paths): # type: ignore Make sure we can override the type generated for variable-length arrays. """ - language_option_overrides = {'variable_array_type': 'scotec::TerribleArray<{TYPE},{MAX_SIZE}>'} + language_option_overrides = {'variable_array_type_template': 'scotec::TerribleArray<{TYPE},{MAX_SIZE}>'} root_namespace = str(gen_paths.dsdl_dir / Path("radar")) compound_types = pydsdl.read_namespace(root_namespace, [], allow_unregulated_fixed_port_id=True) language_context = LanguageContext('cpp', diff --git a/test/gentest_filters/dsdl/vla/uses_vla.0.1.uavcan b/test/gentest_filters/dsdl/vla/uses_vla.0.1.uavcan new file mode 100644 index 00000000..f1f4ca23 --- /dev/null +++ b/test/gentest_filters/dsdl/vla/uses_vla.0.1.uavcan @@ -0,0 +1,2 @@ +int32[<=128] value +@sealed diff --git a/test/gentest_filters/test_filters.py b/test/gentest_filters/test_filters.py index 8abb5e52..d9e4ef52 100644 --- a/test/gentest_filters/test_filters.py +++ b/test/gentest_filters/test_filters.py @@ -234,6 +234,16 @@ def assert_path_in_imports(path: str) -> None: '')) +def test_filter_includes_cpp_vla(gen_paths): # type: ignore + lctx = LanguageContext(target_language='cpp', extension='.h') + type_map = read_namespace((gen_paths.dsdl_dir / pathlib.Path('vla')).as_posix()) + from nunavut.lang.cpp import filter_includes + + test_subject = next(filter(lambda type: (type.short_name == 'uses_vla'), type_map)) + imports = filter_includes(lctx.get_language('nunavut.lang.cpp'), test_subject) + assert '"nunavut/support/variable_length_array.h"' in imports + + @typing.no_type_check @pytest.mark.parametrize('language_name,namespace_separator', [('c', '_'), ('cpp', '::')]) def test_filter_full_reference_name_via_template(gen_paths, language_name, namespace_separator): diff --git a/tox.ini b/tox.ini index 8183d3d2..f4541c2f 100644 --- a/tox.ini +++ b/tox.ini @@ -42,7 +42,8 @@ log_file = pytest.log log_level = DEBUG log_cli = true log_cli_level = WARNING -norecursedirs = submodules +addopts: --keep-generated +norecursedirs = submodules .* build* # The fill fixtures deprecation warning comes from Sybil, which we don't have any control over. Remove when updated. filterwarnings = error @@ -249,3 +250,5 @@ deps = {[dev]deps} {[testenv:docs]deps} {[testenv:lint]deps} +commands = + mypy --install-types --non-interactive From 9708d964735b1d581a5849969f35317920a34000 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Thu, 18 Aug 2022 14:19:40 -0700 Subject: [PATCH 06/20] fixing windows build --- test/gentest_filters/test_filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gentest_filters/test_filters.py b/test/gentest_filters/test_filters.py index d9e4ef52..a9fedea1 100644 --- a/test/gentest_filters/test_filters.py +++ b/test/gentest_filters/test_filters.py @@ -247,7 +247,7 @@ def test_filter_includes_cpp_vla(gen_paths): # type: ignore @typing.no_type_check @pytest.mark.parametrize('language_name,namespace_separator', [('c', '_'), ('cpp', '::')]) def test_filter_full_reference_name_via_template(gen_paths, language_name, namespace_separator): - root_path = str(gen_paths.dsdl_dir / Path("uavcan")) + root_path = (gen_paths.dsdl_dir / Path("uavcan")).as_posix() output_path = gen_paths.out_dir / 'filter_and_test' compound_types = read_namespace(root_path, []) language_context = LanguageContext(target_language=language_name) From 38f8db5ef0e9a3d61a28974c7a71a0ddc969814f Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Thu, 18 Aug 2022 18:33:44 -0700 Subject: [PATCH 07/20] checkpoint almost there. --- src/nunavut/lang/cpp/__init__.py | 6 +- .../cpp/support/variable_length_array.hpp | 163 ++++++++++++++++-- src/nunavut/version.py | 2 +- tox.ini | 7 +- verification/cpp/suite/test_serialization.cpp | 30 ++-- verification/cpp/suite/test_unionant.cpp | 60 +++---- verification/cpp/suite/test_var_len_arr.cpp | 75 ++++++-- .../cpp/suite/test_var_len_arr_compiles.cpp | 28 ++- 8 files changed, 292 insertions(+), 79 deletions(-) diff --git a/src/nunavut/lang/cpp/__init__.py b/src/nunavut/lang/cpp/__init__.py index e358647a..fcc51f30 100644 --- a/src/nunavut/lang/cpp/__init__.py +++ b/src/nunavut/lang/cpp/__init__.py @@ -140,7 +140,7 @@ def _get_default_vla_template(self) -> str: lctx = configurable_language_context_factory(config_no_namespace, 'cpp') template_w_no_namespace = lctx.get_target_language()._get_default_vla_template() - assert template_w_no_namespace.startswith("variable_length_array") + assert template_w_no_namespace.startswith("VariableLengthArray") config_w_namespace = { 'nunavut.lang.cpp': @@ -151,10 +151,10 @@ def _get_default_vla_template(self) -> str: lctx = configurable_language_context_factory(config_w_namespace, 'cpp') template_w_namespace = lctx.get_target_language()._get_default_vla_template() - assert template_w_namespace.startswith("foo::bar::variable_length_array") + assert template_w_namespace.startswith("foo::bar::VariableLengthArray") """ - base_template = "variable_length_array<{TYPE}, {MAX_SIZE}>" + base_template = "VariableLengthArray<{TYPE}, {MAX_SIZE}>" if len(self.support_namespace) > 0: return "::".join(self.support_namespace) + "::" + base_template else: diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index 3119b792..a2f9c980 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -13,21 +13,48 @@ #include #include #include +#include namespace nunavut { namespace support { + +/** + * Default allocator using malloc and free. + */ +template +class MallocAllocator +{ +public: + using value_type = T; + + MallocAllocator() noexcept + {} + + T* allocate(std::size_t n) noexcept + { + return reinterpret_cast(malloc(n * sizeof(T))); + } + + constexpr void deallocate(T* p, std::size_t n) noexcept + { + (void) n; + free(p); + } +}; + + /// /// Minimal, generic container for storing UAVCAN variable-length arrays. One property that is unique /// for variable-length arrays is that they have a maximum bound which this implementation enforces. /// This allows use of an allocator that is backed by statically allocated memory. /// /// @tparam T The type of elements in the array. -/// @tparam Allocator The type of allocator. /// @tparam MaxSize The maximum allowable size and capacity of the array. +/// @tparam Allocator The type of allocator. /// -template +template > class VariableLengthArray { public: @@ -39,24 +66,53 @@ class VariableLengthArray , alloc_() {} - explicit VariableLengthArray(const Allocator& alloc) noexcept(std::is_nothrow_copy_constructible::value) + VariableLengthArray(std::initializer_list l) noexcept(std::is_nothrow_constructible::value) : data_(nullptr) , capacity_(0) , size_(0) - , alloc_(alloc) - {} + , alloc_() + { + reserve(l.size()); + for (const_iterator list_item = l.begin(), end = l.end(); list_item != end; ++list_item) + { + push_back_no_alloc(*list_item); + } + } // // Rule of Five. // - VariableLengthArray(const VariableLengthArray&) = delete; + VariableLengthArray(const VariableLengthArray& rhs) + : data_(nullptr) + , capacity_(0) + , size_(0) + , alloc_(rhs.alloc_) + { + reserve(rhs.size()); + for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) + { + push_back_no_alloc(*list_item); + } + } + VariableLengthArray& operator=(const VariableLengthArray&) = delete; - VariableLengthArray(VariableLengthArray&&) = delete; + + VariableLengthArray(VariableLengthArray&& rhs) + : data_(rhs.data_) + , capacity_(rhs.capacity_) + , size_(rhs.size_) + , alloc_(std::move(rhs.alloc_)) + { + rhs.data_ = nullptr; + rhs.capacity_ = 0; + rhs.size_ = 0; + } + VariableLengthArray& operator=(VariableLengthArray&&) = delete; ~VariableLengthArray() noexcept( noexcept( - VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval()) + VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval()) ) ) { @@ -68,6 +124,21 @@ class VariableLengthArray /// using allocator_type = Allocator; + /// + /// STL-like declaration of the iterator type. + /// + using iterator = typename std::add_pointer::type; + + /// + /// STL-like declaration of the const iterator type. + /// + using const_iterator = typename std::add_pointer::type>::type; + + /// + /// STL-like declaration of the container's storage type. + /// + using value_type = T; + /// /// The maximum size (and capacity) of this array. This size is derived /// from the DSDL definition for a field and represents the maximum number of @@ -80,6 +151,56 @@ class VariableLengthArray // +----------------------------------------------------------------------+ // | ELEMENT ACCESS // +----------------------------------------------------------------------+ + /// + /// Provides direct, unsafe access to the internal data buffer. This pointer + /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. + /// + constexpr const_iterator cbegin() const noexcept + { + return data_; + } + + /// + /// Pointer to memory location after the last, valid element. This pointer + /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. + /// + constexpr const_iterator cend() const noexcept + { + if (nullptr == data_) + { + return nullptr; + } + else + { + return &data_[size_]; + } + } + + /// + /// Provides direct, unsafe access to the internal data buffer. This pointer + /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. + /// + constexpr iterator begin() noexcept + { + return data_; + } + + /// + /// Pointer to memory location after the last, valid element. This pointer + /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. + /// + constexpr iterator end() noexcept + { + if (nullptr == data_) + { + return nullptr; + } + else + { + return &data_[size_]; + } + } + /// /// Provides direct, unsafe access to the internal data buffer. This pointer /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. @@ -206,7 +327,7 @@ class VariableLengthArray ) && noexcept( - VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) + VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) ) ) { @@ -254,7 +375,7 @@ class VariableLengthArray ) && noexcept( - VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) + VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) ) ) { @@ -306,6 +427,24 @@ class VariableLengthArray // +----------------------------------------------------------------------+ // | MODIFIERS // +----------------------------------------------------------------------+ + /// + /// Construct a new element on to the back of the array and grow the array size by 1. + /// + /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to + /// grow the available capacity). + /// + constexpr T* push_back_no_alloc() noexcept(std::is_nothrow_default_constructible::value) + { + if (size_ < capacity_) + { + return new (&data_[size_++]) T(); + } + else + { + return nullptr; + } + } + /// /// Push a new element on to the back of the array and grow the array size by 1. /// @@ -463,8 +602,8 @@ class VariableLengthArray }; // required till C++ 17. Redundant but allowed after that. -template -const std::size_t VariableLengthArray::max_size; +template +const std::size_t VariableLengthArray::max_size; } // namespace support } // namespace nunavut diff --git a/src/nunavut/version.py b/src/nunavut/version.py index 7417f195..2b3e1113 100644 --- a/src/nunavut/version.py +++ b/src/nunavut/version.py @@ -7,7 +7,7 @@ .. autodata:: __version__ """ -__version__ = "1.8.3" #: The version number used in the release of nunavut to pypi. +__version__ = "1.9.0" #: The version number used in the release of nunavut to pypi. __license__ = "MIT" diff --git a/tox.ini b/tox.ini index f4541c2f..0e5f5469 100644 --- a/tox.ini +++ b/tox.ini @@ -251,4 +251,9 @@ deps = {[testenv:docs]deps} {[testenv:lint]deps} commands = - mypy --install-types --non-interactive + mypy -m nunavut \ + -m nunavut.jinja \ + -p nunavut.lang \ + --config-file {toxinidir}/tox.ini \ + --install-types \ + --non-interactive diff --git a/verification/cpp/suite/test_serialization.cpp b/verification/cpp/suite/test_serialization.cpp index 73359483..bb0ec72d 100644 --- a/verification/cpp/suite/test_serialization.cpp +++ b/verification/cpp/suite/test_serialization.cpp @@ -137,37 +137,43 @@ TEST(Serialization, StructReference) obj.i10_4[1] = -0x6666; // saturates to -512 obj.i10_4[2] = +0x0055; // original value retained obj.i10_4[3] = -0x00AA; // original value retained - obj.f16_le2.emplace_back(-1e9F); // saturated to -65504 - obj.f16_le2.emplace_back(+INFINITY); // infinity retained + obj.f16_le2.reserve(2); + ASSERT_TRUE(nullptr != obj.f16_le2.push_back_no_alloc(-1e9F)); // saturated to -65504 + ASSERT_TRUE(nullptr != obj.f16_le2.push_back_no_alloc(+INFINITY)); // infinity retained ASSERT_EQ(2U, obj.f16_le2.size()); //obj.unaligned_bitpacked_3[0] = 0xF5; // 0b101, rest truncated away and ignored TODO:Fix obj.unaligned_bitpacked_3[0] = 1; obj.unaligned_bitpacked_3[1] = 0; obj.unaligned_bitpacked_3[2] = 1; //obj.sealed = 123; // ignored - obj.bytes_lt3.emplace_back(111); - obj.bytes_lt3.emplace_back(222); + obj.bytes_lt3.reserve(2); + ASSERT_TRUE(nullptr != obj.bytes_lt3.push_back_no_alloc(111)); + ASSERT_TRUE(nullptr != obj.bytes_lt3.push_back_no_alloc(222)); ASSERT_EQ(2U, obj.bytes_lt3.size()); obj.bytes_3[0] = -0x77; obj.bytes_3[1] = -0x11; obj.bytes_3[2] = +0x77; - obj.u2_le4.emplace_back(0x02); // retained - obj.u2_le4.emplace_back(0x11); // truncated => 1 - obj.u2_le4.emplace_back(0xFF); // truncated => 3 - //obj.u2_le4.emplace_back(0xFF); // ignored because the length is 3 + obj.u2_le4.reserve(3); + ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0x02)); // retained + ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0x11)); // truncated => 1 + ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0xFF)); // truncated => 3 + //obj.u2_le4.push_back_no_alloc(0xFF); // ignored because the length is 3 ASSERT_EQ(3U, obj.u2_le4.size()); - obj.delimited_fix_le2.emplace_back(); // ignored + obj.delimited_fix_le2.reserve(1); + ASSERT_TRUE(nullptr != obj.delimited_fix_le2.push_back_no_alloc()); // ignored ASSERT_EQ(1U, obj.delimited_fix_le2.size()); obj.u16_2[0] = 0x1234; obj.u16_2[1] = 0x5678; obj.aligned_bitpacked_3[0] = 0xF1U; // obj.unaligned_bitpacked_lt3.bitpacked[0] = 0xF1U; - obj.unaligned_bitpacked_lt3.emplace_back(1); - obj.unaligned_bitpacked_lt3.emplace_back(0); + obj.unaligned_bitpacked_lt3.reserve(2); + ASSERT_TRUE(nullptr != obj.unaligned_bitpacked_lt3.push_back_no_alloc(1)); + ASSERT_TRUE(nullptr != obj.unaligned_bitpacked_lt3.push_back_no_alloc(0)); ASSERT_EQ(2U, obj.unaligned_bitpacked_lt3.size()); // 0b01, rest truncated obj.delimited_var_2[0].set_f16(+1e9F); // truncated to infinity obj.delimited_var_2[1].set_f64(-1e40); // retained - obj.aligned_bitpacked_le3.emplace_back(1); + obj.aligned_bitpacked_le3.reserve(1); + ASSERT_TRUE(nullptr != obj.aligned_bitpacked_le3.push_back_no_alloc(1)); ASSERT_EQ(1U, obj.aligned_bitpacked_le3.size()); // only lsb is set, other truncated const uint8_t reference[] = { diff --git a/verification/cpp/suite/test_unionant.cpp b/verification/cpp/suite/test_unionant.cpp index b76411e0..e00049a0 100644 --- a/verification/cpp/suite/test_unionant.cpp +++ b/verification/cpp/suite/test_unionant.cpp @@ -110,31 +110,31 @@ TEST(UnionantTests, union_value_copy_ctor) ASSERT_EQ(24U, b_value->value); } -/** - * Verify the move constructor of the VariantType - */ -TEST(UnionantTests, union_value_move_ctor) -{ - using ValueType = uavcan::_register::Value_1_0; - const std::string hello_world{"Hello World"}; - ValueType a{}; - // verify that empty is the default such that our emplace of string (next line) is actually changing the - // variant's value type. - ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); - uavcan::primitive::String_1_0& a_result = a.union_value.emplace( - uavcan::primitive::String_1_0{std::vector(hello_world.begin(), hello_world.end())}); - ASSERT_EQ(11UL, a_result.value.size()); - ASSERT_EQ('W', a_result.value[6]); - - ValueType::VariantType b(std::move(a.union_value)); - ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); - ASSERT_EQ(0UL, a_result.value.size()); - uavcan::primitive::String_1_0* b_value = - ValueType::VariantType::get_if(&b); - ASSERT_NE(nullptr, b_value); - ASSERT_EQ(11UL, b_value->value.size()); - ASSERT_EQ('W', b_value->value[6]); -} +// /** +// * Verify the move constructor of the VariantType +// */ +// TEST(UnionantTests, union_value_move_ctor) +// { +// using ValueType = uavcan::_register::Value_1_0; +// const std::string hello_world{"Hello World"}; +// ValueType a{}; +// // verify that empty is the default such that our emplace of string (next line) is actually changing the +// // variant's value type. +// ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); +// uavcan::primitive::String_1_0& a_result = a.union_value.emplace( +// uavcan::primitive::String_1_0{std::vector(hello_world.begin(), hello_world.end())}); +// ASSERT_EQ(11UL, a_result.value.size()); +// ASSERT_EQ('W', a_result.value[6]); + +// ValueType::VariantType b(std::move(a.union_value)); +// ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); +// ASSERT_EQ(0UL, a_result.value.size()); +// uavcan::primitive::String_1_0* b_value = +// ValueType::VariantType::get_if(&b); +// ASSERT_NE(nullptr, b_value); +// ASSERT_EQ(11UL, b_value->value.size()); +// ASSERT_EQ('W', b_value->value[6]); +// } /** * Verify the move assignment operator of the VariantType @@ -142,12 +142,11 @@ TEST(UnionantTests, union_value_move_ctor) TEST(UnionantTests, union_value_move_assignment) { using ValueType = uavcan::_register::Value_1_0; - const std::string hello_world{"Hello World"}; - const std::vector hello_world_vector{hello_world.begin(), hello_world.end()}; + const decltype(uavcan::primitive::String_1_0::value) hello_world_arr{{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'}}; ValueType a{}; uavcan::primitive::String_1_0& a_result = a.union_value.emplace( - uavcan::primitive::String_1_0{hello_world_vector}); - ASSERT_EQ(11UL, a_result.value.size()); + uavcan::primitive::String_1_0{hello_world_arr}); + ASSERT_EQ(12UL, a_result.value.size()); ASSERT_EQ('W', a_result.value[6]); ValueType::VariantType b; @@ -155,5 +154,6 @@ TEST(UnionantTests, union_value_move_assignment) const uavcan::primitive::String_1_0* b_string_value = ValueType::VariantType::get_if(&b); ASSERT_NE(nullptr, b_string_value); - ASSERT_EQ(hello_world_vector, b_string_value->value); + ASSERT_STREQ(reinterpret_cast(hello_world_arr.cbegin()), + reinterpret_cast(b_string_value->value.cbegin())); } diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 0bc8078e..516e9134 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -48,6 +48,9 @@ class Doomed /** * Pavel's O(1) Heap Allocator wrapped in an std::allocator concept. + * + * Note that this implementation probably wouldn't work in a real application + * because it is not copyable. */ template class O1HeapAllocator @@ -60,6 +63,11 @@ class O1HeapAllocator , heap_alloc_(o1heapInit(&heap_[0], SizeCount * sizeof(T), nullptr, nullptr)) {} + O1HeapAllocator(const O1HeapAllocator&) = delete; + O1HeapAllocator& operator=(const O1HeapAllocator&) = delete; + O1HeapAllocator(O1HeapAllocator&&) = delete; + O1HeapAllocator& operator=(O1HeapAllocator&&) = delete; + T* allocate(std::size_t n) { return reinterpret_cast(o1heapAllocate(heap_alloc_, n * sizeof(T))); @@ -158,7 +166,8 @@ template class VLATestsGeneric : public ::testing::Test {}; -using VLATestsGenericAllocators = ::testing::Types, +using VLATestsGenericAllocators = ::testing::Types, + std::allocator, std::allocator, O1HeapAllocator, JunkyStaticAllocator>; @@ -166,7 +175,7 @@ TYPED_TEST_SUITE(VLATestsGeneric, VLATestsGenericAllocators, ); TYPED_TEST(VLATestsGeneric, TestReserve) { - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray subject; ASSERT_EQ(0U, subject.capacity()); ASSERT_EQ(0U, subject.size()); ASSERT_EQ(10U, subject.max_size); @@ -180,7 +189,7 @@ TYPED_TEST(VLATestsGeneric, TestReserve) TYPED_TEST(VLATestsGeneric, TestPush) { - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray subject; ASSERT_EQ(nullptr, subject.data()); ASSERT_EQ(nullptr, subject.push_back_no_alloc(1)); @@ -197,7 +206,7 @@ TYPED_TEST(VLATestsGeneric, TestPush) TYPED_TEST(VLATestsGeneric, TestPop) { - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); const typename TypeParam::value_type* const pushed = subject.push_back_no_alloc(1); ASSERT_NE(nullptr, pushed); @@ -210,7 +219,7 @@ TYPED_TEST(VLATestsGeneric, TestPop) TYPED_TEST(VLATestsGeneric, TestShrink) { - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); const typename TypeParam::value_type* const pushed = subject.push_back_no_alloc(1); ASSERT_NE(nullptr, pushed); @@ -236,7 +245,7 @@ TYPED_TEST_SUITE(VLATestsStatic, VLATestsStaticAllocators, ); TYPED_TEST(VLATestsStatic, TestOutOfMemory) { nunavut::support:: - VariableLengthArray::max()> + VariableLengthArray::max(), TypeParam> subject; ASSERT_EQ(0U, subject.capacity()); @@ -269,7 +278,7 @@ TYPED_TEST(VLATestsStatic, TestOverMaxSize) { static constexpr std::size_t MaxSize = 5; static_assert(MaxSize > 0, "Test assumes MaxSize > 0"); - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray subject; ASSERT_EQ(0U, subject.capacity()); for (std::size_t i = 1; i <= MaxSize; ++i) @@ -295,7 +304,7 @@ TYPED_TEST(VLATestsStatic, TestOverMaxSize) TEST(VLATestsNonTrivial, TestDeallocSize) { - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; ASSERT_EQ(0U, subject.get_allocator().get_alloc_count()); subject.reserve(10); ASSERT_EQ(1U, subject.get_allocator().get_alloc_count()); @@ -310,7 +319,7 @@ TEST(VLATestsNonTrivial, TestDestroy) { int dtor_called = 0; - auto subject = std::make_shared, 10>>(); + auto subject = std::make_shared>>(); ASSERT_EQ(10U, subject->reserve(10)); ASSERT_NE(nullptr, subject->push_back_no_alloc(Doomed(&dtor_called))); @@ -324,7 +333,7 @@ TEST(VLATestsNonTrivial, TestNonFunamental) { int dtor_called = 0; - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); ASSERT_NE(nullptr, subject.push_back_no_alloc(Doomed(&dtor_called))); subject.pop_back(); @@ -343,7 +352,7 @@ TEST(VLATestsNonTrivial, TestNotMovable) (void) rhs; } }; - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); NotMovable source; ASSERT_NE(nullptr, subject.push_back_no_alloc(source)); @@ -371,27 +380,59 @@ TEST(VLATestsNonTrivial, TestMovable) private: int data_; }; - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); Movable* pushed = subject.push_back_no_alloc(Movable(1)); ASSERT_NE(nullptr, pushed); ASSERT_EQ(1, pushed->get_data()); } -/** - * Just remember that this is a possible pattern (unfortunately). - */ TEST(VLATestsNonTrivial, TestMoveToVector) { - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; ASSERT_EQ(decltype(subject)::max_size, subject.reserve(decltype(subject)::max_size)); for (std::size_t i = 0; i < decltype(subject)::max_size; ++i) { ASSERT_NE(nullptr, subject.push_back_no_alloc(i)); } - std::vector a(subject.data(), subject.data() + subject.size()); + std::vector a(subject.cbegin(), subject.cend()); for (std::size_t i = 0; i < decltype(subject)::max_size; ++i) { ASSERT_EQ(i, a[i]); } } + +TEST(VLATestsNonTrivial, TestInitializerArray) +{ + nunavut::support::VariableLengthArray subject{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } +} + +TEST(VLATestsNonTrivial, TestCopyContructor) +{ + nunavut::support::VariableLengthArray fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + nunavut::support::VariableLengthArray subject(fixture); + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } +} + + +TEST(VLATestsNonTrivial, TestMoveContructor) +{ + nunavut::support::VariableLengthArray fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + nunavut::support::VariableLengthArray subject(std::move(fixture)); + ASSERT_EQ(10U, subject.size()); + for (std::size_t i = 0; i < subject.size(); ++i) + { + ASSERT_EQ(subject.size() - i, subject[i]); + } + ASSERT_EQ(0U, fixture.size()); + ASSERT_EQ(0U, fixture.capacity()); +} diff --git a/verification/cpp/suite/test_var_len_arr_compiles.cpp b/verification/cpp/suite/test_var_len_arr_compiles.cpp index 98d33fe1..bd48a7a0 100644 --- a/verification/cpp/suite/test_var_len_arr_compiles.cpp +++ b/verification/cpp/suite/test_var_len_arr_compiles.cpp @@ -86,16 +86,38 @@ class JunkyThrowingAllocator T data_[SizeCount]; }; +TEST(VLATestsStatic, TestDefaultAllocator) +{ + nunavut::support::VariableLengthArray subject; + + static_assert(std::is_nothrow_default_constructible::value, + "VariableLengthArray's default allocator must be no-throw default constructible"); + + static_assert(std::is_nothrow_constructible::value, + "VariableLengthArray's default allocator must be no-throw constructible."); + + static_assert(std::is_nothrow_destructible::value, + "VariableLengthArray's default allocator must be no-throw destructible.'."); + + static_assert(noexcept(subject.reserve(0)), + "VariableLengthArray.reserve must not throw when using the default allocator."); + + static_assert(noexcept(subject.shrink_to_fit()), + "VariableLengthArray.shrink_to_fit must not throw exceptions if using the default allocator"); + + // Use the subject to ensure it isn't elided. + ASSERT_EQ(0U, subject.size()); +} TEST(VLATestsStatic, TestNoThrowAllocator) { - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; static_assert(std::is_nothrow_default_constructible::value, "VariableLengthArray must be no-throw default constructible if the allocator is."); static_assert(std::is_nothrow_constructible::value, - "VariableLengthArray must be no-throw default constructible if the allocator is."); + "VariableLengthArray must be no-throw constructible if the allocator is."); static_assert(std::is_nothrow_destructible::value, "VariableLengthArray must be no-throw destructible if the allocator is."); @@ -113,7 +135,7 @@ TEST(VLATestsStatic, TestNoThrowAllocator) TEST(VLATestsStatic, TestThrowingAllocator) { - nunavut::support::VariableLengthArray, 10> subject; + nunavut::support::VariableLengthArray> subject; static_assert(std::is_default_constructible::value && From 180fbeb494fd242963e6ca866f48edcfa3a45591 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Mon, 22 Aug 2022 22:06:30 -0700 Subject: [PATCH 08/20] Carefully adding a couple more features --- .../cpp/support/variable_length_array.hpp | 69 ++++++++++++++++++ verification/cpp/suite/test_unionant.cpp | 73 +++++++++++-------- verification/cpp/suite/test_var_len_arr.cpp | 9 +++ .../cpp/suite/test_var_len_arr_compiles.cpp | 41 +++++++++++ 4 files changed, 162 insertions(+), 30 deletions(-) diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index a2f9c980..c7299789 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -79,6 +79,24 @@ class VariableLengthArray } } + template + constexpr VariableLengthArray(InputIt first, + InputIt last, + const std::size_t length, + const Allocator& alloc = Allocator()) + noexcept(std::is_nothrow_constructible::value) + : data_(nullptr) + , capacity_(0) + , size_(0) + , alloc_(alloc) + { + reserve(length); + for (size_t inserted = 0; first != last && inserted < length; ++first) + { + push_back_no_alloc(*first); + } + } + // // Rule of Five. // @@ -119,6 +137,48 @@ class VariableLengthArray fast_deallocate(data_, size_, capacity_, alloc_); } + template + constexpr bool operator==(const RHSType& rhs) const noexcept( + noexcept(RHSType().size()) && noexcept(RHSType().cbegin()) && noexcept(RHSType().cend()) + && + noexcept(size()) && noexcept(cbegin()) && noexcept(cend()) + ) + { + if (size() != rhs.size()) + { + return false; + } + if (data() != rhs.data()) + { + typename RHSType::const_iterator rnext = rhs.cbegin(); + typename RHSType::const_iterator rend = rhs.cend(); + const_iterator lnext = cbegin(); + const_iterator lend = cend(); + for(;rnext != rend && lnext != lend; ++rnext, ++lnext) + { + if (*rnext != *lnext) + { + return false; + } + } + if (rnext != rend || lnext != lend) + { + // One of the two iterators returned less then the size value? This is probably a bug but we can only + // say they are not equal here. + return false; + } + } + return true; + } + + template + constexpr bool operator!=(const RHSType& rhs) const noexcept( + noexcept(operator==(rhs)) + ) + { + return !(operator==(rhs)); + } + /// /// STL-like declaration of the allocator type. /// @@ -210,6 +270,15 @@ class VariableLengthArray return data_; } + /// + /// Provides direct, unsafe access to the internal data buffer. This pointer + /// is invalidated by calls to {@code shrink_to_fit} and {@code reserve}. + /// + constexpr const T* data() const noexcept + { + return data_; + } + /// /// Direct, const access to an element. If {@code pos} is > {@code size} /// the behavior is undefined. diff --git a/verification/cpp/suite/test_unionant.cpp b/verification/cpp/suite/test_unionant.cpp index e00049a0..9c6d3178 100644 --- a/verification/cpp/suite/test_unionant.cpp +++ b/verification/cpp/suite/test_unionant.cpp @@ -110,31 +110,39 @@ TEST(UnionantTests, union_value_copy_ctor) ASSERT_EQ(24U, b_value->value); } -// /** -// * Verify the move constructor of the VariantType -// */ -// TEST(UnionantTests, union_value_move_ctor) -// { -// using ValueType = uavcan::_register::Value_1_0; -// const std::string hello_world{"Hello World"}; -// ValueType a{}; -// // verify that empty is the default such that our emplace of string (next line) is actually changing the -// // variant's value type. -// ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); -// uavcan::primitive::String_1_0& a_result = a.union_value.emplace( -// uavcan::primitive::String_1_0{std::vector(hello_world.begin(), hello_world.end())}); -// ASSERT_EQ(11UL, a_result.value.size()); -// ASSERT_EQ('W', a_result.value[6]); - -// ValueType::VariantType b(std::move(a.union_value)); -// ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); -// ASSERT_EQ(0UL, a_result.value.size()); -// uavcan::primitive::String_1_0* b_value = -// ValueType::VariantType::get_if(&b); -// ASSERT_NE(nullptr, b_value); -// ASSERT_EQ(11UL, b_value->value.size()); -// ASSERT_EQ('W', b_value->value[6]); -// } +/** + * Verify the move constructor of the VariantType + */ +TEST(UnionantTests, union_value_move_ctor) +{ + using ValueType = uavcan::_register::Value_1_0; + const char* hello_world{"Hello World"}; + ValueType a{}; + // verify that empty is the default such that our emplace of string (next line) is actually changing the + // variant's value type. + ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); + uavcan::primitive::String_1_0& a_result = a.union_value.emplace( + uavcan::primitive::String_1_0 + { + { + reinterpret_cast(hello_world), + reinterpret_cast(&hello_world[11]), + 11 + } + } + ); + ASSERT_EQ(11UL, a_result.value.size()); + ASSERT_EQ('W', a_result.value[6]); + + ValueType::VariantType b(std::move(a.union_value)); + ASSERT_NE(nullptr, ValueType::VariantType::get_if(&a.union_value)); + ASSERT_EQ(0UL, a_result.value.size()); + uavcan::primitive::String_1_0* b_value = + ValueType::VariantType::get_if(&b); + ASSERT_NE(nullptr, b_value); + ASSERT_EQ(11UL, b_value->value.size()); + ASSERT_EQ('W', b_value->value[6]); +} /** * Verify the move assignment operator of the VariantType @@ -142,11 +150,17 @@ TEST(UnionantTests, union_value_copy_ctor) TEST(UnionantTests, union_value_move_assignment) { using ValueType = uavcan::_register::Value_1_0; - const decltype(uavcan::primitive::String_1_0::value) hello_world_arr{{'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\0'}}; + const char* hello_world{"Hello World"}; + const decltype(uavcan::primitive::String_1_0::value) hello_world_vla + { + reinterpret_cast(hello_world), + reinterpret_cast(&hello_world[11]), + 11 + }; ValueType a{}; uavcan::primitive::String_1_0& a_result = a.union_value.emplace( - uavcan::primitive::String_1_0{hello_world_arr}); - ASSERT_EQ(12UL, a_result.value.size()); + uavcan::primitive::String_1_0{{hello_world_vla}}); + ASSERT_EQ(11UL, a_result.value.size()); ASSERT_EQ('W', a_result.value[6]); ValueType::VariantType b; @@ -154,6 +168,5 @@ TEST(UnionantTests, union_value_move_assignment) const uavcan::primitive::String_1_0* b_string_value = ValueType::VariantType::get_if(&b); ASSERT_NE(nullptr, b_string_value); - ASSERT_STREQ(reinterpret_cast(hello_world_arr.cbegin()), - reinterpret_cast(b_string_value->value.cbegin())); + ASSERT_EQ(hello_world_vla, b_string_value->value); } diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 516e9134..d21e0fe5 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -436,3 +436,12 @@ TEST(VLATestsNonTrivial, TestMoveContructor) ASSERT_EQ(0U, fixture.size()); ASSERT_EQ(0U, fixture.capacity()); } + +TEST(VLATestsNonTrivial, TestCompare) +{ + nunavut::support::VariableLengthArray one{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + std::vector two{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + std::vector three{{9, 8, 7, 6, 5, 4, 3, 2, 1}}; + ASSERT_EQ(one, two); + ASSERT_NE(one, three); +} diff --git a/verification/cpp/suite/test_var_len_arr_compiles.cpp b/verification/cpp/suite/test_var_len_arr_compiles.cpp index bd48a7a0..4c7f63ef 100644 --- a/verification/cpp/suite/test_var_len_arr_compiles.cpp +++ b/verification/cpp/suite/test_var_len_arr_compiles.cpp @@ -160,3 +160,44 @@ TEST(VLATestsStatic, TestThrowingAllocator) // Use the subject to ensure it isn't elided. ASSERT_EQ(0U, subject.size()); } + +/** + * Used to verify noexcept status for VariableLengthArray comparison with other container types. + */ +template +struct ThrowyContainer +{ + constexpr std::size_t size() const + { + return 0; + } + + constexpr const T* cbegin() const + { + return nullptr; + } + + constexpr const T* cend() const + { + return nullptr; + } + +}; + +TEST(VLATestsStatic, TestThrowingComparitor) +{ + nunavut::support::VariableLengthArray subject; + ThrowyContainer throwy; + std::vector no_throwy; + static_assert(!noexcept(subject == throwy), + "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); + + static_assert(!noexcept(subject != throwy), + "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); + + static_assert(noexcept(subject == no_throwy), + "VariableLengthArray comparison should not throw when used the std::vector type."); + + static_assert(noexcept(subject != no_throwy), + "VariableLengthArray comparison should not throw when used the std::vector type."); +} From 219eeab5ee62e5047db13ed70b0278b6517a3bc5 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Tue, 23 Aug 2022 11:13:49 -0700 Subject: [PATCH 09/20] it aint pretty but it works --- .vscode/settings.json | 4 +- .../cpp/support/variable_length_array.hpp | 44 +++++++----- .../lang/cpp/templates/deserialization.j2 | 7 +- verification/cpp/suite/test_serialization.cpp | 43 +++++++----- verification/cpp/suite/test_var_len_arr.cpp | 67 +++++++++++++------ 5 files changed, 106 insertions(+), 59 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 866a9697..898a45d1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,8 +40,8 @@ "C_Cpp.configurationWarnings": "Disabled", "files.associations": { "*.py.template": "python", - "*.cc": "c++", - "*.hpp": "c++", + "*.cc": "cpp", + "*.hpp": "cpp", "__bit_reference": "cpp", "__config": "cpp", "__debug": "cpp", diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index c7299789..97df8af3 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -75,7 +75,7 @@ class VariableLengthArray reserve(l.size()); for (const_iterator list_item = l.begin(), end = l.end(); list_item != end; ++list_item) { - push_back_no_alloc(*list_item); + push_back(*list_item); } } @@ -93,7 +93,7 @@ class VariableLengthArray reserve(length); for (size_t inserted = 0; first != last && inserted < length; ++first) { - push_back_no_alloc(*first); + push_back(*first); } } @@ -109,7 +109,7 @@ class VariableLengthArray reserve(rhs.size()); for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) { - push_back_no_alloc(*list_item); + push_back(*list_item); } } @@ -200,13 +200,22 @@ class VariableLengthArray using value_type = T; /// - /// The maximum size (and capacity) of this array. This size is derived + /// The maximum size (and capacity) of this array type. This size is derived /// from the DSDL definition for a field and represents the maximum number of /// elements allowed if the specified allocator is able to provide adequate /// memory (i.e. there may be up to this many elements but there shall never /// be more). /// - static constexpr const std::size_t max_size = MaxSize; + static constexpr const std::size_t type_max_size = MaxSize; + + /// + /// The maximum size (and capacity) of this array. This method is compatible + /// with {@code stl::vector::max_size} and always returns {@code type_max_size}. + /// + constexpr std::size_t max_size() const noexcept + { + return type_max_size; + } // +----------------------------------------------------------------------+ // | ELEMENT ACCESS @@ -374,7 +383,7 @@ class VariableLengthArray /// /// The current number of elements in the array. This number increases with each - /// successful call to {@code push_back_no_alloc} and decreases with each call to + /// successful call to {@code push_back} and decreases with each call to /// {@code pop_back} (when size is > 0). /// constexpr std::size_t size() const noexcept @@ -502,16 +511,17 @@ class VariableLengthArray /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to /// grow the available capacity). /// - constexpr T* push_back_no_alloc() noexcept(std::is_nothrow_default_constructible::value) + constexpr void push_back() noexcept(std::is_nothrow_default_constructible::value) { if (size_ < capacity_) { - return new (&data_[size_++]) T(); + new (&data_[size_++]) T(); } else { - return nullptr; + // TODO: if exceptions are enabled then throw std::length_error } + return; } /// @@ -520,16 +530,17 @@ class VariableLengthArray /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to /// grow the available capacity). /// - constexpr T* push_back_no_alloc(T&& value) noexcept(std::is_nothrow_move_constructible::value) + constexpr void push_back(T&& value) noexcept(std::is_nothrow_move_constructible::value) { if (size_ < capacity_) { - return new (&data_[size_++]) T(std::move(value)); + new (&data_[size_++]) T(std::move(value)); } else { - return nullptr; + // TODO: if exceptions are enabled then throw std::length_error } + return; } /// @@ -538,16 +549,17 @@ class VariableLengthArray /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to /// grow the available capacity). /// - constexpr T* push_back_no_alloc(const T& value) noexcept(std::is_nothrow_copy_constructible::value) + constexpr void push_back(const T& value) noexcept(std::is_nothrow_copy_constructible::value) { if (size_ < capacity_) { - return new (&data_[size_++]) T(value); + new (&data_[size_++]) T(value); } else { - return nullptr; + // TODO: if exceptions are enabled then throw std::length_error } + return; } /// @@ -672,7 +684,7 @@ class VariableLengthArray // required till C++ 17. Redundant but allowed after that. template -const std::size_t VariableLengthArray::max_size; +const std::size_t VariableLengthArray::type_max_size; } // namespace support } // namespace nunavut diff --git a/src/nunavut/lang/cpp/templates/deserialization.j2 b/src/nunavut/lang/cpp/templates/deserialization.j2 index 3206bdc7..715d0bfe 100644 --- a/src/nunavut/lang/cpp/templates/deserialization.j2 +++ b/src/nunavut/lang/cpp/templates/deserialization.j2 @@ -183,7 +183,7 @@ {% set ref_size = 'size'|to_template_unique_name %} // Array length prefix: {{ t.length_field_type }} {{ _deserialize_integer(t.length_field_type, ('const %s %s'|format((t.length_field_type | declaration), ref_size)) , offset) }} - if ( {{ ref_size}} > {{ t.capacity }}U) + if ( {{ ref_size }} > {{ t.capacity }}U) { return -nunavut::support::Error::REPRESENTATION_BAD_ARRAY_LENGTH; } @@ -199,8 +199,11 @@ {% endif %} {# GENERAL CASE #} {% set ref_index = 'index'|to_template_unique_name %} - for (size_t {{ ref_index }} = 0U; {{ ref_index }} < {{ reference }}.size(); ++{{ ref_index }}) + for (size_t {{ ref_index }} = 0U; {{ ref_index }} < {{ ref_size }}; ++{{ ref_index }}) { + // TODO This is terribly inefficient. We need to completely refactor this template to use C++ emplace and + // move semantics instead of assuming C-style containers + {{ reference }}.push_back(); {{ _deserialize_any(t.element_type, reference + ('[%s]'|format(ref_index)), element_offset) |trim|indent diff --git a/verification/cpp/suite/test_serialization.cpp b/verification/cpp/suite/test_serialization.cpp index bb0ec72d..ad24985f 100644 --- a/verification/cpp/suite/test_serialization.cpp +++ b/verification/cpp/suite/test_serialization.cpp @@ -138,42 +138,47 @@ TEST(Serialization, StructReference) obj.i10_4[2] = +0x0055; // original value retained obj.i10_4[3] = -0x00AA; // original value retained obj.f16_le2.reserve(2); - ASSERT_TRUE(nullptr != obj.f16_le2.push_back_no_alloc(-1e9F)); // saturated to -65504 - ASSERT_TRUE(nullptr != obj.f16_le2.push_back_no_alloc(+INFINITY)); // infinity retained + obj.f16_le2.push_back(-1e9F); // saturated to -65504 + ASSERT_EQ(1U, obj.f16_le2.size()); + obj.f16_le2.push_back(+INFINITY); // infinity retained ASSERT_EQ(2U, obj.f16_le2.size()); - //obj.unaligned_bitpacked_3[0] = 0xF5; // 0b101, rest truncated away and ignored TODO:Fix + //obj.unaligned_bitpacked_3[0] = 0xF5; // 0b101, rest truncated away and ignored TODO:Fix obj.unaligned_bitpacked_3[0] = 1; obj.unaligned_bitpacked_3[1] = 0; obj.unaligned_bitpacked_3[2] = 1; - //obj.sealed = 123; // ignored + //obj.sealed = 123; // ignored obj.bytes_lt3.reserve(2); - ASSERT_TRUE(nullptr != obj.bytes_lt3.push_back_no_alloc(111)); - ASSERT_TRUE(nullptr != obj.bytes_lt3.push_back_no_alloc(222)); + obj.bytes_lt3.push_back(111); + ASSERT_EQ(1U, obj.bytes_lt3.size()); + obj.bytes_lt3.push_back(222); ASSERT_EQ(2U, obj.bytes_lt3.size()); obj.bytes_3[0] = -0x77; obj.bytes_3[1] = -0x11; obj.bytes_3[2] = +0x77; obj.u2_le4.reserve(3); - ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0x02)); // retained - ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0x11)); // truncated => 1 - ASSERT_TRUE(nullptr != obj.u2_le4.push_back_no_alloc(0xFF)); // truncated => 3 - //obj.u2_le4.push_back_no_alloc(0xFF); // ignored because the length is 3 + obj.u2_le4.push_back(0x02); // retained + ASSERT_EQ(1U, obj.u2_le4.size()); + obj.u2_le4.push_back(0x11); // truncated => 1 + ASSERT_EQ(2U, obj.u2_le4.size()); + obj.u2_le4.push_back(0xFF); // truncated => 3 ASSERT_EQ(3U, obj.u2_le4.size()); + //obj.u2_le4.push_back(0xFF); // ignored because the length is 3 obj.delimited_fix_le2.reserve(1); - ASSERT_TRUE(nullptr != obj.delimited_fix_le2.push_back_no_alloc()); // ignored + obj.delimited_fix_le2.push_back(); // ignored ASSERT_EQ(1U, obj.delimited_fix_le2.size()); obj.u16_2[0] = 0x1234; obj.u16_2[1] = 0x5678; obj.aligned_bitpacked_3[0] = 0xF1U; // obj.unaligned_bitpacked_lt3.bitpacked[0] = 0xF1U; obj.unaligned_bitpacked_lt3.reserve(2); - ASSERT_TRUE(nullptr != obj.unaligned_bitpacked_lt3.push_back_no_alloc(1)); - ASSERT_TRUE(nullptr != obj.unaligned_bitpacked_lt3.push_back_no_alloc(0)); - ASSERT_EQ(2U, obj.unaligned_bitpacked_lt3.size()); // 0b01, rest truncated - obj.delimited_var_2[0].set_f16(+1e9F); // truncated to infinity - obj.delimited_var_2[1].set_f64(-1e40); // retained + obj.unaligned_bitpacked_lt3.push_back(1); + ASSERT_EQ(1U, obj.unaligned_bitpacked_lt3.size()); + obj.unaligned_bitpacked_lt3.push_back(0); + ASSERT_EQ(2U, obj.unaligned_bitpacked_lt3.size()); // 0b01, rest truncated + obj.delimited_var_2[0].set_f16(+1e9F); // truncated to infinity + obj.delimited_var_2[1].set_f64(-1e40); // retained obj.aligned_bitpacked_le3.reserve(1); - ASSERT_TRUE(nullptr != obj.aligned_bitpacked_le3.push_back_no_alloc(1)); + obj.aligned_bitpacked_le3.push_back(1); ASSERT_EQ(1U, obj.aligned_bitpacked_le3.size()); // only lsb is set, other truncated const uint8_t reference[] = { @@ -312,6 +317,8 @@ TEST(Serialization, StructReference) ASSERT_EQ(0U, obj.aligned_bitpacked_le3.size()); // // Deserialize the above reference representation and compare the result against the original object. + obj.regulated::basics::Struct__0_1::~Struct__0_1(); + new (&obj)regulated::basics::Struct__0_1(); result = obj.deserialize(reference); ASSERT_TRUE(result) << "Error was " << result.error(); ASSERT_EQ(sizeof(reference) - 16U, result.value()); // 16 trailing bytes implicitly truncated away @@ -349,6 +356,8 @@ TEST(Serialization, StructReference) // ASSERT_EQ(1, obj.aligned_bitpacked_le3.bitpacked[0]); // unused MSB are zero-padded // Repeat the above, but apply implicit zero extension somewhere in the middle. + obj.regulated::basics::Struct__0_1::~Struct__0_1(); + new (&obj)regulated::basics::Struct__0_1(); result = obj.deserialize({reference, 25U, 0U}); ASSERT_TRUE(result) << "Error was " << result.error(); ASSERT_EQ(25U, result.value()); // the returned size shall not exceed the buffer size diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index d21e0fe5..6d235689 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -178,27 +178,32 @@ TYPED_TEST(VLATestsGeneric, TestReserve) nunavut::support::VariableLengthArray subject; ASSERT_EQ(0U, subject.capacity()); ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(10U, subject.max_size); + ASSERT_EQ(10U, subject.max_size()); ASSERT_EQ(1U, subject.reserve(1)); ASSERT_EQ(1U, subject.capacity()); ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(10U, subject.max_size); + ASSERT_EQ(10U, subject.max_size()); } TYPED_TEST(VLATestsGeneric, TestPush) { nunavut::support::VariableLengthArray subject; ASSERT_EQ(nullptr, subject.data()); - ASSERT_EQ(nullptr, subject.push_back_no_alloc(1)); + ASSERT_EQ(0U, subject.size()); + subject.push_back(1); + ASSERT_EQ(0U, subject.size()); ASSERT_EQ(10U, subject.reserve(10)); ASSERT_EQ(10U, subject.capacity()); ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(20U, subject.max_size); - const typename TypeParam::value_type* const pushed = subject.push_back_no_alloc(1); + ASSERT_EQ(20U, subject.max_size()); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + const typename TypeParam::value_type* const pushed = &subject[0]; + ASSERT_NE(nullptr, pushed); ASSERT_EQ(*pushed, 1); ASSERT_EQ(1U, subject.size()); @@ -208,7 +213,9 @@ TYPED_TEST(VLATestsGeneric, TestPop) { nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); - const typename TypeParam::value_type* const pushed = subject.push_back_no_alloc(1); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + const typename TypeParam::value_type* const pushed = &subject[0]; ASSERT_NE(nullptr, pushed); ASSERT_EQ(*pushed, 1); ASSERT_EQ(1U, subject.size()); @@ -221,7 +228,9 @@ TYPED_TEST(VLATestsGeneric, TestShrink) { nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); - const typename TypeParam::value_type* const pushed = subject.push_back_no_alloc(1); + subject.push_back(1); + ASSERT_EQ(1U, subject.size()); + const typename TypeParam::value_type* const pushed = &subject[0]; ASSERT_NE(nullptr, pushed); ASSERT_EQ(*pushed, 1); ASSERT_EQ(1U, subject.size()); @@ -261,13 +270,16 @@ TYPED_TEST(VLATestsStatic, TestOutOfMemory) break; } ASSERT_EQ(i, subject.capacity()); - typename TypeParam::value_type* pushed = - subject.push_back_no_alloc(static_cast(i)); + subject.push_back(static_cast(i)); + ASSERT_EQ(i, subject.size()); + typename TypeParam::value_type* pushed = &subject[i-1]; ASSERT_NE(nullptr, pushed); ASSERT_EQ(static_cast(i), *pushed); } ASSERT_TRUE(did_run_out_of_memory); - ASSERT_EQ(nullptr, subject.push_back_no_alloc(0)); + const std::size_t size_before = subject.size(); + subject.push_back(0); + ASSERT_EQ(size_before, subject.size()); for (std::size_t i = 1; i < ran_out_of_memory_at; ++i) { ASSERT_EQ(static_cast(i), subject[i - 1]); @@ -284,13 +296,17 @@ TYPED_TEST(VLATestsStatic, TestOverMaxSize) for (std::size_t i = 1; i <= MaxSize; ++i) { ASSERT_EQ(i, subject.reserve(i)); - typename TypeParam::value_type* pushed = - subject.push_back_no_alloc(static_cast(i)); + subject.push_back(static_cast(i)); + typename TypeParam::value_type* pushed = &subject[i - 1]; + ASSERT_EQ(i, subject.size()); ASSERT_NE(nullptr, pushed); ASSERT_EQ(static_cast(i), *pushed); } ASSERT_EQ(MaxSize, subject.reserve(MaxSize + 1)); - ASSERT_EQ(nullptr, subject.push_back_no_alloc(0)); + + ASSERT_EQ(MaxSize, subject.size()); + subject.push_back(0); + ASSERT_EQ(MaxSize, subject.size()); for (std::size_t i = 0; i < MaxSize; ++i) { ASSERT_EQ(static_cast(i + 1), subject[i]); @@ -322,8 +338,10 @@ TEST(VLATestsNonTrivial, TestDestroy) auto subject = std::make_shared>>(); ASSERT_EQ(10U, subject->reserve(10)); - ASSERT_NE(nullptr, subject->push_back_no_alloc(Doomed(&dtor_called))); - ASSERT_NE(nullptr, subject->push_back_no_alloc(Doomed(&dtor_called))); + subject->push_back(Doomed(&dtor_called)); + ASSERT_EQ(1U, subject->size()); + subject->push_back(Doomed(&dtor_called)); + ASSERT_EQ(2U, subject->size()); ASSERT_EQ(0, dtor_called); subject.reset(); ASSERT_EQ(2, dtor_called); @@ -335,7 +353,8 @@ TEST(VLATestsNonTrivial, TestNonFunamental) nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); - ASSERT_NE(nullptr, subject.push_back_no_alloc(Doomed(&dtor_called))); + subject.push_back(Doomed(&dtor_called)); + ASSERT_EQ(1U, subject.size()); subject.pop_back(); ASSERT_EQ(1, dtor_called); } @@ -355,7 +374,8 @@ TEST(VLATestsNonTrivial, TestNotMovable) nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); NotMovable source; - ASSERT_NE(nullptr, subject.push_back_no_alloc(source)); + subject.push_back(source); + ASSERT_EQ(1U, subject.size()); } TEST(VLATestsNonTrivial, TestMovable) @@ -382,7 +402,9 @@ TEST(VLATestsNonTrivial, TestMovable) }; nunavut::support::VariableLengthArray> subject; ASSERT_EQ(10U, subject.reserve(10)); - Movable* pushed = subject.push_back_no_alloc(Movable(1)); + subject.push_back(Movable(1)); + ASSERT_EQ(1U, subject.size()); + Movable* pushed = &subject[0]; ASSERT_NE(nullptr, pushed); ASSERT_EQ(1, pushed->get_data()); } @@ -390,13 +412,14 @@ TEST(VLATestsNonTrivial, TestMovable) TEST(VLATestsNonTrivial, TestMoveToVector) { nunavut::support::VariableLengthArray> subject; - ASSERT_EQ(decltype(subject)::max_size, subject.reserve(decltype(subject)::max_size)); - for (std::size_t i = 0; i < decltype(subject)::max_size; ++i) + ASSERT_EQ(decltype(subject)::type_max_size, subject.reserve(decltype(subject)::type_max_size)); + for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) { - ASSERT_NE(nullptr, subject.push_back_no_alloc(i)); + subject.push_back(i); + ASSERT_EQ(i+1, subject.size()); } std::vector a(subject.cbegin(), subject.cend()); - for (std::size_t i = 0; i < decltype(subject)::max_size; ++i) + for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) { ASSERT_EQ(i, a[i]); } From a3a592044967489b800214ae5de91daa541d39e7 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Tue, 23 Aug 2022 12:22:49 -0700 Subject: [PATCH 10/20] not sure why you no Windows? --- test/gentest_filters/test_filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gentest_filters/test_filters.py b/test/gentest_filters/test_filters.py index a9fedea1..dc94225c 100644 --- a/test/gentest_filters/test_filters.py +++ b/test/gentest_filters/test_filters.py @@ -248,7 +248,7 @@ def test_filter_includes_cpp_vla(gen_paths): # type: ignore @pytest.mark.parametrize('language_name,namespace_separator', [('c', '_'), ('cpp', '::')]) def test_filter_full_reference_name_via_template(gen_paths, language_name, namespace_separator): root_path = (gen_paths.dsdl_dir / Path("uavcan")).as_posix() - output_path = gen_paths.out_dir / 'filter_and_test' + output_path = (gen_paths.out_dir / Path("filter_and_test")).as_posix() compound_types = read_namespace(root_path, []) language_context = LanguageContext(target_language=language_name) namespace = build_namespace_tree(compound_types, From 2928c17c5ca752dc1b77c0a9e2a81d60de5cf09a Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Fri, 26 Aug 2022 17:22:32 -0700 Subject: [PATCH 11/20] fixing windows and adding except specifiers to new contrustors --- .vscode/settings.json | 6 +- conftest.py | 3 +- .../cpp/support/variable_length_array.hpp | 150 +++++++++++++----- .../cpp/suite/test_var_len_arr_compiles.cpp | 132 ++++++++++----- 4 files changed, 206 insertions(+), 85 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 898a45d1..4d0211e1 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -136,7 +136,11 @@ "any": "cpp", "span": "cpp", "variant": "cpp", - "__bits": "cpp" + "__bits": "cpp", + "filesystem": "cpp", + "__memory": "cpp", + "compare": "cpp", + "concepts": "cpp" }, "python.linting.mypyArgs": [ "--config-file=${workspaceFolder}/tox.ini" diff --git a/conftest.py b/conftest.py index 429d9a83..cabb4be2 100644 --- a/conftest.py +++ b/conftest.py @@ -14,6 +14,7 @@ import tempfile import textwrap import typing +import urllib from doctest import ELLIPSIS import pydsdl @@ -187,7 +188,7 @@ def out_dir(self) -> pathlib.Path: The directory to place test output under for this test case. """ if self._out_dir is None: - self._out_dir = self.create_new_temp_dir(self.test_name) + self._out_dir = self.create_new_temp_dir(urllib.parse.quote_plus(self.test_name)) return self._out_dir @property diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index 97df8af3..a90823fe 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -14,6 +14,9 @@ #include #include #include +#if __cpp_exceptions +#include +#endif namespace nunavut { @@ -66,7 +69,14 @@ class VariableLengthArray , alloc_() {} - VariableLengthArray(std::initializer_list l) noexcept(std::is_nothrow_constructible::value) + VariableLengthArray(std::initializer_list l) noexcept + ( + noexcept(reserve(1)) + && + std::is_nothrow_constructible::value + && + std::is_nothrow_copy_constructible::value + ) : data_(nullptr) , capacity_(0) , size_(0) @@ -75,7 +85,7 @@ class VariableLengthArray reserve(l.size()); for (const_iterator list_item = l.begin(), end = l.end(); list_item != end; ++list_item) { - push_back(*list_item); + push_back_impl(*list_item); } } @@ -84,7 +94,14 @@ class VariableLengthArray InputIt last, const std::size_t length, const Allocator& alloc = Allocator()) - noexcept(std::is_nothrow_constructible::value) + noexcept + ( + noexcept(reserve(1)) + && + std::is_nothrow_copy_constructible::value + && + std::is_nothrow_copy_constructible::value + ) : data_(nullptr) , capacity_(0) , size_(0) @@ -93,14 +110,21 @@ class VariableLengthArray reserve(length); for (size_t inserted = 0; first != last && inserted < length; ++first) { - push_back(*first); + push_back_impl(*first); } } // // Rule of Five. // - VariableLengthArray(const VariableLengthArray& rhs) + VariableLengthArray(const VariableLengthArray& rhs) noexcept + ( + noexcept(reserve(1)) + && + std::is_nothrow_copy_constructible::value + && + std::is_nothrow_copy_constructible::value + ) : data_(nullptr) , capacity_(0) , size_(0) @@ -109,13 +133,13 @@ class VariableLengthArray reserve(rhs.size()); for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) { - push_back(*list_item); + push_back_impl(*list_item); } } VariableLengthArray& operator=(const VariableLengthArray&) = delete; - VariableLengthArray(VariableLengthArray&& rhs) + VariableLengthArray(VariableLengthArray&& rhs) noexcept(std::is_nothrow_move_constructible::value) : data_(rhs.data_) , capacity_(rhs.capacity_) , size_(rhs.size_) @@ -129,20 +153,24 @@ class VariableLengthArray VariableLengthArray& operator=(VariableLengthArray&&) = delete; ~VariableLengthArray() noexcept( - noexcept( - VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval()) - ) + noexcept + ( + VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval()) + ) ) { fast_deallocate(data_, size_, capacity_, alloc_); } template - constexpr bool operator==(const RHSType& rhs) const noexcept( - noexcept(RHSType().size()) && noexcept(RHSType().cbegin()) && noexcept(RHSType().cend()) - && - noexcept(size()) && noexcept(cbegin()) && noexcept(cend()) - ) + constexpr bool operator==(const RHSType& rhs) const noexcept + ( + noexcept(RHSType().size()) + && + noexcept(RHSType().cbegin()) + && + noexcept(RHSType().cend()) + ) { if (size() != rhs.size()) { @@ -179,6 +207,11 @@ class VariableLengthArray return !(operator==(rhs)); } + /// + /// STL-like declaration of pointer type. + /// + using pointer = typename std::add_pointer::type; + /// /// STL-like declaration of the allocator type. /// @@ -506,22 +539,24 @@ class VariableLengthArray // | MODIFIERS // +----------------------------------------------------------------------+ /// - /// Construct a new element on to the back of the array and grow the array size by 1. + /// Construct a new element on to the back of the array and grow the array size by 1. If exceptions are disabled + /// the caller must check before and after this call to see if the size grew to determine success. If using + /// exceptions this method throws {@code std::length_error} if the size of this collection is at capacity. /// /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to /// grow the available capacity). + /// @throw std::length_error if the size of this collection is at capacity. /// - constexpr void push_back() noexcept(std::is_nothrow_default_constructible::value) + constexpr void push_back() { - if (size_ < capacity_) + if (nullptr == push_back_impl()) { - new (&data_[size_++]) T(); + #if __cpp_exceptions + throw std::length_error("size is at capacity. Use reserve to grow the capacity."); + #endif } - else - { - // TODO: if exceptions are enabled then throw std::length_error - } - return; + // else, without exceptions the caller has to check the size property to see if this + // method succeeded. } /// @@ -530,36 +565,31 @@ class VariableLengthArray /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to /// grow the available capacity). /// - constexpr void push_back(T&& value) noexcept(std::is_nothrow_move_constructible::value) + constexpr void push_back(T&& value) { - if (size_ < capacity_) + if (nullptr == push_back_impl(value)) { - new (&data_[size_++]) T(std::move(value)); + #if __cpp_exceptions + throw std::length_error("size is at capacity. Use reserve to grow the capacity."); + #endif } - else - { - // TODO: if exceptions are enabled then throw std::length_error - } - return; + // else, without exceptions the caller has to check the size property to see if this + // method succeeded. } /// /// Push a new element on to the back of the array and grow the array size by 1. /// - /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to - /// grow the available capacity). - /// constexpr void push_back(const T& value) noexcept(std::is_nothrow_copy_constructible::value) { - if (size_ < capacity_) + if (nullptr == push_back_impl(value)) { - new (&data_[size_++]) T(value); + #if __cpp_exceptions + throw std::length_error("size is at capacity. Use reserve to grow the capacity."); + #endif } - else - { - // TODO: if exceptions are enabled then throw std::length_error - } - return; + // else, without exceptions the caller has to check the size property to see if this + // method succeeded. } /// @@ -575,6 +605,42 @@ class VariableLengthArray } private: + constexpr pointer push_back_impl() noexcept(std::is_nothrow_default_constructible::value) + { + if (size_ < capacity_) + { + return new (&data_[size_++]) T(); + } + else + { + return nullptr; + } + } + + constexpr pointer push_back_impl(T&& value) noexcept(std::is_nothrow_move_constructible::value) + { + if (size_ < capacity_) + { + return new (&data_[size_++]) T(std::move(value)); + } + else + { + return nullptr; + } + } + + constexpr pointer push_back_impl(const T& value) noexcept(std::is_nothrow_copy_constructible::value) + { + if (size_ < capacity_) + { + return new (&data_[size_++]) T(value); + } + else + { + return nullptr; + } + } + /// /// If trivially destructible then we don't have to call the destructors. /// diff --git a/verification/cpp/suite/test_var_len_arr_compiles.cpp b/verification/cpp/suite/test_var_len_arr_compiles.cpp index 4c7f63ef..0bff6a57 100644 --- a/verification/cpp/suite/test_var_len_arr_compiles.cpp +++ b/verification/cpp/suite/test_var_len_arr_compiles.cpp @@ -8,7 +8,6 @@ #include "gmock/gmock.h" #include "nunavut/support/variable_length_array.hpp" - template class JunkyNoThrowAllocator { @@ -17,12 +16,19 @@ class JunkyNoThrowAllocator JunkyNoThrowAllocator() noexcept : data_() - {} + { + } JunkyNoThrowAllocator(const JunkyNoThrowAllocator& rhs) noexcept : data_() { - (void)rhs; + (void) rhs; + } + + JunkyNoThrowAllocator(JunkyNoThrowAllocator&& rhs) noexcept + : data_() + { + (void) rhs; } T* allocate(std::size_t n) noexcept @@ -39,15 +45,14 @@ class JunkyNoThrowAllocator constexpr void deallocate(T* p, std::size_t n) noexcept { - (void)p; - (void)n; + (void) p; + (void) n; } private: T data_[SizeCount]; }; - template class JunkyThrowingAllocator { @@ -56,12 +61,19 @@ class JunkyThrowingAllocator JunkyThrowingAllocator() : data_() - {} + { + } JunkyThrowingAllocator(const JunkyThrowingAllocator& rhs) : data_() { - (void)rhs; + (void) rhs; + } + + JunkyThrowingAllocator(JunkyThrowingAllocator&& rhs) + : data_() + { + (void) rhs; } T* allocate(std::size_t n) @@ -78,32 +90,46 @@ class JunkyThrowingAllocator constexpr void deallocate(T* p, std::size_t n) { - (void)p; - (void)n; + (void) p; + (void) n; } private: T data_[SizeCount]; }; +struct ThrowyThing +{ + ThrowyThing() {} + ThrowyThing(const ThrowyThing&) {} + ThrowyThing(ThrowyThing&&) {} +}; + +struct NotThrowyThing +{ + NotThrowyThing() noexcept {} + NotThrowyThing(const NotThrowyThing&) noexcept {} + NotThrowyThing(NotThrowyThing&&) noexcept {} +}; + TEST(VLATestsStatic, TestDefaultAllocator) { nunavut::support::VariableLengthArray subject; static_assert(std::is_nothrow_default_constructible::value, - "VariableLengthArray's default allocator must be no-throw default constructible"); + "VariableLengthArray's default allocator must be no-throw default constructible"); static_assert(std::is_nothrow_constructible::value, - "VariableLengthArray's default allocator must be no-throw constructible."); + "VariableLengthArray's default allocator must be no-throw constructible."); static_assert(std::is_nothrow_destructible::value, - "VariableLengthArray's default allocator must be no-throw destructible.'."); + "VariableLengthArray's default allocator must be no-throw destructible.'."); static_assert(noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must not throw when using the default allocator."); + "VariableLengthArray.reserve must not throw when using the default allocator."); static_assert(noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must not throw exceptions if using the default allocator"); + "VariableLengthArray.shrink_to_fit must not throw exceptions if using the default allocator"); // Use the subject to ensure it isn't elided. ASSERT_EQ(0U, subject.size()); @@ -114,20 +140,20 @@ TEST(VLATestsStatic, TestNoThrowAllocator) nunavut::support::VariableLengthArray> subject; static_assert(std::is_nothrow_default_constructible::value, - "VariableLengthArray must be no-throw default constructible if the allocator is."); + "VariableLengthArray must be no-throw default constructible if the allocator is."); static_assert(std::is_nothrow_constructible::value, - "VariableLengthArray must be no-throw constructible if the allocator is."); + "VariableLengthArray must be no-throw constructible if the allocator is."); static_assert(std::is_nothrow_destructible::value, - "VariableLengthArray must be no-throw destructible if the allocator is."); + "VariableLengthArray must be no-throw destructible if the allocator is."); static_assert(noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must not throw exceptions if Allocator::allocate does not."); + "VariableLengthArray.reserve must not throw exceptions if Allocator::allocate does not."); static_assert(noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must not throw exceptions if Allocator::deallocate " - "and Allocate::allocate do not."); + "VariableLengthArray.shrink_to_fit must not throw exceptions if Allocator::deallocate " + "and Allocate::allocate do not."); // Use the subject to ensure it isn't elided. ASSERT_EQ(0U, subject.size()); @@ -137,25 +163,23 @@ TEST(VLATestsStatic, TestThrowingAllocator) { nunavut::support::VariableLengthArray> subject; - static_assert(std::is_default_constructible::value - && - !std::is_nothrow_default_constructible::value, - "VariableLengthArray must allow exceptions from the constructor if the allocator does."); + static_assert(std::is_default_constructible::value && + !std::is_nothrow_default_constructible::value, + "VariableLengthArray must allow exceptions from the constructor if the allocator does."); - static_assert(std::is_constructible::value - && - !std::is_nothrow_constructible::value, - "VariableLengthArray must allow exceptions from the constructor if the allocator does."); + static_assert(std::is_constructible::value && + !std::is_nothrow_constructible::value, + "VariableLengthArray must allow exceptions from the constructor if the allocator does."); static_assert(!std::is_nothrow_destructible::value, - "VariableLengthArray must be allow exceptions from the destructor if the allocator does."); + "VariableLengthArray must be allow exceptions from the destructor if the allocator does."); static_assert(!noexcept(subject.reserve(0)), - "VariableLengthArray.reserve must allow exceptions if Allocator::allocate does."); + "VariableLengthArray.reserve must allow exceptions if Allocator::allocate does."); static_assert(!noexcept(subject.shrink_to_fit()), - "VariableLengthArray.shrink_to_fit must allow exceptions if either Allocator::deallocate " - "or Allocate::allocate do."); + "VariableLengthArray.shrink_to_fit must allow exceptions if either Allocator::deallocate " + "or Allocate::allocate do."); // Use the subject to ensure it isn't elided. ASSERT_EQ(0U, subject.size()); @@ -164,7 +188,7 @@ TEST(VLATestsStatic, TestThrowingAllocator) /** * Used to verify noexcept status for VariableLengthArray comparison with other container types. */ -template +template struct ThrowyContainer { constexpr std::size_t size() const @@ -181,23 +205,49 @@ struct ThrowyContainer { return nullptr; } - }; TEST(VLATestsStatic, TestThrowingComparitor) { nunavut::support::VariableLengthArray subject; - ThrowyContainer throwy; - std::vector no_throwy; + ThrowyContainer throwy; + std::vector no_throwy; static_assert(!noexcept(subject == throwy), - "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); + "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); static_assert(!noexcept(subject != throwy), - "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); + "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); static_assert(noexcept(subject == no_throwy), - "VariableLengthArray comparison should not throw when used the std::vector type."); + "VariableLengthArray comparison should not throw when used the std::vector type."); static_assert(noexcept(subject != no_throwy), - "VariableLengthArray comparison should not throw when used the std::vector type."); + "VariableLengthArray comparison should not throw when used the std::vector type."); +} + +TEST(VLATestsStatic, TestNotThrowingCopyCtor) +{ + using nothrowy_type = + nunavut::support::VariableLengthArray>; + static_assert(noexcept(nothrowy_type(std::declval::type>())), + "VariableLengthArray copy constructor should not throw if the value type doesn't."); + using nothrowy_type_throwy_allocator = + nunavut::support::VariableLengthArray>; + static_assert(!noexcept(nothrowy_type_throwy_allocator( + std::declval::type>())), + "VariableLengthArray copy constructor should throw if the allocator copy constructor throws even if " + "the value type doesn't."); +} + +TEST(VLATestsStatic, TestThrowingCopyCtor) +{ + using throwy_type = nunavut::support::VariableLengthArray>; + static_assert(!noexcept(throwy_type(std::declval::type>())), + "VariableLengthArray copy constructor should throw if the value type does."); + using throwy_type_nothrowy_allocator = + nunavut::support::VariableLengthArray>; + static_assert(!noexcept(throwy_type_nothrowy_allocator( + std::declval::type>())), + "VariableLengthArray copy constructor should throw if the value type copy constructor throws even if " + "the allocator type doesn't."); } From 5eaf0673dd2b4e6d3de7e67591d69710d8dea677 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Fri, 26 Aug 2022 19:15:54 -0700 Subject: [PATCH 12/20] fixing test --- .../cpp/support/variable_length_array.hpp | 4 +-- verification/cpp/suite/test_var_len_arr.cpp | 31 +++++++++++++++++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index a90823fe..32726eed 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -567,7 +567,7 @@ class VariableLengthArray /// constexpr void push_back(T&& value) { - if (nullptr == push_back_impl(value)) + if (nullptr == push_back_impl(std::move(value))) { #if __cpp_exceptions throw std::length_error("size is at capacity. Use reserve to grow the capacity."); @@ -580,7 +580,7 @@ class VariableLengthArray /// /// Push a new element on to the back of the array and grow the array size by 1. /// - constexpr void push_back(const T& value) noexcept(std::is_nothrow_copy_constructible::value) + constexpr void push_back(const T& value) { if (nullptr == push_back_impl(value)) { diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 6d235689..668d9dc7 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -12,6 +12,7 @@ #include "o1heap/o1heap.h" #include #include +#include /** * Used to test that destructors were called. @@ -192,7 +193,15 @@ TYPED_TEST(VLATestsGeneric, TestPush) nunavut::support::VariableLengthArray subject; ASSERT_EQ(nullptr, subject.data()); ASSERT_EQ(0U, subject.size()); - subject.push_back(1); + try + { + subject.push_back(1); + } + catch(const std::length_error& e) + { + std::cerr << e.what() << '\n'; + } + ASSERT_EQ(0U, subject.size()); ASSERT_EQ(10U, subject.reserve(10)); @@ -278,7 +287,15 @@ TYPED_TEST(VLATestsStatic, TestOutOfMemory) } ASSERT_TRUE(did_run_out_of_memory); const std::size_t size_before = subject.size(); - subject.push_back(0); + try + { + subject.push_back(0); + } + catch(const std::length_error& e) + { + std::cerr << e.what() << '\n'; + } + ASSERT_EQ(size_before, subject.size()); for (std::size_t i = 1; i < ran_out_of_memory_at; ++i) { @@ -305,7 +322,15 @@ TYPED_TEST(VLATestsStatic, TestOverMaxSize) ASSERT_EQ(MaxSize, subject.reserve(MaxSize + 1)); ASSERT_EQ(MaxSize, subject.size()); - subject.push_back(0); + try + { + subject.push_back(0); + } + catch(const std::length_error& e) + { + std::cerr << e.what() << '\n'; + } + ASSERT_EQ(MaxSize, subject.size()); for (std::size_t i = 0; i < MaxSize; ++i) { From bb45764aa8d8e5281b1e37e8d9f47da265ed9599 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Mon, 29 Aug 2022 11:00:29 -0700 Subject: [PATCH 13/20] fixing gcc builds --- setup.cfg | 2 +- .../cpp/support/variable_length_array.hpp | 102 ++++++++++++++---- verification/CMakeLists.txt | 35 +++--- verification/cpp/suite/test_var_len_arr.cpp | 48 ++++++++- .../cpp/suite/test_var_len_arr_compiles.cpp | 40 ------- 5 files changed, 148 insertions(+), 79 deletions(-) diff --git a/setup.cfg b/setup.cfg index 6551e63e..1287facc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,7 +7,7 @@ description = Generate code from DSDL using Jinja2 templates. long_description = file: README.rst long_description_content_type = text/x-rst license = MIT -license_file = LICENSE.rst +license_files = LICENSE.rst keywords = uavcan, dsdl, can, can-bus, codegen, cyphal, opencyphal classifiers = Development Status :: 3 - Alpha diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index 32726eed..6cdd6ce8 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -9,6 +9,7 @@ #define NUNAVUT_SUPPORT_VARIABLE_LENGTH_ARRAY_HPP_INCLUDED #include +#include #include #include #include @@ -57,7 +58,7 @@ class MallocAllocator /// @tparam MaxSize The maximum allowable size and capacity of the array. /// @tparam Allocator The type of allocator. /// -template > +template > class VariableLengthArray { public: @@ -71,7 +72,7 @@ class VariableLengthArray VariableLengthArray(std::initializer_list l) noexcept ( - noexcept(reserve(1)) + noexcept(VariableLengthArray().reserve(1)) && std::is_nothrow_constructible::value && @@ -96,7 +97,7 @@ class VariableLengthArray const Allocator& alloc = Allocator()) noexcept ( - noexcept(reserve(1)) + noexcept(VariableLengthArray().reserve(1)) && std::is_nothrow_copy_constructible::value && @@ -119,7 +120,7 @@ class VariableLengthArray // VariableLengthArray(const VariableLengthArray& rhs) noexcept ( - noexcept(reserve(1)) + noexcept(VariableLengthArray().reserve(1)) && std::is_nothrow_copy_constructible::value && @@ -137,7 +138,29 @@ class VariableLengthArray } } - VariableLengthArray& operator=(const VariableLengthArray&) = delete; + VariableLengthArray& operator=(const VariableLengthArray& rhs) noexcept + ( + noexcept(VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval())) + && + noexcept(VariableLengthArray().reserve(1)) + && + std::is_nothrow_copy_constructible::value + && + std::is_nothrow_copy_constructible::value + ) + { + fast_deallocate(data_, size_, capacity_, alloc_); + data_ = nullptr; + capacity_ = 0; + size_ = 0; + alloc_ = rhs.alloc_; + reserve(rhs.size()); + for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) + { + push_back_impl(*list_item); + } + return *this; + } VariableLengthArray(VariableLengthArray&& rhs) noexcept(std::is_nothrow_move_constructible::value) : data_(rhs.data_) @@ -150,7 +173,28 @@ class VariableLengthArray rhs.size_ = 0; } - VariableLengthArray& operator=(VariableLengthArray&&) = delete; + VariableLengthArray& operator=(VariableLengthArray&& rhs) noexcept + ( + noexcept(VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval())) + && + std::is_nothrow_move_assignable::value + ) + { + fast_deallocate(data_, size_, capacity_, alloc_); + + alloc_ = std::move(rhs.alloc_); + + data_ = rhs.data_; + rhs.data_ = nullptr; + + capacity_ = rhs.capacity_; + rhs.capacity_ = 0; + + size_ = rhs.size_; + rhs.size_ = 0; + + return *this; + } ~VariableLengthArray() noexcept( noexcept @@ -162,15 +206,7 @@ class VariableLengthArray fast_deallocate(data_, size_, capacity_, alloc_); } - template - constexpr bool operator==(const RHSType& rhs) const noexcept - ( - noexcept(RHSType().size()) - && - noexcept(RHSType().cbegin()) - && - noexcept(RHSType().cend()) - ) + constexpr bool operator==(const VariableLengthArray& rhs) const noexcept { if (size() != rhs.size()) { @@ -178,13 +214,13 @@ class VariableLengthArray } if (data() != rhs.data()) { - typename RHSType::const_iterator rnext = rhs.cbegin(); - typename RHSType::const_iterator rend = rhs.cend(); + const_iterator rnext = rhs.cbegin(); + const_iterator rend = rhs.cend(); const_iterator lnext = cbegin(); const_iterator lend = cend(); - for(;rnext != rend && lnext != lend; ++rnext, ++lnext) + for(;rnext != rend && lnext != lend; ++lnext, ++rnext) { - if (*rnext != *lnext) + if (!internal_compare_element(*lnext, *rnext)) { return false; } @@ -199,9 +235,8 @@ class VariableLengthArray return true; } - template - constexpr bool operator!=(const RHSType& rhs) const noexcept( - noexcept(operator==(rhs)) + constexpr bool operator!=(const VariableLengthArray& rhs) const noexcept( + noexcept(operator==(rhs)) ) { return !(operator==(rhs)); @@ -605,6 +640,29 @@ class VariableLengthArray } private: + template + static constexpr bool + internal_compare_element(const U& lhs, const U& rhs, typename std::enable_if::value>::type* = 0) noexcept + { + return (lhs == rhs); + } + + template + static constexpr bool + internal_compare_element(const U& lhs, const U& rhs, typename std::enable_if::value>::type* = 0) noexcept + { + // From the C++ documentation for std::numeric_limits::epsilon() + // https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon + + const auto diff = std::fabs(lhs - rhs); + const auto sum = std::fabs(lhs + rhs); + // Scale the relative machine epsilon up. + const auto epsilon = std::numeric_limits::epsilon() * sum; + + return (diff <= epsilon) || diff < std::numeric_limits::min(); + } + + constexpr pointer push_back_impl() noexcept(std::is_nothrow_default_constructible::value) { if (size_ < capacity_) diff --git a/verification/CMakeLists.txt b/verification/CMakeLists.txt index b64a5d10..d7cd942c 100644 --- a/verification/CMakeLists.txt +++ b/verification/CMakeLists.txt @@ -70,6 +70,8 @@ else() message(FATAL_ERROR "Unknown or no verification language (${NUNAVUT_VERIFICATION_LANG}). Try cmake -DNUNAVUT_VERIFICATION_LANG:string=[cpp|c]") endif() +string(TOLOWER ${NUNAVUT_VERIFICATION_LANG} LOCAL_VERIFICATION_LANG) + if(DEFINED ENV{NUNAVUT_FLAGSET}) set(NUNAVUT_FLAGSET "$ENV{NUNAVUT_FLAGSET}") message(STATUS "Using ${NUNAVUT_FLAGSET} from environment for NUNAVUT_FLAGSET") @@ -214,16 +216,11 @@ create_dsdl_target(dsdl-test add_dependencies(dsdl-test nunavut-support) -# +---------------------------------------------------------------------------+ -# | FLAG SETS -# +---------------------------------------------------------------------------+ -string(TOLOWER ${NUNAVUT_VERIFICATION_LANG} LOCAL_VERIFICATION_LANG) - -if (LOCAL_VERIFICATION_LANG STREQUAL "c") - set(LOCAL_VERIFICATION_LANG_STANDARD_C "${NUNAVUT_VERIFICATION_LANG_STANDARD}") - set(LOCAL_VERIFICATION_LANG_STANDARD_CXX "") - list(APPEND LOCAL_ADDITIONAL_DSDL_LIBS "") -elseif(LOCAL_VERIFICATION_LANG STREQUAL "cpp") +if(LOCAL_VERIFICATION_LANG STREQUAL "cpp") + # C++ tests including going back and forth between c and c++. We include + # c serialization support when verifying c++ for this reason. Future versions of this + # verification build might make this more generic allowing any language to test interoperability + # with any other language. set(LOCAL_VERIFICATION_LANG_STANDARD_C "") set(LOCAL_VERIFICATION_LANG_STANDARD_CXX "${NUNAVUT_VERIFICATION_LANG_STANDARD}") list(APPEND LOCAL_ADDITIONAL_DSDL_LIBS dsdl-regulated-c dsdl-test-c) @@ -279,10 +276,20 @@ elseif(LOCAL_VERIFICATION_LANG STREQUAL "cpp") ${NUNAVUT_SUBMODULES_ROOT}/public_regulated_data_types/uavcan) add_dependencies(dsdl-test-c nunavut-support-c) -elseif(NOT NUNAVUT_VERIFICATION_LANG_STANDARD STREQUAL "") - message(FATAL_ERROR "NUNAVUT_VERIFICATION_LANG_STANDARD was set to ${NUNAVUT_VERIFICATION_LANG_STANDARD} but " - "NUNAVUT_VERIFICATION_LANG language was ${NUNAVUT_VERIFICATION_LANG} which does not " - "support this option") +endif() + +# +---------------------------------------------------------------------------+ +# | FLAG SETS +# +---------------------------------------------------------------------------+ + +if (LOCAL_VERIFICATION_LANG STREQUAL "c") + set(LOCAL_VERIFICATION_LANG_STANDARD_C "${NUNAVUT_VERIFICATION_LANG_STANDARD}") + set(LOCAL_VERIFICATION_LANG_STANDARD_CXX "") + list(APPEND LOCAL_ADDITIONAL_DSDL_LIBS "") +elseif(LOCAL_VERIFICATION_LANG STREQUAL "cpp") + set(LOCAL_VERIFICATION_LANG_STANDARD_C "") + set(LOCAL_VERIFICATION_LANG_STANDARD_CXX "${NUNAVUT_VERIFICATION_LANG_STANDARD}") + list(APPEND LOCAL_ADDITIONAL_DSDL_LIBS "") else() set(LOCAL_VERIFICATION_LANG_STANDARD_C "") set(LOCAL_VERIFICATION_LANG_STANDARD_CXX "") diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 668d9dc7..7e3a7d7d 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -488,8 +488,52 @@ TEST(VLATestsNonTrivial, TestMoveContructor) TEST(VLATestsNonTrivial, TestCompare) { nunavut::support::VariableLengthArray one{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - std::vector two{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; - std::vector three{{9, 8, 7, 6, 5, 4, 3, 2, 1}}; + nunavut::support::VariableLengthArray two{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; + nunavut::support::VariableLengthArray three{{9, 8, 7, 6, 5, 4, 3, 2, 1}}; + ASSERT_EQ(one, one); ASSERT_EQ(one, two); ASSERT_NE(one, three); } + +TEST(VLATestsNonTrivial, TestFPCompare) +{ + nunavut::support::VariableLengthArray one{{1.0, 2.0}}; + nunavut::support::VariableLengthArray two{{1.0, 2.0}}; + const double nexttwo = std::nextafter(2.00, INFINITY); + const double epsilon = nexttwo - 2.00; + nunavut::support::VariableLengthArray three{{1.0, std::nextafter(nexttwo + epsilon, INFINITY)}}; + ASSERT_EQ(one, one); + ASSERT_EQ(one, two); + ASSERT_NE(one, three); +} + +TEST(VLATestsNonTrivial, TestCopyAssignment) +{ + nunavut::support::VariableLengthArray lhs{1.00}; + nunavut::support::VariableLengthArray rhs{{2.00, 3.00}}; + ASSERT_EQ(1U, lhs.size()); + ASSERT_EQ(2U, rhs.size()); + ASSERT_NE(lhs, rhs); + lhs = rhs; + ASSERT_EQ(2U, lhs.size()); + ASSERT_EQ(2U, rhs.size()); + ASSERT_EQ(lhs, rhs); +} + +TEST(VLATestsNonTrivial, TestMoveAssignment) +{ + nunavut::support::VariableLengthArray lhs{{std::string("one"), + std::string("two")}}; + nunavut::support::VariableLengthArray rhs{{std::string("three"), + std::string("four"), + std::string("five")}}; + ASSERT_EQ(2U, lhs.size()); + ASSERT_EQ(3U, rhs.size()); + ASSERT_NE(lhs, rhs); + lhs = std::move(rhs); + ASSERT_EQ(3U, lhs.size()); + ASSERT_EQ(0U, rhs.size()); + ASSERT_EQ(0U, rhs.capacity()); + ASSERT_NE(lhs, rhs); + ASSERT_EQ(std::string("three"), lhs[0]); +} diff --git a/verification/cpp/suite/test_var_len_arr_compiles.cpp b/verification/cpp/suite/test_var_len_arr_compiles.cpp index 0bff6a57..686c2210 100644 --- a/verification/cpp/suite/test_var_len_arr_compiles.cpp +++ b/verification/cpp/suite/test_var_len_arr_compiles.cpp @@ -185,46 +185,6 @@ TEST(VLATestsStatic, TestThrowingAllocator) ASSERT_EQ(0U, subject.size()); } -/** - * Used to verify noexcept status for VariableLengthArray comparison with other container types. - */ -template -struct ThrowyContainer -{ - constexpr std::size_t size() const - { - return 0; - } - - constexpr const T* cbegin() const - { - return nullptr; - } - - constexpr const T* cend() const - { - return nullptr; - } -}; - -TEST(VLATestsStatic, TestThrowingComparitor) -{ - nunavut::support::VariableLengthArray subject; - ThrowyContainer throwy; - std::vector no_throwy; - static_assert(!noexcept(subject == throwy), - "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); - - static_assert(!noexcept(subject != throwy), - "VariableLengthArray comparison should throw when used with the ThrowyContainer type."); - - static_assert(noexcept(subject == no_throwy), - "VariableLengthArray comparison should not throw when used the std::vector type."); - - static_assert(noexcept(subject != no_throwy), - "VariableLengthArray comparison should not throw when used the std::vector type."); -} - TEST(VLATestsStatic, TestNotThrowingCopyCtor) { using nothrowy_type = From 28a36f3de1626087e87c47db11a41ce2465d49c0 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Mon, 29 Aug 2022 16:47:45 -0700 Subject: [PATCH 14/20] fixing logic error in fp test --- verification/cpp/suite/test_var_len_arr.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 7e3a7d7d..91e22509 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -497,11 +497,10 @@ TEST(VLATestsNonTrivial, TestCompare) TEST(VLATestsNonTrivial, TestFPCompare) { - nunavut::support::VariableLengthArray one{{1.0, 2.0}}; - nunavut::support::VariableLengthArray two{{1.0, 2.0}}; - const double nexttwo = std::nextafter(2.00, INFINITY); - const double epsilon = nexttwo - 2.00; - nunavut::support::VariableLengthArray three{{1.0, std::nextafter(nexttwo + epsilon, INFINITY)}}; + nunavut::support::VariableLengthArray one{{1.00, 2.00}}; + nunavut::support::VariableLengthArray two{{1.00, 2.00}}; + const double epsilon_for_two_comparison = std::nextafter(4.00, INFINITY) - 4.00; + nunavut::support::VariableLengthArray three{{1.00, std::nextafter(2.00 + epsilon_for_two_comparison, INFINITY)}}; ASSERT_EQ(one, one); ASSERT_EQ(one, two); ASSERT_NE(one, three); From ef2951647626f0fff55e6d85a9f8e612a24a34f3 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Fri, 23 Sep 2022 20:34:17 -0700 Subject: [PATCH 15/20] tests are broken but pushing to save my work --- .../cpp/support/variable_length_array.hpp | 362 ++++++++++-------- verification/cpp/suite/test_var_len_arr.cpp | 133 ++++--- 2 files changed, 284 insertions(+), 211 deletions(-) diff --git a/src/nunavut/lang/cpp/support/variable_length_array.hpp b/src/nunavut/lang/cpp/support/variable_length_array.hpp index 6cdd6ce8..72336b04 100644 --- a/src/nunavut/lang/cpp/support/variable_length_array.hpp +++ b/src/nunavut/lang/cpp/support/variable_length_array.hpp @@ -16,7 +16,7 @@ #include #include #if __cpp_exceptions -#include +# include #endif namespace nunavut @@ -33,8 +33,7 @@ class MallocAllocator public: using value_type = T; - MallocAllocator() noexcept - {} + MallocAllocator() noexcept {} T* allocate(std::size_t n) noexcept { @@ -48,7 +47,6 @@ class MallocAllocator } }; - /// /// Minimal, generic container for storing UAVCAN variable-length arrays. One property that is unique /// for variable-length arrays is that they have a maximum bound which this implementation enforces. @@ -62,22 +60,17 @@ template ::value) : data_(nullptr) , capacity_(0) , size_(0) , alloc_() - {} - - VariableLengthArray(std::initializer_list l) noexcept - ( - noexcept(VariableLengthArray().reserve(1)) - && - std::is_nothrow_constructible::value - && - std::is_nothrow_copy_constructible::value - ) + { + } + + VariableLengthArray(std::initializer_list l) noexcept( + noexcept(VariableLengthArray().reserve(1)) && + std::is_nothrow_constructible::value && std::is_nothrow_copy_constructible::value) : data_(nullptr) , capacity_(0) , size_(0) @@ -91,18 +84,14 @@ class VariableLengthArray } template - constexpr VariableLengthArray(InputIt first, - InputIt last, - const std::size_t length, - const Allocator& alloc = Allocator()) - noexcept - ( - noexcept(VariableLengthArray().reserve(1)) - && - std::is_nothrow_copy_constructible::value - && - std::is_nothrow_copy_constructible::value - ) + constexpr VariableLengthArray( + InputIt first, + InputIt last, + const std::size_t length, + const Allocator& alloc = + Allocator()) noexcept(noexcept(VariableLengthArray().reserve(1)) && + std::is_nothrow_copy_constructible::value && + std::is_nothrow_copy_constructible::value) : data_(nullptr) , capacity_(0) , size_(0) @@ -118,14 +107,9 @@ class VariableLengthArray // // Rule of Five. // - VariableLengthArray(const VariableLengthArray& rhs) noexcept - ( - noexcept(VariableLengthArray().reserve(1)) - && - std::is_nothrow_copy_constructible::value - && - std::is_nothrow_copy_constructible::value - ) + VariableLengthArray(const VariableLengthArray& rhs) noexcept( + noexcept(VariableLengthArray().reserve(1)) && + std::is_nothrow_copy_constructible::value && std::is_nothrow_copy_constructible::value) : data_(nullptr) , capacity_(0) , size_(0) @@ -138,22 +122,19 @@ class VariableLengthArray } } - VariableLengthArray& operator=(const VariableLengthArray& rhs) noexcept - ( - noexcept(VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval())) - && - noexcept(VariableLengthArray().reserve(1)) - && - std::is_nothrow_copy_constructible::value - && - std::is_nothrow_copy_constructible::value - ) + VariableLengthArray& operator=(const VariableLengthArray& rhs) noexcept( + noexcept(VariableLengthArray::template fast_deallocate( + nullptr, + 0, + 0, + std::declval())) && noexcept(VariableLengthArray().reserve(1)) && + std::is_nothrow_copy_constructible::value && std::is_nothrow_copy_constructible::value) { fast_deallocate(data_, size_, capacity_, alloc_); - data_ = nullptr; + data_ = nullptr; capacity_ = 0; - size_ = 0; - alloc_ = rhs.alloc_; + size_ = 0; + alloc_ = rhs.alloc_; reserve(rhs.size()); for (const_iterator list_item = rhs.cbegin(), end = rhs.cend(); list_item != end; ++list_item) { @@ -168,40 +149,39 @@ class VariableLengthArray , size_(rhs.size_) , alloc_(std::move(rhs.alloc_)) { - rhs.data_ = nullptr; + rhs.data_ = nullptr; rhs.capacity_ = 0; - rhs.size_ = 0; + rhs.size_ = 0; } - VariableLengthArray& operator=(VariableLengthArray&& rhs) noexcept - ( - noexcept(VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval())) - && - std::is_nothrow_move_assignable::value - ) + VariableLengthArray& operator=(VariableLengthArray&& rhs) noexcept( + noexcept(VariableLengthArray::template fast_deallocate(nullptr, + 0, + 0, + std::declval())) && + std::is_nothrow_move_assignable::value) { fast_deallocate(data_, size_, capacity_, alloc_); alloc_ = std::move(rhs.alloc_); - data_ = rhs.data_; + data_ = rhs.data_; rhs.data_ = nullptr; - capacity_ = rhs.capacity_; + capacity_ = rhs.capacity_; rhs.capacity_ = 0; - size_ = rhs.size_; + size_ = rhs.size_; rhs.size_ = 0; return *this; } ~VariableLengthArray() noexcept( - noexcept - ( - VariableLengthArray::template fast_deallocate(nullptr, 0, 0, std::declval()) - ) - ) + noexcept(VariableLengthArray::template fast_deallocate(nullptr, + 0, + 0, + std::declval()))) { fast_deallocate(data_, size_, capacity_, alloc_); } @@ -215,10 +195,10 @@ class VariableLengthArray if (data() != rhs.data()) { const_iterator rnext = rhs.cbegin(); - const_iterator rend = rhs.cend(); + const_iterator rend = rhs.cend(); const_iterator lnext = cbegin(); - const_iterator lend = cend(); - for(;rnext != rend && lnext != lend; ++lnext, ++rnext) + const_iterator lend = cend(); + for (; rnext != rend && lnext != lend; ++lnext, ++rnext) { if (!internal_compare_element(*lnext, *rnext)) { @@ -235,9 +215,7 @@ class VariableLengthArray return true; } - constexpr bool operator!=(const VariableLengthArray& rhs) const noexcept( - noexcept(operator==(rhs)) - ) + constexpr bool operator!=(const VariableLengthArray& rhs) const noexcept(noexcept(operator==(rhs))) { return !(operator==(rhs)); } @@ -467,15 +445,12 @@ class VariableLengthArray /// @param desired_capacity The number of elements to allocate, but not initialize, memory for. /// @return The new (or unchanged) capacity of this object. /// - std::size_t reserve(const std::size_t desired_capacity) noexcept( - noexcept( - allocator_type().allocate(0) - ) - && - noexcept( - VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) - ) - ) + std::size_t reserve(const std::size_t desired_capacity) noexcept(noexcept(allocator_type().allocate(0)) && noexcept( + VariableLengthArray::template move_and_free(nullptr, + nullptr, + 0, + 0, + std::declval()))) { const std::size_t clamped_capacity = (desired_capacity > MaxSize) ? MaxSize : desired_capacity; const std::size_t no_shrink_capacity = (clamped_capacity > size_) ? clamped_capacity : size_; @@ -484,8 +459,7 @@ class VariableLengthArray try { new_data = alloc_.allocate(no_shrink_capacity); - } - catch (const std::bad_alloc& e) + } catch (const std::bad_alloc& e) { // we ignore the exception since all allocation failures are modeled using // null by this class. @@ -512,18 +486,12 @@ class VariableLengthArray /// could not provide enough memory to move the existing objects to a smaller allocation. /// bool shrink_to_fit() noexcept( - noexcept( - allocator_type().deallocate(nullptr, 0) - ) - && - noexcept( - allocator_type().allocate(0) - ) - && - noexcept( - VariableLengthArray::template move_and_free(nullptr, nullptr, 0, 0, std::declval()) - ) - ) + noexcept(allocator_type().deallocate(nullptr, 0)) && noexcept(allocator_type().allocate(0)) && noexcept( + VariableLengthArray::template move_and_free(nullptr, + nullptr, + 0, + 0, + std::declval()))) { if (size_ == capacity_) { @@ -546,8 +514,7 @@ class VariableLengthArray try { minimized_data = alloc_.allocate(size_ * sizeof(T)); - } - catch (const std::bad_alloc& e) + } catch (const std::bad_alloc& e) { // we ignore the exception since all allocation failures are modeled using // null by this class. @@ -574,57 +541,93 @@ class VariableLengthArray // | MODIFIERS // +----------------------------------------------------------------------+ /// - /// Construct a new element on to the back of the array and grow the array size by 1. If exceptions are disabled - /// the caller must check before and after this call to see if the size grew to determine success. If using - /// exceptions this method throws {@code std::length_error} if the size of this collection is at capacity. + /// Construct a new element on to the back of the array. Grows size by 1 and may grow capacity. + /// + /// If exceptions are disabled the caller must check before and after to see if the size grew to determine success. + /// If using exceptions this method throws {@code std::length_error} if the size of this collection is at capacity + /// or {@code std::bad_alloc} if the allocator failed to provide enough memory. + /// + /// If exceptions are disabled use the following logic: + /// + /// const size_t size_before = my_array.size(); + /// my_array.push_back(); + /// if (size_before == my_array.size()) + /// { + /// // failure + /// if (size_before == my_array.max_size()) + /// { + /// // length_error: you probably should have checked this first. + /// } + /// else + /// { + /// // bad_alloc: out of memory + /// } + // } // else, success. /// - /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to - /// grow the available capacity). /// @throw std::length_error if the size of this collection is at capacity. + /// @throw std::bad_alloc if memory was needed and none could be allocated. /// constexpr void push_back() { + if (!ensure_size_plus_one()) + { + return; + } + if (nullptr == push_back_impl()) { - #if __cpp_exceptions +#if __cpp_exceptions throw std::length_error("size is at capacity. Use reserve to grow the capacity."); - #endif +#endif } - // else, without exceptions the caller has to check the size property to see if this - // method succeeded. } /// - /// Push a new element on to the back of the array and grow the array size by 1. + /// Allocate a new element on to the back of the array and move value into it. Grows size by 1 and + /// may grow capacity. /// - /// @return A pointer to the stored value or nullptr if there was not enough capacity (use reserve to - /// grow the available capacity). + /// See VariableLengthArray::push_back() for full documentation. + /// + /// @throw std::length_error if the size of this collection is at capacity. + /// @throw std::bad_alloc if memory was needed and none could be allocated. /// constexpr void push_back(T&& value) { + if (!ensure_size_plus_one()) + { + return; + } + if (nullptr == push_back_impl(std::move(value))) { - #if __cpp_exceptions +#if __cpp_exceptions throw std::length_error("size is at capacity. Use reserve to grow the capacity."); - #endif +#endif } - // else, without exceptions the caller has to check the size property to see if this - // method succeeded. } /// - /// Push a new element on to the back of the array and grow the array size by 1. + /// Allocate a new element on to the back of the array and copy value into it. Grows size by 1 and + /// may grow capacity. + /// + /// See VariableLengthArray::push_back() for full documentation. + /// + /// @throw std::length_error if the size of this collection is at capacity. + /// @throw std::bad_alloc if memory was needed and none could be allocated. /// constexpr void push_back(const T& value) { + if (!ensure_size_plus_one()) + { + return; + } + if (nullptr == push_back_impl(value)) { - #if __cpp_exceptions +#if __cpp_exceptions throw std::length_error("size is at capacity. Use reserve to grow the capacity."); - #endif +#endif } - // else, without exceptions the caller has to check the size property to see if this - // method succeeded. } /// @@ -640,29 +643,83 @@ class VariableLengthArray } private: + /** + * Ensure amortized, constant-time expansion of capacity as single items are added. + */ + constexpr bool ensure_size_plus_one() + { + if (size_ < capacity_) + { + return true; + } + + if (capacity_ == MaxSize) + { +#if __cpp_exceptions + throw std::length_error("Already at max size. Cannot increase capacity."); +#endif + return false; + } + + // https://stackoverflow.com/questions/1100311/what-is-the-ideal-growth-rate-for-a-dynamically-allocated-array/20481237#20481237 + const std::size_t capacity_before = capacity_; + + const std::size_t half_capacity = capacity_before / 2U; + + // That is, instead of a capacity sequence of 1, 2, 3, 4, 6, 9 we start from zero as 2, 4, 6, 9. The first + // opportunity for reusing previously freed memory comes when increasing to 19 from 13 since E(n-1) == 21. + const std::size_t new_capacity = capacity_before + ((half_capacity <= 1) ? 2 : half_capacity); + + + if (new_capacity > MaxSize) + { + reserve(MaxSize); + } + else + { + reserve(new_capacity); + } + + if (capacity_before == capacity_) + { +#if __cpp_exceptions + throw std::bad_alloc(); +#endif + return false; + } + else + { + return true; + } + + } + template - static constexpr bool - internal_compare_element(const U& lhs, const U& rhs, typename std::enable_if::value>::type* = 0) noexcept + static constexpr bool internal_compare_element( + const U& lhs, + const U& rhs, + typename std::enable_if::value>::type* = 0) noexcept { return (lhs == rhs); } template - static constexpr bool - internal_compare_element(const U& lhs, const U& rhs, typename std::enable_if::value>::type* = 0) noexcept + static constexpr bool internal_compare_element( + const U& lhs, + const U& rhs, + typename std::enable_if::value>::type* = 0) noexcept { // From the C++ documentation for std::numeric_limits::epsilon() // https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon const auto diff = std::fabs(lhs - rhs); - const auto sum = std::fabs(lhs + rhs); + const auto sum = std::fabs(lhs + rhs); // Scale the relative machine epsilon up. const auto epsilon = std::numeric_limits::epsilon() * sum; return (diff <= epsilon) || diff < std::numeric_limits::min(); } - constexpr pointer push_back_impl() noexcept(std::is_nothrow_default_constructible::value) { if (size_ < capacity_) @@ -707,10 +764,8 @@ class VariableLengthArray const std::size_t src_size_count, const std::size_t src_capacity_count, Allocator& alloc, - typename std::enable_if::value>::type* = 0) - noexcept( - noexcept(allocator_type().deallocate(nullptr, 0)) - ) + typename std::enable_if::value>::type* = + 0) noexcept(noexcept(allocator_type().deallocate(nullptr, 0))) { (void) src_size_count; alloc.deallocate(src, src_capacity_count); @@ -725,12 +780,8 @@ class VariableLengthArray const std::size_t src_size_count, const std::size_t src_capacity_count, Allocator& alloc, - typename std::enable_if::value>::type* = 0) - noexcept( - std::is_nothrow_destructible::value - && - noexcept(allocator_type().deallocate(nullptr,0)) - ) + typename std::enable_if::value>::type* = + 0) noexcept(std::is_nothrow_destructible::value&& noexcept(allocator_type().deallocate(nullptr, 0))) { std::size_t dtor_iterator = src_size_count; while (dtor_iterator > 0) @@ -744,15 +795,14 @@ class VariableLengthArray /// Move stuff in src to dst and then free all the memory allocated for src. /// template - static constexpr void move_and_free(U* const dst, - U* const src, - std::size_t src_len_count, - std::size_t src_capacity_count, - Allocator& alloc, - typename std::enable_if::value>::type* = 0) - noexcept( - noexcept(fast_deallocate(nullptr, 0, 0, std::declval())) - ) + static constexpr void move_and_free( + U* const dst, + U* const src, + std::size_t src_len_count, + std::size_t src_capacity_count, + Allocator& alloc, + typename std::enable_if::value>::type* = + 0) noexcept(noexcept(fast_deallocate(nullptr, 0, 0, std::declval()))) { if (src_len_count > 0) { @@ -766,25 +816,17 @@ class VariableLengthArray /// Same as above but for non-fundamental types. We can't just memcpy for these. /// template - static constexpr void move_and_free(U* const dst, - U* const src, - std::size_t src_len_count, - std::size_t src_capacity_count, - Allocator& alloc, - typename std::enable_if::value>::type* = 0, - typename std::enable_if< - std::is_move_constructible::value - || - std::is_copy_constructible::value>::type* = 0) - noexcept( - ( - std::is_nothrow_move_constructible::value - || - std::is_nothrow_copy_constructible::value - ) - && - noexcept(fast_deallocate(nullptr, 0, 0, std::declval())) - ) + static constexpr void move_and_free( + U* const dst, + U* const src, + std::size_t src_len_count, + std::size_t src_capacity_count, + Allocator& alloc, + typename std::enable_if::value>::type* = 0, + typename std::enable_if::value || std::is_copy_constructible::value>::type* = + 0) noexcept((std::is_nothrow_move_constructible::value || + std::is_nothrow_copy_constructible< + U>::value) && noexcept(fast_deallocate(nullptr, 0, 0, std::declval()))) { if (src_len_count > 0) { diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 91e22509..04034f92 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -23,16 +23,17 @@ class Doomed Doomed(int* out_signal_dtor) : out_signal_dtor_(out_signal_dtor) , moved_(false) - {} + { + } Doomed(Doomed&& from) noexcept : out_signal_dtor_(from.out_signal_dtor_) , moved_(false) { from.moved_ = true; } - Doomed(const Doomed&) = delete; + Doomed(const Doomed&) = delete; Doomed& operator=(const Doomed&) = delete; - Doomed& operator=(Doomed&&) = delete; + Doomed& operator=(Doomed&&) = delete; ~Doomed() { @@ -62,12 +63,13 @@ class O1HeapAllocator O1HeapAllocator() : heap_() , heap_alloc_(o1heapInit(&heap_[0], SizeCount * sizeof(T), nullptr, nullptr)) - {} + { + } - O1HeapAllocator(const O1HeapAllocator&) = delete; + O1HeapAllocator(const O1HeapAllocator&) = delete; O1HeapAllocator& operator=(const O1HeapAllocator&) = delete; - O1HeapAllocator(O1HeapAllocator&&) = delete; - O1HeapAllocator& operator=(O1HeapAllocator&&) = delete; + O1HeapAllocator(O1HeapAllocator&&) = delete; + O1HeapAllocator& operator=(O1HeapAllocator&&) = delete; T* allocate(std::size_t n) { @@ -92,15 +94,16 @@ template class JunkyStaticAllocator { public: - using value_type = T; - using array_constref_type = const T(&)[SizeCount]; + using value_type = T; + using array_constref_type = const T (&)[SizeCount]; JunkyStaticAllocator() : data_() , alloc_count_(0) , last_alloc_size_(0) , last_dealloc_size_(0) - {} + { + } JunkyStaticAllocator(const JunkyStaticAllocator& rhs) : data_() @@ -152,8 +155,9 @@ class JunkyStaticAllocator { return data_; } + private: - T data_[SizeCount]; + T data_[SizeCount]; std::size_t alloc_count_; std::size_t last_alloc_size_; std::size_t last_dealloc_size_; @@ -165,17 +169,26 @@ class JunkyStaticAllocator */ template class VLATestsGeneric : public ::testing::Test -{}; +{ +}; + +namespace +{ +static constexpr std::size_t VLATestsGeneric_MinMaxSize = O1HEAP_ALIGNMENT * 8; + +} // end anonymous namespace using VLATestsGenericAllocators = ::testing::Types, std::allocator, std::allocator, - O1HeapAllocator, - JunkyStaticAllocator>; + O1HeapAllocator, + JunkyStaticAllocator>; TYPED_TEST_SUITE(VLATestsGeneric, VLATestsGenericAllocators, ); TYPED_TEST(VLATestsGeneric, TestReserve) { + static_assert(10 < VLATestsGeneric_MinMaxSize, + "Test requires max size of array is less than max size of the smallest allocator"); nunavut::support::VariableLengthArray subject; ASSERT_EQ(0U, subject.capacity()); ASSERT_EQ(0U, subject.size()); @@ -190,36 +203,39 @@ TYPED_TEST(VLATestsGeneric, TestReserve) TYPED_TEST(VLATestsGeneric, TestPush) { - nunavut::support::VariableLengthArray subject; + nunavut::support::VariableLengthArray + subject; ASSERT_EQ(nullptr, subject.data()); ASSERT_EQ(0U, subject.size()); - try - { - subject.push_back(1); - } - catch(const std::length_error& e) + + typename TypeParam::value_type x = 0; + const std::size_t max_items_expected_to_work = VLATestsGeneric_MinMaxSize / 4; + for (std::size_t i = 0; i < max_items_expected_to_work; ++i) { - std::cerr << e.what() << '\n'; + std::cerr << i << ": " << subject.capacity() << std::endl; + // + // O1HeapAllocator fails when the subject requests 42 items with 28 items + // already allocated and after allocating then freeing 2, 4, 6, 9, 13, and 19 items (53 items over 6 + // allocations). Since 6 + 2 < NUM_BINS_MAX, 53 items were released, and + // 42 * 8 is < FRAGMENT_SIZE_MAX there doesn't seem to be a valid reason why 01Heap runs out of memory at + // this point? + // + subject.push_back(x); + + ASSERT_EQ(i + 1, subject.size()); + ASSERT_LE(subject.size(), subject.capacity()); + + const typename TypeParam::value_type* const pushed = &subject[i]; + + ASSERT_EQ(*pushed, x); + ++x; } - - ASSERT_EQ(0U, subject.size()); - - ASSERT_EQ(10U, subject.reserve(10)); - - ASSERT_EQ(10U, subject.capacity()); - ASSERT_EQ(0U, subject.size()); - ASSERT_EQ(20U, subject.max_size()); - subject.push_back(1); - ASSERT_EQ(1U, subject.size()); - const typename TypeParam::value_type* const pushed = &subject[0]; - - ASSERT_NE(nullptr, pushed); - ASSERT_EQ(*pushed, 1); - ASSERT_EQ(1U, subject.size()); } TYPED_TEST(VLATestsGeneric, TestPop) { + static_assert(20 < VLATestsGeneric_MinMaxSize, + "Test requires max size of array is less than max size of the smallest allocator"); nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); subject.push_back(1); @@ -235,6 +251,8 @@ TYPED_TEST(VLATestsGeneric, TestPop) TYPED_TEST(VLATestsGeneric, TestShrink) { + static_assert(20 < VLATestsGeneric_MinMaxSize, + "Test requires max size of array is less than max size of the smallest allocator"); nunavut::support::VariableLengthArray subject; ASSERT_EQ(10U, subject.reserve(10)); subject.push_back(1); @@ -254,7 +272,8 @@ TYPED_TEST(VLATestsGeneric, TestShrink) */ template class VLATestsStatic : public ::testing::Test -{}; +{ +}; using VLATestsStaticAllocators = ::testing::Types, JunkyStaticAllocator>; @@ -281,7 +300,7 @@ TYPED_TEST(VLATestsStatic, TestOutOfMemory) ASSERT_EQ(i, subject.capacity()); subject.push_back(static_cast(i)); ASSERT_EQ(i, subject.size()); - typename TypeParam::value_type* pushed = &subject[i-1]; + typename TypeParam::value_type* pushed = &subject[i - 1]; ASSERT_NE(nullptr, pushed); ASSERT_EQ(static_cast(i), *pushed); } @@ -290,8 +309,10 @@ TYPED_TEST(VLATestsStatic, TestOutOfMemory) try { subject.push_back(0); - } - catch(const std::length_error& e) + } catch (const std::length_error& e) + { + std::cerr << e.what() << '\n'; + } catch (const std::bad_alloc& e) { std::cerr << e.what() << '\n'; } @@ -325,8 +346,10 @@ TYPED_TEST(VLATestsStatic, TestOverMaxSize) try { subject.push_back(0); - } - catch(const std::length_error& e) + } catch (const std::length_error& e) + { + std::cerr << e.what() << '\n'; + } catch (const std::bad_alloc& e) { std::cerr << e.what() << '\n'; } @@ -410,7 +433,8 @@ TEST(VLATestsNonTrivial, TestMovable) public: Movable(int data) : data_(data) - {} + { + } Movable(const Movable&) = delete; Movable(Movable&& move_from) noexcept : data_(move_from.data_) @@ -441,7 +465,7 @@ TEST(VLATestsNonTrivial, TestMoveToVector) for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) { subject.push_back(i); - ASSERT_EQ(i+1, subject.size()); + ASSERT_EQ(i + 1, subject.size()); } std::vector a(subject.cbegin(), subject.cend()); for (std::size_t i = 0; i < decltype(subject)::type_max_size; ++i) @@ -471,7 +495,6 @@ TEST(VLATestsNonTrivial, TestCopyContructor) } } - TEST(VLATestsNonTrivial, TestMoveContructor) { nunavut::support::VariableLengthArray fixture{{10, 9, 8, 7, 6, 5, 4, 3, 2, 1}}; @@ -500,7 +523,8 @@ TEST(VLATestsNonTrivial, TestFPCompare) nunavut::support::VariableLengthArray one{{1.00, 2.00}}; nunavut::support::VariableLengthArray two{{1.00, 2.00}}; const double epsilon_for_two_comparison = std::nextafter(4.00, INFINITY) - 4.00; - nunavut::support::VariableLengthArray three{{1.00, std::nextafter(2.00 + epsilon_for_two_comparison, INFINITY)}}; + nunavut::support::VariableLengthArray three{ + {1.00, std::nextafter(2.00 + epsilon_for_two_comparison, INFINITY)}}; ASSERT_EQ(one, one); ASSERT_EQ(one, two); ASSERT_NE(one, three); @@ -521,11 +545,9 @@ TEST(VLATestsNonTrivial, TestCopyAssignment) TEST(VLATestsNonTrivial, TestMoveAssignment) { - nunavut::support::VariableLengthArray lhs{{std::string("one"), - std::string("two")}}; - nunavut::support::VariableLengthArray rhs{{std::string("three"), - std::string("four"), - std::string("five")}}; + nunavut::support::VariableLengthArray lhs{{std::string("one"), std::string("two")}}; + nunavut::support::VariableLengthArray rhs{ + {std::string("three"), std::string("four"), std::string("five")}}; ASSERT_EQ(2U, lhs.size()); ASSERT_EQ(3U, rhs.size()); ASSERT_NE(lhs, rhs); @@ -536,3 +558,12 @@ TEST(VLATestsNonTrivial, TestMoveAssignment) ASSERT_NE(lhs, rhs); ASSERT_EQ(std::string("three"), lhs[0]); } + +TEST(VLATestsNonTrivial, TestPushBackGrowsCapacity) +{ + static constexpr std::size_t MaxSize = 5; + nunavut::support::VariableLengthArray subject; + ASSERT_EQ(0U, subject.capacity()); + subject.push_back(); + ASSERT_LE(1U, subject.capacity()); +} From 612e0f0ccfa2cf803abefad22323f5b4caefa842 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sat, 24 Sep 2022 11:13:48 -0700 Subject: [PATCH 16/20] fixing test --- verification/cpp/suite/test_var_len_arr.cpp | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 04034f92..0b8e46be 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -174,14 +174,14 @@ class VLATestsGeneric : public ::testing::Test namespace { -static constexpr std::size_t VLATestsGeneric_MinMaxSize = O1HEAP_ALIGNMENT * 8; +static constexpr std::size_t VLATestsGeneric_MinMaxSize = O1HEAP_ALIGNMENT; } // end anonymous namespace using VLATestsGenericAllocators = ::testing::Types, std::allocator, std::allocator, - O1HeapAllocator, + O1HeapAllocator, JunkyStaticAllocator>; TYPED_TEST_SUITE(VLATestsGeneric, VLATestsGenericAllocators, ); @@ -209,17 +209,8 @@ TYPED_TEST(VLATestsGeneric, TestPush) ASSERT_EQ(0U, subject.size()); typename TypeParam::value_type x = 0; - const std::size_t max_items_expected_to_work = VLATestsGeneric_MinMaxSize / 4; - for (std::size_t i = 0; i < max_items_expected_to_work; ++i) - { - std::cerr << i << ": " << subject.capacity() << std::endl; - // - // O1HeapAllocator fails when the subject requests 42 items with 28 items - // already allocated and after allocating then freeing 2, 4, 6, 9, 13, and 19 items (53 items over 6 - // allocations). Since 6 + 2 < NUM_BINS_MAX, 53 items were released, and - // 42 * 8 is < FRAGMENT_SIZE_MAX there doesn't seem to be a valid reason why 01Heap runs out of memory at - // this point? - // + for (std::size_t i = 0; i < VLATestsGeneric_MinMaxSize; ++i) + { subject.push_back(x); ASSERT_EQ(i + 1, subject.size()); From b54663bebe880e752d3007f6c9de221ac167d870 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sat, 24 Sep 2022 13:09:53 -0700 Subject: [PATCH 17/20] fixing 32bit builds --- verification/cpp/suite/test_var_len_arr.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/verification/cpp/suite/test_var_len_arr.cpp b/verification/cpp/suite/test_var_len_arr.cpp index 0b8e46be..977d166b 100644 --- a/verification/cpp/suite/test_var_len_arr.cpp +++ b/verification/cpp/suite/test_var_len_arr.cpp @@ -174,14 +174,17 @@ class VLATestsGeneric : public ::testing::Test namespace { -static constexpr std::size_t VLATestsGeneric_MinMaxSize = O1HEAP_ALIGNMENT; +static constexpr std::size_t VLATestsGeneric_MinMaxSize = 32; +static constexpr std::size_t VLATestsGeneric_O1HeapSize = O1HEAP_ALIGNMENT << 5; + +static_assert(VLATestsGeneric_O1HeapSize > VLATestsGeneric_MinMaxSize, "Unexpected test environment encountered."); } // end anonymous namespace using VLATestsGenericAllocators = ::testing::Types, std::allocator, std::allocator, - O1HeapAllocator, + O1HeapAllocator, JunkyStaticAllocator>; TYPED_TEST_SUITE(VLATestsGeneric, VLATestsGenericAllocators, ); From 20e6c9b421278d203b0d13cde54efc11a1f8eaf7 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sat, 24 Sep 2022 17:20:16 -0700 Subject: [PATCH 18/20] removing vestigal comment --- src/nunavut/jinja/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nunavut/jinja/__init__.py b/src/nunavut/jinja/__init__.py index 03318736..6c81c5a8 100644 --- a/src/nunavut/jinja/__init__.py +++ b/src/nunavut/jinja/__init__.py @@ -780,7 +780,7 @@ def __init__(self, namespace: nunavut.Namespace, **kwargs: typing.Any): self._sub_folders = None # type: typing.Optional[pathlib.Path] self._serialization_support_enabled = ( - False # If not enabled then we remove any serialization support files found + False ) if target_language is not None: self._serialization_support_enabled = not target_language.omit_serialization_support From 4d8485cff4a79bb6e5f862a18c54949c310b7c78 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sat, 24 Sep 2022 18:04:12 -0700 Subject: [PATCH 19/20] fixing linter error --- src/nunavut/jinja/__init__.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/nunavut/jinja/__init__.py b/src/nunavut/jinja/__init__.py index 6c81c5a8..1c6dd084 100644 --- a/src/nunavut/jinja/__init__.py +++ b/src/nunavut/jinja/__init__.py @@ -779,9 +779,7 @@ def __init__(self, namespace: nunavut.Namespace, **kwargs: typing.Any): target_language = self.language_context.get_target_language() self._sub_folders = None # type: typing.Optional[pathlib.Path] - self._serialization_support_enabled = ( - False - ) + self._serialization_support_enabled = False if target_language is not None: self._serialization_support_enabled = not target_language.omit_serialization_support From d3ab8e219b7f71674e057f8b3623b7c47314d798 Mon Sep 17 00:00:00 2001 From: Scott Dixon Date: Sun, 25 Sep 2022 14:41:14 -0700 Subject: [PATCH 20/20] fixing code smell --- src/nunavut/cli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nunavut/cli/__init__.py b/src/nunavut/cli/__init__.py index 48f4bf48..873d818f 100644 --- a/src/nunavut/cli/__init__.py +++ b/src/nunavut/cli/__init__.py @@ -511,7 +511,7 @@ def extension_type(raw_arg: str) -> str: def _extra_includes_from_env(env_var_name: str) -> typing.List[str]: try: extra_includes_from_env = os.environ[env_var_name].split(os.pathsep) - logging.info("Additional include directories from {}: %s", env_var_name, str(extra_includes_from_env)) + logging.info("Additional include directories from {}: {}".format(env_var_name, str(extra_includes_from_env))) return extra_includes_from_env except KeyError: return []