Преглед изворни кода

Add ASan config and enable it in fastbuild. (#437)

Adds all the necessary machinery to our toolchain and Bazel
configuration to support ASan. This includes ensuring sufficient debug
information is available for backtraces, etc.

As part of ASan, it enables UBSan to catch more basic undefined behavior
in C++. It also enables more complete checking in ASan for lifetime
bugs.

These configs can be enabled in any build mode with `--config=asan`.
They are also enabled by default in `-c fastbuild` where asserts are
also enabled. The goal is to have a single build mode that catches the
overwhelming majority of correctness issues.

Leak checking is part of ASan and finds leaks in `executable_semantics`
code that probably aren't interesting to fix right now. I've disabled
leak checking in the `BUILD` file for the test that showed this --
everything else passed. If more things need this disabled, the same
`BUILD` change should be easily replicated.

If you see unsymbolized backtraces, you may need to either put
`llvm-symbolizer` on your path, or point the `ASAN_SYMBOLIZER_PATH`
environment variable at it. For example, in the project root you could
do something like the following to use the downloaded toolchain's
symbolizer:
```bash export
ASAN_SYMBOLIZER_PATH=$PWD/bazel-clang-toolchain/bin/llvm-symbolizer
```
I'll try to update documentation soon with this as well.
Chandler Carruth пре 5 година
родитељ
комит
7a00f6e15b
3 измењених фајлова са 160 додато и 41 уклоњено
  1. 26 0
      .bazelrc
  2. 130 41
      bazel/cc_toolchains/clang_cc_toolchain_config.bzl
  3. 4 0
      executable_semantics/syntax/BUILD

+ 26 - 0
.bazelrc

@@ -5,10 +5,36 @@
 build --crosstool_top=@bazel_cc_toolchain
 build --host_crosstool_top=@bazel_cc_toolchain
 
+# Allow the toolchain to configure itself differently in the host build from
+# the target build. Even when the host and target platforms are ostensibly the
+# same (and use identical toolchains), it is beneficial to be able to configure
+# the specific toolchain features enabled for the target separately from the
+# host. For example, sanitizers make sense for the target significantly more
+# than for the host.
+#
+# Bazel bug tracking undoing the default here:
+# https://github.com/bazelbuild/bazel/issues/13315
+build --incompatible_dont_enable_host_nonhost_crosstool_features=false
+
+# Completely disable Bazel's automatic stripping of debug information. Removing
+# that information causes unhelpful backtraces from unittest failures and other
+# crashes. Optimized builds already avoid using debug information by default.
+build --strip=never
+
 build:force_local_bootstrap --repo_env=CARBON_FORCE_LOCAL_BOOTSTRAP_BUILD=1
 
+# Configuration for enabling Address Sanitizer. Note that this is enabled by
+# default for fastbuild. The config is provided to enable ASan even in
+# optimized or other build configurations.
+build:asan --features=asan
+
+# Configuration for enabling LibFuzzer (along with ASan).
 build:fuzzer --features=fuzzer
 
+# Always allow tests to symbolize themselves with whatever `llvm-symbolize` is
+# in the users environment.
+test --test_env=ASAN_SYMBOLIZER_PATH
+
 # Force actions to have a UTF-8 language encoding.
 # TODO: Need to investigate what this should be on Windows, but at least for
 # Linux and macOS this seems strictly better than the Bazel default of just

+ 130 - 41
bazel/cc_toolchains/clang_cc_toolchain_config.bzl

@@ -166,37 +166,18 @@ def _impl(ctx):
                 flag_groups = ([
                     flag_group(
                         flags = [
-                            "-g0",
-                            "-O3",
                             "-DNDEBUG",
                             "-ffunction-sections",
                             "-fdata-sections",
-                            # Even when optimizing, preserve frame pointers for
-                            # profiling.
-                            "-fno-omit-frame-pointer",
-                            "-mno-omit-leaf-frame-pointer",
                         ],
                     ),
                 ]),
                 with_features = [with_feature_set(features = ["opt"])],
             ),
-            flag_set(
-                actions = codegen_compile_actions,
-                flag_groups = ([
-                    flag_group(
-                        flags = ["-g"],
-                    ),
-                ]),
-                with_features = [with_feature_set(features = ["dbg"])],
-            ),
             flag_set(
                 actions = codegen_compile_actions,
                 flag_groups = [
                     flag_group(flags = ["-fPIC"], expand_if_available = "pic"),
-                    flag_group(
-                        flags = ["-gsplit-dwarf", "-g"],
-                        expand_if_available = "per_object_debug_info_file",
-                    ),
                 ],
             ),
             flag_set(
@@ -282,6 +263,83 @@ def _impl(ctx):
         ],
     )
 
