pw_span: Add flag to enable asserts

Optionally backs Pigweed's std::span polyfill with PW_ASSERT() rather
than always disabling assert checks.

Bug: b/234873709
Change-Id: I2c0a316a9c7f0753fb3f0d81d93715512989d420
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/59496
Reviewed-by: Tom Craig <tommycraig@gmail.com>
Reviewed-by: Wyatt Hepler <hepler@google.com>
Pigweed-Auto-Submit: Armando Montanez <amontanez@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/.gn b/.gn
index 4dcf1b7..3fe130b 100644
--- a/.gn
+++ b/.gn
@@ -15,6 +15,9 @@
 buildconfig = "//BUILDCONFIG.gn"
 
 default_args = {
+  # Default all upstream Pigweed toolchains to enable pw::span asserts.
+  pw_span_ENABLE_ASSERTS = true
+
   pw_build_PIP_CONSTRAINTS =
       [ "//pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list" ]
 
diff --git a/pw_span/BUILD.bazel b/pw_span/BUILD.bazel
index 61efc4a..d1a800d 100644
--- a/pw_span/BUILD.bazel
+++ b/pw_span/BUILD.bazel
@@ -25,9 +25,15 @@
 pw_cc_library(
     name = "pw_span",
     srcs = ["public/pw_span/internal/span_common.inc"],
-    hdrs = ["public/pw_span/span.h"],
+    hdrs = [
+        "public/pw_span/internal/config.h",
+        "public/pw_span/span.h",
+    ],
     includes = ["public"],
-    deps = ["//pw_polyfill"],
+    deps = [
+        # TODO(b/243851191): Depending on pw_assert causes a dependency cycle.
+        "//pw_polyfill",
+    ],
 )
 
 pw_cc_test(
diff --git a/pw_span/BUILD.gn b/pw_span/BUILD.gn
index 3fe2c06..e9f1901 100644
--- a/pw_span/BUILD.gn
+++ b/pw_span/BUILD.gn
@@ -14,16 +14,58 @@
 
 import("//build_overrides/pigweed.gni")
 
+import("$dir_pw_build/module_config.gni")
 import("$dir_pw_build/target_types.gni")
 import("$dir_pw_docgen/docs.gni")
 import("$dir_pw_toolchain/traits.gni")
 import("$dir_pw_unit_test/test.gni")
 
+declare_args() {
+  # Whether or not to enable bounds-checking asserts in pw::span. Enabling this
+  # may significantly increase binary size, and can introduce dependency cycles
+  # if your pw_assert backend's headers depends directly or indirectly on
+  # pw_span. It's recommended to enable this for debug builds if possible.
+  pw_span_ENABLE_ASSERTS = false
+
+  # The build target that overrides the default configuration options for this
+  # module. This should point to a source set that provides defines through a
+  # public config (which may -include a file or add defines directly).
+  #
+  # Most modules depend on pw_build_DEFAULT_MODULE_CONFIG as the default config,
+  # but since this module's config options require interaction with the build
+  # system, this defaults to an internal config to properly support
+  # pw_span_ENABLE_ASSERTS.
+  pw_span_CONFIG = "$dir_pw_span:span_asserts"
+}
+
+config("public_include_path") {
+  include_dirs = [ "public" ]
+  visibility = [ ":*" ]
+}
+
+pw_source_set("config") {
+  public = [ "public/pw_span/internal/config.h" ]
+  public_configs = [ ":public_include_path" ]
+  public_deps = [ pw_span_CONFIG ]
+  remove_public_deps = [ "*" ]
+  visibility = [ ":*" ]
+}
+
 config("public_config") {
   include_dirs = [ "public" ]
   visibility = [ ":*" ]
 }
 
+config("span_asserts_config") {
+  defines = [ "PW_SPAN_ENABLE_ASSERTS=${pw_span_ENABLE_ASSERTS}" ]
+  visibility = [ ":span_asserts" ]
+}
+
+pw_source_set("span_asserts") {
+  public_configs = [ ":span_asserts_config" ]
+  visibility = [ ":config" ]
+}
+
 # Provides "pw_span/span.h" and pw::span.
 pw_source_set("pw_span") {
   public = [ "public/pw_span/span.h" ]
@@ -32,7 +74,10 @@
 
 pw_source_set("common") {
   public_configs = [ ":public_config" ]
-  public_deps = [ dir_pw_polyfill ]
+  public_deps = [
+    ":config",
+    "$dir_pw_polyfill",
+  ]
 
   # Polyfill <cstddef> (std::byte) and <iterator> (std::size(), std::data) if
   # C++17 is not supported.
@@ -43,6 +88,11 @@
     ]
   }
 
+  # Only add a dependency on pw_assert if the flag is explicitly enabled.
+  if (pw_span_ENABLE_ASSERTS) {
+    public_deps += [ "$dir_pw_assert:assert" ]
+  }
+
   sources = [ "public/pw_span/internal/span_common.inc" ]
   visibility = [ ":*" ]
 }
diff --git a/pw_span/CMakeLists.txt b/pw_span/CMakeLists.txt
index a63ab66..4e30377 100644
--- a/pw_span/CMakeLists.txt
+++ b/pw_span/CMakeLists.txt
@@ -18,10 +18,12 @@
 pw_add_module_library(pw_span
   HEADERS
     public/pw_span/span.h
+    public/pw_span/internal/config.h
     public/pw_span/internal/span_common.inc
   PUBLIC_INCLUDES
     public
   PUBLIC_DEPS
+    pw_assert
     pw_polyfill
     pw_polyfill.standard_library
 )
diff --git a/pw_span/docs.rst b/pw_span/docs.rst
index 2a7dc98..8a2d1b6 100644
--- a/pw_span/docs.rst
+++ b/pw_span/docs.rst
@@ -65,6 +65,26 @@
   ``pw_bytes/span.h`` provides ``ByteSpan`` and ``ConstByteSpan`` aliases for
   these types.
 
+----------------------------
+Module Configuration Options
+----------------------------
+The following configurations can be adjusted via compile-time configuration of
+this module, see the
+:ref:`module documentation <module-structure-compile-time-configuration>` for
+more details.
+
+.. c:macro:: PW_SPAN_ENABLE_ASSERTS
+
+  PW_SPAN_ENABLE_ASSERTS controls whether pw_span's implementation includes
+  asserts for detecting disallowed span operations at runtime. For C++20 and
+  later, this replaces std::span with the custom implementation in pw_span to
+  ensure bounds-checking asserts have been enabled.
+
+  This defaults to disabled because of the significant increase in code size
+  caused by enabling this feature. It's strongly recommended to enable this
+  in debug and testing builds. This can be done by setting
+  ``pw_span_ENABLE_ASSERTS`` to ``true`` in the GN build.
+
 -------------
 Compatibility
 -------------
diff --git a/pw_span/public/pw_span/internal/config.h b/pw_span/public/pw_span/internal/config.h
new file mode 100644
index 0000000..89e6fe3
--- /dev/null
+++ b/pw_span/public/pw_span/internal/config.h
@@ -0,0 +1,26 @@
+// Copyright 2022 The Pigweed Authors
+//
+// 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
+//
+//     https://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.
+#pragma once
+
+// PW_SPAN_ENABLE_ASSERTS controls whether pw_span's implementation includes
+// asserts for detecting disallowed span operations at runtime. For C++20 and
+// later, this replaces std::span with the custom implementation in pw_span to
+// ensure bounds-checking asserts have been enabled.
+//
+// This defaults to disabled because of the significant increase in code size
+// caused by enabling this feature. It's strongly recommended to enable this
+// in debug and testing builds.
+#if !defined(PW_SPAN_ENABLE_ASSERTS)
+#define PW_SPAN_ENABLE_ASSERTS 0
+#endif  // !defined(PW_SPAN_ENABLE_ASSERTS)
diff --git a/pw_span/public/pw_span/internal/span_common.inc b/pw_span/public/pw_span/internal/span_common.inc
index a347ffc..ac68adc 100644
--- a/pw_span/public/pw_span/internal/span_common.inc
+++ b/pw_span/public/pw_span/internal/span_common.inc
@@ -45,9 +45,17 @@
 #include <utility>
 
 #include "pw_polyfill/language_feature_macros.h"
+#include "pw_span/internal/config.h"
 
-// Pigweed: Disable the asserts from Chromium for now.
+#if PW_SPAN_ENABLE_ASSERTS
+
+#include "pw_assert/assert.h"
+
+#define _PW_SPAN_ASSERT(arg) PW_ASSERT(arg)
+
+#else
 #define _PW_SPAN_ASSERT(arg)
+#endif  // PW_SPAN_ENABLE_ASSERTS
 
 // The file that includes this .inc file must define the following macros:
 //
diff --git a/pw_span/public/pw_span/span.h b/pw_span/public/pw_span/span.h
index cd4dce5..69a118c 100644
--- a/pw_span/public/pw_span/span.h
+++ b/pw_span/public/pw_span/span.h
@@ -16,13 +16,17 @@
 // implementation is shared with the std::span polyfill class.
 #pragma once
 
+#include "pw_span/internal/config.h"
+
 #if __has_include(<version>)
 #include <version>
 #endif  // __has_include(<version>)
 
-// If the C++ library fully supports <span>, pw::span is an alias of std::span.
-#if defined(__cpp_lib_span) && __cpp_lib_span >= 202002L || \
-    defined(_PW_SPAN_POLYFILL_ENABLED)
+// If the C++ library fully supports <span>, pw::span is an alias of std::span,
+// but only if PW_SPAN_ENABLE_ASSERTS is not enabled.
+#if !PW_SPAN_ENABLE_ASSERTS &&                               \
+    (defined(__cpp_lib_span) && __cpp_lib_span >= 202002L || \
+     defined(_PW_SPAN_POLYFILL_ENABLED))
 
 #include <span>
 
diff --git a/targets/host/target_toolchains.gni b/targets/host/target_toolchains.gni
index e657c30..a50fe73 100644
--- a/targets/host/target_toolchains.gni
+++ b/targets/host/target_toolchains.gni
@@ -422,6 +422,11 @@
       forward_variables_from(_os_specific_config, "*")
       default_configs += _internal_clang_default_configs
 
+      # Don't enable span asserts since C++20 provides the implementation for
+      # pw::span, and there's no way to ensure asserts are enabled for the C++
+      # standard library's std::span implementation.
+      pw_span_ENABLE_ASSERTS = false
+
       # Set the C++ standard to C++20 instead of the default.
       pw_toolchain_CXX_STANDARD = pw_toolchain_STANDARD.CXX20
     }