Skip to content

Commit

Permalink
compilers: Convert b_sanitize to a free form array option
Browse files Browse the repository at this point in the history
There are a lot of sanitizers these days, and trying to keep up with
them would be entirely too much work. Instead we'll do a compile check
to see if the option is valid. An array makes more sense than a string,
since users could just write `address,undefined` or they could write
`undefined,address`. With the array we get to handle that ourselves and
users writing code know that it's not safe to simply do a check like
```meson
if get_option('b_sanitize') == 'address,undefined'
  ...
endif
```

instead they need to do something like
```meson
opt = get_option('b_sanitizers')
if opt.contains('address') and opt.contains('undefined')
   ...
endif
```

To ensure backwards compatibility, `get_option('b_sanitize')` is
guaranteed to return a string with the values in the same order, unless
a new value is added.

Fixes mesonbuild#8283
Fixes mesonbuild#7761
Fixes mesonbuild#5154
Fixes mesonbuild#1582
  • Loading branch information
dcbaker committed Sep 19, 2024
1 parent 5b7459e commit 74b38b1
Show file tree
Hide file tree
Showing 16 changed files with 150 additions and 72 deletions.
71 changes: 39 additions & 32 deletions docs/markdown/Builtin-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,22 @@ not be relied on, since they can be absolute paths in the following cases:

### Directories

| Option | Default value | Description |
| ------ | ------------- | ----------- |
| prefix | see below | Installation prefix |
| bindir | bin | Executable directory |
| datadir | share | Data file directory |
| includedir | include | Header file directory |
| infodir | share/info | Info page directory |
| libdir | see below | Library directory |
| licensedir | see below | Licenses directory (since 1.1.0)|
| libexecdir | libexec | Library executable directory |
| localedir | share/locale | Locale data directory |
| localstatedir | var | Localstate data directory |
| mandir | share/man | Manual page directory |
| sbindir | sbin | System executable directory |
| sharedstatedir | com | Architecture-independent data directory |
| sysconfdir | etc | Sysconf data directory |
| Option | Default value | Description |
| -------------- | ------------- | --------------------------------------- |
| prefix | see below | Installation prefix |
| bindir | bin | Executable directory |
| datadir | share | Data file directory |
| includedir | include | Header file directory |
| infodir | share/info | Info page directory |
| libdir | see below | Library directory |
| licensedir | see below | Licenses directory (since 1.1.0) |
| libexecdir | libexec | Library executable directory |
| localedir | share/locale | Locale data directory |
| localstatedir | var | Localstate data directory |
| mandir | share/man | Manual page directory |
| sbindir | sbin | System executable directory |
| sharedstatedir | com | Architecture-independent data directory |
| sysconfdir | etc | Sysconf data directory |


`prefix` defaults to `C:/` on Windows, and `/usr/local` otherwise. You
Expand Down Expand Up @@ -139,7 +139,7 @@ from it. For example, `-Dbuildtype=debugoptimized` is the same as
the two-way mapping:

| buildtype | debug | optimization |
| --------- | ----- | ------------ |
| -------------- | ----- | ------------ |
| plain | false | plain |
| debug | true | 0 |
| debugoptimized | true | 2 |
Expand All @@ -154,7 +154,7 @@ Exact flags per warning level is compiler specific, but there is an approximativ
table for most common compilers.

| Warning level | GCC/Clang | MSVC |
| ------------- | --- | ---- |
| ------------- | ------------------------ | ----- |
| 0 | | |
| 1 | -Wall | /W2 |
| 2 | -Wall -Wextra | /W3 |
Expand Down Expand Up @@ -205,7 +205,7 @@ The following options are available. Note that they may not be
available on all platforms or with all compilers:

| Option | Default value | Possible values | Description |
|---------------------|----------------------|---------------------------------------------------------------|--------------------------------------------------------------------------------|
| ------------------- | -------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| b_asneeded | true | true, false | Use -Wl,--as-needed when linking |
| b_bitcode | false | true, false | Embed Apple bitcode, see below |
| b_colorout | always | auto, always, never | Use colored output |
Expand All @@ -224,10 +224,17 @@ 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.6. Before 1.6 they
were string values, and restricted to a specific subset of values: `none`,
`address`, `thread`, `undefined`, `memory`, `leak`, or `address,undefined`. In
1.6 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 value, which is
guaranteed to match the old string value if the array value contains the same
values (ie, `['undefined', 'address'] == 'address,undefined`). If a value not
allowed before 1.6 is used `b_sanitize` will return a string in undefined order.
In 1.6 `get_option('b_sanitize', format : 2)`, will return a free form array,
with no ordering guarantees.