+    # Handle different levels of optimization with individual features so that
+    # they can be ordered and the defaults can override the minimal settings if
+    # both are enabled.
+    minimal_optimization_flags = feature(
+        name = "minimal_optimization_flags",
+        flag_sets = [flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = [
+                "-O1",
+            ])],
+        )],
+    )
+    default_optimization_flags = feature(
+        name = "default_optimization_flags",
+        enabled = True,
+        requires = [feature_set(["opt"])],
+        flag_sets = [flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = [
+                "-O3",
+            ])],
+        )],
+    )
+
+    # Handle different levels and forms of debug info emission with individual
+    # features so that they can be ordered and the defaults can override the
+    # minimal settings if both are enabled.
+    minimal_debug_info_flags = feature(
+        name = "minimal_debug_info_flags",
+        flag_sets = [flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = [
+                "-gmlt",
+            ])],
+        )],
+    )
+    default_debug_info_flags = feature(
+        name = "default_debug_info_flags",
+        enabled = True,
+        flag_sets = [
+            flag_set(
+                actions = codegen_compile_actions,
+                flag_groups = ([
+                    flag_group(
+                        flags = ["-g"],
+                    ),
+                ]),
+                with_features = [with_feature_set(features = ["dbg"])],
+            ),
+            flag_set(
+                actions = codegen_compile_actions,
+                flag_groups = [
+                    flag_group(
+                        flags = ["-gsplit-dwarf", "-g"],
+                        expand_if_available = "per_object_debug_info_file",
+                    ),
+                ],
+            ),
+        ],
+    )
+
+    # This feature can be enabled in conjunction with any optimizations to
+    # ensure accurate call stacks and backtraces for profilers or errors.
+    preserve_call_stacks = feature(
+        name = "preserve_call_stacks",
+        flag_sets = [flag_set(
+            actions = codegen_compile_actions,
+            flag_groups = [flag_group(flags = [
+                # Ensure good backtraces by preserving frame pointers and
+                # disabling tail call elimination.
+                "-fno-omit-frame-pointer",
+                "-mno-omit-leaf-frame-pointer",
+                "-fno-optimize-sibling-calls",
+            ])],
+        )],
+    )
+
     sysroot_feature = feature(
         name = "sysroot",
         enabled = True,
@@ -361,28 +419,49 @@ def _impl(ctx):
             ),
         ],
     )
+
+    sanitizer_common_flags = feature(
+        name = "sanitizer_common_flags",
+        requires = [feature_set(["nonhost"])],
+        implies = ["minimal_optimization_flags", "minimal_debug_info_flags", "preserve_call_stacks"],
+        flag_sets = [flag_set(
+            actions = all_link_actions,
+            flag_groups = [flag_group(flags = [
+                "-static-libsan",
+            ])],
+        )],
+    )
+
+    asan = feature(
+        name = "asan",
+        requires = [feature_set(["nonhost"])],
+        implies = ["sanitizer_common_flags"],
+        flag_sets = [flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flag_groups = [flag_group(flags = [
+                "-fsanitize=address,undefined",
+                "-fsanitize-address-use-after-scope",
+            ])],
+        )],
+    )
+
+    enable_asan_in_fastbuild = feature(
+        name = "enable_asan_in_fastbuild",
+        enabled = True,
+        requires = [feature_set(["nonhost", "fastbuild"])],
+        implies = ["asan"],
+    )
+
     fuzzer = feature(
         name = "fuzzer",
-        flag_sets = [
-            flag_set(
-                actions = all_compile_actions + all_link_actions,
-                flag_groups = [
-                    flag_group(
-                        flags = ["-fsanitize=fuzzer,address"],
-                    ),
-                ],
-            ),
-            flag_set(
-                actions = all_link_actions,
-                flag_groups = ([
-                    flag_group(
-                        flags = [
-                            "-static-libsan",
-                        ],
-                    ),
-                ]),
-            ),
-        ],
+        requires = [feature_set(["nonhost"])],
+        implies = ["asan"],
+        flag_sets = [flag_set(
+            actions = all_compile_actions + all_link_actions,
+            flag_groups = [flag_group(flags = [
+                "-fsanitize=fuzzer",
+            ])],
+        )],
     )
 
     linux_flags_feature = feature(
@@ -612,20 +691,30 @@ def _impl(ctx):
 
     # First, define features that are simply used to configure others.
     features = [
-        feature(name = "no_legacy_features"),
         feature(name = "dbg"),
         feature(name = "fastbuild"),
+        feature(name = "host"),
+        feature(name = "no_legacy_features"),
+        feature(name = "nonhost"),
         feature(name = "opt"),
+        feature(name = "supports_dynamic_linker", enabled = ctx.attr.target_cpu == "k8"),
         feature(name = "supports_pic", enabled = True),
         feature(name = "supports_start_end_lib", enabled = ctx.attr.target_cpu == "k8"),
-        feature(name = "supports_dynamic_linker", enabled = ctx.attr.target_cpu == "k8"),
     ]
 
     # The order of the features determines the relative order of flags used.
     # Start off adding the baseline features.
     features += [
         default_flags_feature,
+        minimal_optimization_flags,
+        default_optimization_flags,
+        minimal_debug_info_flags,
+        default_debug_info_flags,
+        preserve_call_stacks,
         sysroot_feature,
+        sanitizer_common_flags,
+        asan,
+        enable_asan_in_fastbuild,
         fuzzer,
         layering_check,
         module_maps,

+ 4 - 0
executable_semantics/syntax/BUILD

@@ -46,6 +46,10 @@ cc_library(
 cc_test(
     name = "paren_contents_test",
     srcs = ["paren_contents_test.cpp"],
+    env = {
+        # FIXME: Remove this when leaks are fixed.
+        "ASAN_OPTIONS": "detect_leaks=0",
+    },
     deps = [
         ":paren_contents",
         "@llvm-project//llvm:gtest",