Skip to content

Commit

Permalink
Handle more complicated combinations of fsanitize.
Browse files Browse the repository at this point in the history
Deal with the `-fsanitize=address,undefined` syntax, as well as cases
like `-fsanitize=address -fno-sanitize=address`.

Test: ./run_tests.py --rebuild
Test: Manually checked a few cases with more complicated flags
Test: nose2 build
Bug: android/ndk#540
Change-Id: Ib3e740cce53f5007e1d1218898c4c15a19b94922
(cherry picked from commit 4d958275a1026b6d1f3e9c6dd0ff45b6c6d57b8d)
  • Loading branch information
DanAlbert committed Feb 21, 2018
1 parent b4c2758 commit f64cba3
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 4 deletions.
9 changes: 5 additions & 4 deletions build/core/sanitizers.mk
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ NDK_TOOLCHAIN_LIB_DIR := \
NDK_APP_ASAN := $(NDK_APP_DST_DIR)/$(TARGET_ASAN_BASENAME)
NDK_APP_UBSAN := $(NDK_APP_DST_DIR)/$(TARGET_UBSAN_BASENAME)

NDK_ALL_LDFLAGS := $(NDK_APP_LDFLAGS)
NDK_MODULES_LDFLAGS :=
$(foreach __module,$(__ndk_modules),\
$(eval NDK_ALL_LDFLAGS += $(__ndk_modules.$(__module).LDFLAGS)))
NDK_FSANITIZE_LDFLAGS := $(filter -fsanitize=%,$(NDK_ALL_LDFLAGS))
NDK_SANITIZERS := $(patsubst -fsanitize=%,%,$(NDK_FSANITIZE_LDFLAGS))
$(eval NDK_MODULES_LDFLAGS += --module $(__ndk_modules.$(__module).LDFLAGS)))
NDK_SANITIZERS := $(strip \
$(shell $(HOST_PYTHON) $(BUILD_PY)/ldflags_to_sanitizers.py \
$(NDK_APP_LDFLAGS) $(NDK_MODULES_LDFLAGS)))

NDK_SANITIZER_NAME := UBSAN
NDK_SANITIZER_FSANITIZE_ARGS := undefined
Expand Down
70 changes: 70 additions & 0 deletions build/ldflags_to_sanitizers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#!/usr/bin/env python
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Determines which sanitizers should be linked based on ldflags."""
from __future__ import print_function

import sys


def sanitizers_from_args(args):
"""Returns the sanitizers enabled by a given set of ldflags."""
sanitizers = set()
for arg in args:
if arg.startswith('-fsanitize='):
sanitizer_list = arg.partition('=')[2]
sanitizers |= set(sanitizer_list.split(','))
elif arg.startswith('-fno-sanitize='):
sanitizer_list = arg.partition('=')[2]
sanitizers -= set(sanitizer_list.split(','))
return sorted(list(sanitizers))


def argv_to_module_arg_lists(args):
"""Converts module ldflags from argv format to per-module lists.
Flags are passed to us in the following format:
['global flag', '--module', 'flag1', 'flag2', '--module', 'flag 3']
These should be returned as a list for the global flags and a list of
per-module lists, i.e.:
['global flag'], [['flag1', 'flag2'], ['flag1', 'flag3']]
"""
modules = [[]]
for arg in args:
if arg == '--module':
modules.append([])
else:
modules[-1].append(arg)
return modules[0], modules[1:]


def main(argv):
"""Program entry point."""
if len(argv) < 4:
sys.exit(
'usage: ldflags_to_sanitizers.py GLOBAL_FLAGS '
'--module MODULE_FLAGS [--module MODULE_FLAGS...]')

all_sanitizers = list(sanitizers_from_args(sys.argv[1]))
modules_flags = argv_to_module_arg_lists(argv[2:])
for module_flags in modules_flags:
all_sanitizers.extend(sanitizers_from_args(module_flags))
print(' '.join(sorted(set(all_sanitizers))))


if __name__ == '__main__':
main(sys.argv)
89 changes: 89 additions & 0 deletions build/test_ldflags_to_sanitizers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env python
#
# Copyright (C) 2018 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Does test_ldflags_to_sanitizers stuff."""
from __future__ import absolute_import
import unittest

from build.ldflags_to_sanitizers import argv_to_module_arg_lists
from build.ldflags_to_sanitizers import sanitizers_from_args


class LdflagsToSanitizersTest(unittest.TestCase):
def test_sanitizers_from_args_no_sanitize_args(self):
"""Tests that we don't identify sanitizers when there are none."""
self.assertListEqual([], sanitizers_from_args([]))
self.assertListEqual([], sanitizers_from_args(['foo', 'bar']))

def test_sanitizers_from_args_enabled_sanitizers(self):
"""Tests that we find enabled sanitizers."""
self.assertListEqual(
['address'], sanitizers_from_args(['-fsanitize=address']))
self.assertListEqual(
['address'], sanitizers_from_args(['-fsanitize=address', 'foo']))
self.assertListEqual(
['address', 'undefined'],
sanitizers_from_args(
['-fsanitize=address', '-fsanitize=undefined']))
self.assertListEqual(
['address', 'undefined'],
sanitizers_from_args(['-fsanitize=address,undefined']))
self.assertListEqual(
['address', 'undefined'],
sanitizers_from_args(['-fsanitize=address,undefined', 'foo']))

def test_sanitizers_from_args_disabled_sanitizers(self):
"""Tests that we don't find disabled sanitizers."""
self.assertListEqual([], sanitizers_from_args(
['-fno-sanitize=address']))
self.assertListEqual([], sanitizers_from_args(
['-fno-sanitize=address', 'foo']))
self.assertListEqual([], sanitizers_from_args(
['-fno-sanitize=address', '-fno-sanitize=undefined']))
self.assertListEqual([], sanitizers_from_args(
['-fno-sanitize=address,undefined']))
self.assertListEqual([], sanitizers_from_args(
['-fno-sanitize=address,undefined', 'foo']))

def test_sanitizers_from_args_enabled_disabled_sanitizers(self):
"""Tests that we correctly identify only enabled sanitizers."""
self.assertListEqual([], sanitizers_from_args(
['-fsanitize=address', '-fno-sanitize=address']))
self.assertListEqual(['address'], sanitizers_from_args(
['-fsanitize=address', '-fno-sanitize=address',
'-fsanitize=address']))
self.assertListEqual([], sanitizers_from_args(
['-fsanitize=address', '-fno-sanitize=address',
'-fsanitize=address', '-fno-sanitize=address']))
self.assertListEqual(['undefined'], sanitizers_from_args(
['-fsanitize=address,undefined', '-fno-sanitize=address']))
self.assertListEqual(['undefined'], sanitizers_from_args(
['-fsanitize=address', '-fsanitize=undefined',
'-fno-sanitize=address']))

def test_argv_to_module_arg_lists(self):
"""Tests that modules' arguments are properly identified."""
self.assertTupleEqual(([], []), argv_to_module_arg_lists([]))
self.assertTupleEqual((['foo'], []), argv_to_module_arg_lists(['foo']))

self.assertTupleEqual(
([], [['foo', 'bar'], ['baz']]),
argv_to_module_arg_lists(
['--module', 'foo', 'bar', '--module', 'baz']))

self.assertTupleEqual(
(['foo', 'bar'], [['baz']]),
argv_to_module_arg_lists(['foo', 'bar', '--module', 'baz']))

0 comments on commit f64cba3

Please sign in to comment.