\* < 0 means disable, == 0 means automatic selection, > 0 sets a specific number to use

Expand All @@ -240,7 +247,7 @@ used internally to pick the CRT compiler arguments for `from_buildtype` or
option:

| buildtype | from_buildtype | static_from_buildtype |
| -------- | -------------- | --------------------- |
| -------------- | -------------- | --------------------- |
| debug | `/MDd` | `/MTd` |
| debugoptimized | `/MD` | `/MT` |
| release | `/MD` | `/MT` |
Expand Down Expand Up @@ -378,7 +385,7 @@ option with the module's name:
### Pkgconfig module

| Option | Default value | Possible values | Description |
|-------------|---------------|-----------------|------------------------------------------------------------|
| ----------- | ------------- | --------------- | ---------------------------------------------------------- |
| relocatable | false | true, false | Generate the pkgconfig files as relocatable (Since 0.63.0) |

*Since 0.63.0* The `pkgconfig.relocatable` option is used by the
Expand All @@ -399,13 +406,13 @@ install prefix. For example: if the install prefix is `/usr` and the

### Python module

| Option | Default value | Possible values | Description |
| ------ | ------------- | ----------------- | ----------- |
| bytecompile | 0 | integer from -1 to 2 | What bytecode optimization level to use (Since 1.2.0) |
| install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) |
| platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) |
| purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) |
| allow_limited_api | true | true, false | Disables project-wide use of the Python Limited API (Since 1.3.0) |
| Option | Default value | Possible values | Description |
| ----------------- | ------------- | ------------------------- | ------------------------------------------------------------------------ |
| bytecompile | 0 | integer from -1 to 2 | What bytecode optimization level to use (Since 1.2.0) |
| install_env | prefix | {auto,prefix,system,venv} | Which python environment to install to (Since 0.62.0) |
| platlibdir | | Directory path | Directory for site-specific, platform-specific files (Since 0.60.0) |
| purelibdir | | Directory path | Directory for site-specific, non-platform-specific files (Since 0.60.0) |
| allow_limited_api | true | true, false | Disables project-wide use of the Python Limited API (Since 1.3.0) |

*Since 0.60.0* The `python.platlibdir` and `python.purelibdir` options are used
by the python module methods `python.install_sources()` and
Expand Down
17 changes: 17 additions & 0 deletions docs/markdown/snippets/b_sanitizer_changes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
## Changes to the b_sanitize option

In meson <= 1.6 the b_sanitize option is a combo, which is an enumerated set of
values. One picks one of the values as provided by that enumeration. In 1.6
this was changed to a free array of options, and a compiler check for the
validity of those options.

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 different combinations

