diff options
-rw-r--r-- | docs/markdown/Builtin-options.md | 18 | ||||
-rw-r--r-- | docs/markdown/snippets/b_sanitizer_changes.md | 17 | ||||
-rw-r--r-- | mesonbuild/backend/vs2010backend.py | 3 | ||||
-rw-r--r-- | mesonbuild/compilers/compilers.py | 16 | ||||
-rw-r--r-- | mesonbuild/compilers/cuda.py | 6 | ||||
-rw-r--r-- | mesonbuild/compilers/mixins/gnu.py | 11 | ||||
-rw-r--r-- | mesonbuild/compilers/mixins/islinker.py | 3 | ||||
-rw-r--r-- | mesonbuild/compilers/mixins/visualstudio.py | 11 | ||||
-rw-r--r-- | mesonbuild/interpreter/interpreter.py | 17 | ||||
-rw-r--r-- | mesonbuild/linkers/linkers.py | 22 | ||||
-rw-r--r-- | mesonbuild/modules/gnome.py | 4 | ||||
-rw-r--r-- | test cases/unit/125 sanitizers/meson.build | 8 | ||||
-rw-r--r-- | unittests/allplatformstests.py | 5 | ||||
-rw-r--r-- | unittests/linuxliketests.py | 22 |
14 files changed, 119 insertions, 44 deletions
diff --git a/docs/markdown/Builtin-options.md b/docs/markdown/Builtin-options.md index ffbab47..e1686f8 100644 --- a/docs/markdown/Builtin-options.md +++ b/docs/markdown/Builtin-options.md @@ -231,10 +231,20 @@ available on all platforms or with all compilers: | b_pie | false | true, false | Build position-independent executables (since 0.49.0) | | b_vscrt | from_buildtype | none, md, mdd, mt, mtd, from_buildtype, static_from_buildtype | VS runtime library to use (since 0.48.0) (static_from_buildtype since 0.56.0) | -The value of `b_sanitize` can be one of: `none`, `address`, `thread`, -`undefined`, `memory`, `leak`, `address,undefined`, but note that some -compilers might not support all of them. For example Visual Studio -only supports the address sanitizer. +The default and possible values of sanitizers changed in 1.8. Before 1.8 they +were string values, and restricted to a specific subset of values: `none`, +`address`, `thread`, `undefined`, `memory`, `leak`, or `address,undefined`. In +1.8 it was changed to a free form array of sanitizers, which are checked by a +compiler and linker check. For backwards compatibility reasons +`get_option('b_sanitize')` continues to return a string with the array values +separated by a comma. Furthermore: + + - If the `b_sanitize` option is empty, the `'none'` string is returned. + + - If it contains only the values `'address'` and `'undefined'`, they are + always returned as the `'address,undefined'` string, in this order. + + - Otherwise, the array elements are returned in undefined order. \* < 0 means disable, == 0 means automatic selection, > 0 sets a specific number to use diff --git a/docs/markdown/snippets/b_sanitizer_changes.md b/docs/markdown/snippets/b_sanitizer_changes.md new file mode 100644 index 0000000..f726d70 --- /dev/null +++ b/docs/markdown/snippets/b_sanitizer_changes.md @@ -0,0 +1,17 @@ +## Changes to the b_sanitize option + +Before 1.8 the `b_sanitize` option was a combo option, which is an enumerated +set of values. In 1.8 this was changed to a free-form array of options where +available sanitizers are not hardcoded anymore but instead verified via a +compiler check. + +This solves a number of longstanding issues such as: + + - Sanitizers may be supported by a compiler, but not on a specific platform + (OpenBSD). + - New sanitizers are not recognized by Meson. + - Using sanitizers in previously-unsupported combinations. + +To not break backwards compatibility, calling `get_option('b_sanitize')` +continues to return the configured value as a string, with a guarantee that +`address,undefined` remains ordered. diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py index 1015083..feef3a7 100644 --- a/mesonbuild/backend/vs2010backend.py +++ b/mesonbuild/backend/vs2010backend.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2014-2016 The Meson development team +# Copyright © 2023-2024 Intel Corporation from __future__ import annotations import copy @@ -272,7 +273,7 @@ class Vs2010Backend(backends.Backend): try: self.sanitize = self.environment.coredata.get_option(OptionKey('b_sanitize')) except KeyError: - self.sanitize = 'none' + self.sanitize = [] sln_filename = os.path.join(self.environment.get_build_dir(), self.build.project_name + '.sln') projlist = self.generate_projects(vslite_ctx) self.gen_testproj() diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index 27bc44b..f0744f8 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -222,9 +222,7 @@ BASE_OPTIONS: T.Mapping[OptionKey, options.AnyOptionType] = { options.UserComboOption('b_lto_mode', 'Select between different LTO modes.', 'default', choices=['default', 'thin']), options.UserBooleanOption('b_thinlto_cache', 'Use LLVM ThinLTO caching for faster incremental builds', False), options.UserStringOption('b_thinlto_cache_dir', 'Directory to store ThinLTO cache objects', ''), - options.UserComboOption( - 'b_sanitize', 'Code sanitizer to use', 'none', - choices=['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined']), + options.UserStringArrayOption('b_sanitize', 'Code sanitizer to use', []), options.UserBooleanOption('b_lundef', 'Use -Wl,--no-undefined when linking', True), options.UserBooleanOption('b_asneeded', 'Use -Wl,--as-needed when linking', True), options.UserComboOption( @@ -307,7 +305,9 @@ def get_base_compile_args(target: 'BuildTarget', compiler: 'Compiler', env: 'Env pass try: sanitize = env.coredata.get_option_for_target(target, 'b_sanitize') - assert isinstance(sanitize, str) + assert isinstance(sanitize, list) + if sanitize == ['none']: + sanitize = [] sanitize_args = compiler.sanitizer_compile_args(sanitize) # We consider that if there are no sanitizer arguments returned, then # the language doesn't support them. @@ -376,7 +376,9 @@ def get_base_link_args(target: 'BuildTarget', pass try: sanitizer = env.coredata.get_option_for_target(target, 'b_sanitize') - assert isinstance(sanitizer, str) + assert isinstance(sanitizer, list) + if sanitizer == ['none']: + sanitizer = [] sanitizer_args = linker.sanitizer_link_args(sanitizer) # We consider that if there are no sanitizer arguments returned, then # the language doesn't support them. @@ -1044,10 +1046,10 @@ class Compiler(HoldableObject, metaclass=abc.ABCMeta): thinlto_cache_dir: T.Optional[str] = None) -> T.List[str]: return self.linker.get_lto_args() - def sanitizer_compile_args(self, value: str) -> T.List[str]: + def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: return [] - def sanitizer_link_args(self, value: str) -> T.List[str]: + def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: return self.linker.sanitizer_args(value) def get_asneeded_args(self) -> T.List[str]: diff --git a/mesonbuild/compilers/cuda.py b/mesonbuild/compilers/cuda.py index 134cd4e..612643c 100644 --- a/mesonbuild/compilers/cuda.py +++ b/mesonbuild/compilers/cuda.py @@ -1,6 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2017 The Meson development team -# Copyright © 2024 Intel Corporation +# Copyright © 2023-2024 Intel Corporation from __future__ import annotations @@ -700,10 +700,10 @@ class CudaCompiler(Compiler): # return self._to_host_flags(self.host_compiler.get_optimization_args(optimization_level)) return cuda_optimization_args[optimization_level] - def sanitizer_compile_args(self, value: str) -> T.List[str]: + def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: return self._to_host_flags(self.host_compiler.sanitizer_compile_args(value)) - def sanitizer_link_args(self, value: str) -> T.List[str]: + def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: return self._to_host_flags(self.host_compiler.sanitizer_link_args(value)) def get_debug_args(self, is_debug: bool) -> T.List[str]: diff --git a/mesonbuild/compilers/mixins/gnu.py b/mesonbuild/compilers/mixins/gnu.py index 4dc3445..70fd9ee 100644 --- a/mesonbuild/compilers/mixins/gnu.py +++ b/mesonbuild/compilers/mixins/gnu.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2019-2022 The meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -495,11 +496,11 @@ class GnuLikeCompiler(Compiler, metaclass=abc.ABCMeta): # for their specific arguments return ['-flto'] - def sanitizer_compile_args(self, value: str) -> T.List[str]: - if value == 'none': - return [] - args = ['-fsanitize=' + value] - if 'address' in value: # for -fsanitize=address,undefined + def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + args = ['-fsanitize=' + ','.join(value)] + if 'address' in value: args.append('-fno-omit-frame-pointer') return args diff --git a/mesonbuild/compilers/mixins/islinker.py b/mesonbuild/compilers/mixins/islinker.py index 44040a7..3f35619 100644 --- a/mesonbuild/compilers/mixins/islinker.py +++ b/mesonbuild/compilers/mixins/islinker.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2019 The Meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -37,7 +38,7 @@ class BasicLinkerIsCompilerMixin(Compiler): functionality itself. """ - def sanitizer_link_args(self, value: str) -> T.List[str]: + def sanitizer_link_args(self, value: T.List[str]) -> T.List[str]: return [] def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default', diff --git a/mesonbuild/compilers/mixins/visualstudio.py b/mesonbuild/compilers/mixins/visualstudio.py index 30127ec..275e7ab 100644 --- a/mesonbuild/compilers/mixins/visualstudio.py +++ b/mesonbuild/compilers/mixins/visualstudio.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2019 The meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -166,12 +167,10 @@ class VisualStudioLikeCompiler(Compiler, metaclass=abc.ABCMeta): def get_no_optimization_args(self) -> T.List[str]: return ['/Od', '/Oi-'] - def sanitizer_compile_args(self, value: str) -> T.List[str]: - if value == 'none': - return [] - if value != 'address': - raise mesonlib.MesonException('VS only supports address sanitizer at the moment.') - return ['/fsanitize=address'] + def sanitizer_compile_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + return [f'/fsanitize={",".join(value)}'] def get_output_args(self, outputname: str) -> T.List[str]: if self.mode == 'PREPROCESSOR': diff --git a/mesonbuild/interpreter/interpreter.py b/mesonbuild/interpreter/interpreter.py index ff93ac6..42e6243 100644 --- a/mesonbuild/interpreter/interpreter.py +++ b/mesonbuild/interpreter/interpreter.py @@ -1068,14 +1068,14 @@ class Interpreter(InterpreterBase, HoldableObject): @typed_pos_args('get_option', str) @noKwargs - def func_get_option(self, nodes: mparser.BaseNode, args: T.Tuple[str], - kwargs: 'TYPE_kwargs') -> T.Union[options.UserOption, 'TYPE_var']: + def func_get_option(self, node: mparser.BaseNode, args: T.Tuple[str], + kwargs: kwtypes.FuncGetOption) -> T.Union[options.UserOption, 'TYPE_var']: optname = args[0] + if ':' in optname: raise InterpreterException('Having a colon in option name is forbidden, ' 'projects are not allowed to directly access ' 'options of other subprojects.') - if optname_regex.search(optname.split('.', maxsplit=1)[-1]) is not None: raise InterpreterException(f'Invalid option name {optname!r}') @@ -1096,6 +1096,15 @@ class Interpreter(InterpreterBase, HoldableObject): ocopy.name = optname ocopy.value = value return ocopy + elif optname == 'b_sanitize': + assert isinstance(value_object, options.UserStringArrayOption) + # To ensure backwards compatibility this always returns a string. + # We may eventually want to introduce a new "format" kwarg that + # allows the user to modify this behaviour, but for now this is + # likely good enough for most usecases. + if not value: + return 'none' + return ','.join(sorted(value)) elif isinstance(value_object, options.UserOption): if isinstance(value_object.value, str): return P_OBJ.OptionString(value, f'{{{optname}}}') @@ -3090,7 +3099,7 @@ class Interpreter(InterpreterBase, HoldableObject): if OptionKey('b_sanitize') not in self.coredata.optstore: return if (self.coredata.optstore.get_value('b_lundef') and - self.coredata.optstore.get_value('b_sanitize') != 'none'): + self.coredata.optstore.get_value('b_sanitize')): value = self.coredata.optstore.get_value('b_sanitize') mlog.warning(textwrap.dedent(f'''\ Trying to use {value} sanitizer on Clang with b_lundef. diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index 0dc2c0b..b114d49 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2012-2022 The Meson development team +# Copyright © 2023 Intel Corporation from __future__ import annotations @@ -223,7 +224,7 @@ class DynamicLinker(metaclass=abc.ABCMeta): def get_thinlto_cache_args(self, path: str) -> T.List[str]: return [] - def sanitizer_args(self, value: str) -> T.List[str]: + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: return [] def get_asneeded_args(self) -> T.List[str]: @@ -599,6 +600,9 @@ class PosixDynamicLinkerMixin(DynamicLinkerBase): def get_search_args(self, dirname: str) -> T.List[str]: return ['-L' + dirname] + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + return [] + class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): @@ -654,10 +658,10 @@ class GnuLikeDynamicLinkerMixin(DynamicLinkerBase): def get_lto_args(self) -> T.List[str]: return ['-flto'] - def sanitizer_args(self, value: str) -> T.List[str]: - if value == 'none': - return [] - return ['-fsanitize=' + value] + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + return [f'-fsanitize={",".join(value)}'] def get_coverage_args(self) -> T.List[str]: return ['--coverage'] @@ -811,10 +815,10 @@ class AppleDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def get_coverage_args(self) -> T.List[str]: return ['--coverage'] - def sanitizer_args(self, value: str) -> T.List[str]: - if value == 'none': - return [] - return ['-fsanitize=' + value] + def sanitizer_args(self, value: T.List[str]) -> T.List[str]: + if not value: + return value + return [f'-fsanitize={",".join(value)}'] def no_undefined_args(self) -> T.List[str]: # We used to emit -undefined,error, but starting with Xcode 15 / diff --git a/mesonbuild/modules/gnome.py b/mesonbuild/modules/gnome.py index dffc615..e3b1f3d 100644 --- a/mesonbuild/modules/gnome.py +++ b/mesonbuild/modules/gnome.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2015-2016 The Meson development team +# Copyright © 2023-2024 Intel Corporation '''This module provides helper functions for Gnome/GLib related functionality such as gobject-introspection, gresources and gtk-doc''' @@ -912,9 +913,8 @@ class GnomeModule(ExtensionModule): cflags += state.project_args[lang] if OptionKey('b_sanitize') in compiler.base_options: sanitize = state.environment.coredata.optstore.get_value('b_sanitize') - assert isinstance(sanitize, str) + assert isinstance(sanitize, list) cflags += compiler.sanitizer_compile_args(sanitize) - sanitize = sanitize.split(',') # These must be first in ldflags if 'address' in sanitize: internal_ldflags += ['-lasan'] diff --git a/test cases/unit/125 sanitizers/meson.build b/test cases/unit/125 sanitizers/meson.build new file mode 100644 index 0000000..b42fb35 --- /dev/null +++ b/test cases/unit/125 sanitizers/meson.build @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright © 2023-2024 Intel Corporation + +project('sanitizer', 'c', meson_version : '>= 1.8') + +summary({ + 'value': get_option('b_sanitize'), +}, section: 'summary') diff --git a/unittests/allplatformstests.py b/unittests/allplatformstests.py index 7f4e1e7..84d5245 100644 --- a/unittests/allplatformstests.py +++ b/unittests/allplatformstests.py @@ -1,5 +1,6 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright 2016-2021 The Meson development team +# Copyright © 2023-2024 Intel Corporation import subprocess import re @@ -2770,7 +2771,7 @@ class AllPlatformTests(BasePlatformTests): obj = mesonbuild.coredata.load(self.builddir) self.assertEqual(obj.optstore.get_value('bindir'), 'bar') self.assertEqual(obj.optstore.get_value('buildtype'), 'release') - self.assertEqual(obj.optstore.get_value('b_sanitize'), 'thread') + self.assertEqual(obj.optstore.get_value('b_sanitize'), ['thread']) self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['-Dbar']) self.setconf(['--bindir=bar', '--bindir=foo', '-Dbuildtype=release', '-Dbuildtype=plain', @@ -2779,7 +2780,7 @@ class AllPlatformTests(BasePlatformTests): obj = mesonbuild.coredata.load(self.builddir) self.assertEqual(obj.optstore.get_value('bindir'), 'foo') self.assertEqual(obj.optstore.get_value('buildtype'), 'plain') - self.assertEqual(obj.optstore.get_value('b_sanitize'), 'address') + self.assertEqual(obj.optstore.get_value('b_sanitize'), ['address']) self.assertEqual(obj.optstore.get_value(OptionKey('c_args')), ['-Dfoo']) self.wipe() except KeyError: diff --git a/unittests/linuxliketests.py b/unittests/linuxliketests.py index 08eb4b9..01862b6 100644 --- a/unittests/linuxliketests.py +++ b/unittests/linuxliketests.py @@ -1930,3 +1930,25 @@ class LinuxlikeTests(BasePlatformTests): self.check_has_flag(compdb, mainsrc, '-O3') self.check_has_flag(compdb, sub1src, '-O2') self.check_has_flag(compdb, sub2src, '-O2') + + def test_sanitizers(self): + testdir = os.path.join(self.unit_test_dir, '125 sanitizers') + + with self.subTest('no b_sanitize value'): + try: + out = self.init(testdir) + self.assertRegex(out, 'value *: *none') + finally: + self.wipe() + + for value, expected in { '': 'none', + 'none': 'none', + 'address': 'address', + 'undefined,address': 'address,undefined', + 'address,undefined': 'address,undefined' }.items(): + with self.subTest('b_sanitize=' + value): + try: + out = self.init(testdir, extra_args=['-Db_sanitize=' + value]) + self.assertRegex(out, 'value *: *' + expected) + finally: + self.wipe() |