In order to not break backwards compatibility, meson will continue to
return `get_option('b_sanitize')` as a string, with a guarantee that
`address,undefined` will remain ordered. Calling
`get_option('b_sanitize', format : 2)`
returns a free form list with no ordering guarantees.
3 changes: 2 additions & 1 deletion mesonbuild/backend/vs2010backend.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -272,7 +273,7 @@ def generate(self,
try:
self.sanitize = self.environment.coredata.get_option(OptionKey('b_sanitize'))
except MesonException:
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()
Expand Down
10 changes: 5 additions & 5 deletions mesonbuild/compilers/compilers.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,7 @@ def init_option(self, name: OptionKey) -> options._U:
choices=['default', 'thin']),
OptionKey('b_thinlto_cache'): BaseOption(options.UserBooleanOption, 'Use LLVM ThinLTO caching for faster incremental builds', False),
OptionKey('b_thinlto_cache_dir'): BaseOption(options.UserStringOption, 'Directory to store ThinLTO cache objects', ''),
OptionKey('b_sanitize'): BaseOption(options.UserComboOption, 'Code sanitizer to use', 'none',
choices=['none', 'address', 'thread', 'undefined', 'memory', 'leak', 'address,undefined']),
OptionKey('b_sanitize'): BaseOption(options.UserArrayOption, 'Code sanitizers to use', []),
OptionKey('b_lundef'): BaseOption(options.UserBooleanOption, 'Use -Wl,--no-undefined when linking', True),
OptionKey('b_asneeded'): BaseOption(options.UserBooleanOption, 'Use -Wl,--as-needed when linking', True),
OptionKey('b_pgo'): BaseOption(options.UserComboOption, 'Use profile guided optimization', 'off',
Expand Down Expand Up @@ -301,7 +300,7 @@ def get_base_compile_args(options: 'KeyedOptionDictType', compiler: 'Compiler',
pass
try:
sani_opt = options.get_value(OptionKey('b_sanitize'))
assert isinstance(sani_opt, str), 'for mypy'
assert isinstance(sani_opt, list), 'for mypy'
sani_args = compiler.sanitizer_compile_args(sani_opt)
# We consider that if there are no sanitizer arguments returned, then the language doesn't support them
if sani_args:
Expand Down Expand Up @@ -362,6 +361,7 @@ def get_base_link_args(options: 'KeyedOptionDictType', linker: 'Compiler',
pass
try:
sani_opt = options.get_value(OptionKey('b_sanitize'))
assert isinstance(sani_opt, list), 'for mypy'
sani_args = linker.sanitizer_link_args(sani_opt)
# We consider that if there are no sanitizer arguments returned, then the language doesn't support them
if sani_args:
Expand Down Expand Up @@ -1024,10 +1024,10 @@ def get_lto_link_args(self, *, threads: int = 0, mode: str = 'default',
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]:
Expand Down
5 changes: 3 additions & 2 deletions mesonbuild/compilers/cuda.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2012-2017 The Meson development team
# Copyright © 2023 Intel Corporation

from __future__ import annotations

Expand Down Expand Up @@ -707,10 +708,10 @@ def get_optimization_args(self, optimization_level: str) -> T.List[str]:
# 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]:
Expand Down
11 changes: 6 additions & 5 deletions mesonbuild/compilers/mixins/gnu.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -497,11 +498,11 @@ def get_lto_compile_args(self, *, threads: int = 0, mode: str = 'default') -> T.
# 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

Expand Down
3 changes: 2 additions & 1 deletion mesonbuild/compilers/mixins/islinker.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 The Meson development team
# Copyright © 2023 Intel Corporation

from __future__ import annotations

Expand Down Expand Up @@ -36,7 +37,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',
Expand Down
11 changes: 5 additions & 6 deletions mesonbuild/compilers/mixins/visualstudio.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2019 The meson development team
# Copyright © 2023 Intel Corporation

from __future__ import annotations

Expand Down Expand Up @@ -166,12 +167,10 @@ def get_compile_only_args(self) -> T.List[str]:
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':
Expand Down
30 changes: 25 additions & 5 deletions mesonbuild/interpreter/interpreter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,22 +1087,42 @@ def get_option_internal(self, optname: str) -> options.UserOption:
raise InterpreterException(f'Tried to access unknown option {optname!r}.')

@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']:
@typed_kwargs(
'get_option',
KwargInfo(
'format', int, default=1, since='1.6.0',
validator=lambda x: "Version must be >= 1 if given" if x < 0 else None
),
)
def func_get_option(self, node: mparser.BaseNode, args: T.Tuple[str],
kwargs: kwtypes.FuncGetOption) -> T.Union[options.UserOption, 'TYPE_var']:
optname = args[0]
opt_fmt = kwargs['format']
if optname == 'b_sanitize':
if opt_fmt > 2:
raise InvalidArguments('b_sanitize only has two option formats')
FeatureNew.single_use('b_sanitize version 2 (as an array)', '1.6',
self.subproject, location=node)
elif opt_fmt != 1:
raise InvalidArguments(f'{optname} does not have multiple versions')

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}')

opt = self.get_option_internal(optname)
if isinstance(opt, options.UserFeatureOption):
opt.name = optname
return opt
elif optname == 'b_sanitize':
assert isinstance(opt, options.UserArrayOption), 'for mypy'
# to ensure backwards compat this always returns a string
if opt_fmt == 1:
return ','.join(sorted(opt.value))
return opt.value
elif isinstance(opt, options.UserOption):
if isinstance(opt.value, str):
return P_OBJ.OptionString(opt.value, f'{{{optname}}}')
Expand Down Expand Up @@ -3077,7 +3097,7 @@ def check_clang_asan_lundef(self) -> None:
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.
Expand Down
9 changes: 7 additions & 2 deletions mesonbuild/interpreter/kwargs.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright © 2021 The Meson Developers
# Copyright © 2021 Intel Corporation
# Copyright 2021 The Meson Developers
# Copyright © 2021-2024 Intel Corporation
from __future__ import annotations

"""Keyword Argument type annotations."""
Expand Down Expand Up @@ -477,3 +477,8 @@ class FuncDeclareDependency(TypedDict):
sources: T.List[T.Union[FileOrString, build.GeneratedTypes]]
variables: T.Dict[str, str]
version: T.Optional[str]


class FuncGetOption(TypedDict):

format: int
Loading

0 comments on commit 74b38b1

Please sign in to comment.