pw_preprocessor: Macros for disabling warnings

Change-Id: I00e9b00d93b46fa7a30e2cb7e6b83e5a81310585
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/40460
Commit-Queue: Wyatt Hepler <hepler@google.com>
Reviewed-by: Ewout van Bekkum <ewout@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
diff --git a/pw_persistent_ram/public/pw_persistent_ram/persistent.h b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
index 9d45465..556fd53 100644
--- a/pw_persistent_ram/public/pw_persistent_ram/persistent.h
+++ b/pw_persistent_ram/public/pw_persistent_ram/persistent.h
@@ -24,14 +24,11 @@
 
 namespace pw::persistent_ram {
 
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wuninitialized"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wuninitialized"
-#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
-#endif
+// The Persistent class intentionally uses uninitialized memory, which triggers
+// compiler warnings. Disable those warnings for this file.
+PW_MODIFY_DIAGNOSTICS_PUSH();
+PW_MODIFY_DIAGNOSTIC(ignored, "-Wuninitialized");
+PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
 
 // A simple container for holding a value T with CRC16 integrity checking.
 //
@@ -157,10 +154,6 @@
   };
 };
 
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
+PW_MODIFY_DIAGNOSTICS_POP();
 
 }  // namespace pw::persistent_ram
diff --git a/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h b/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
index 033d244..3f1d4e9 100644
--- a/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
+++ b/pw_persistent_ram/public/pw_persistent_ram/persistent_buffer.h
@@ -21,6 +21,7 @@
 
 #include "pw_bytes/span.h"
 #include "pw_checksum/crc16_ccitt.h"
+#include "pw_preprocessor/compiler.h"
 #include "pw_status/status.h"
 #include "pw_stream/stream.h"
 
@@ -56,14 +57,11 @@
   volatile uint16_t& checksum_;
 };
 
-#if defined(__clang__)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wuninitialized"
-#elif defined(__GNUC__)
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wuninitialized"
-#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
-#endif
+// The PersistentBuffer class intentionally uses uninitialized memory, which
+// triggers compiler warnings. Disable those warnings for this file.
+PW_MODIFY_DIAGNOSTICS_PUSH();
+PW_MODIFY_DIAGNOSTIC(ignored, "-Wuninitialized");
+PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
 
 // When a PersistentBuffer is statically allocated in persistent memory, its
 // state will persist across soft resets in accordance with the expected
@@ -142,9 +140,6 @@
   volatile std::byte buffer_[kMaxSizeBytes];
 };
 
-#if defined(__clang__)
-#pragma clang diagnostic pop
-#elif defined(__GNUC__)
-#pragma GCC diagnostic pop
-#endif
+PW_MODIFY_DIAGNOSTICS_POP();
+
 }  // namespace pw::persistent_ram
diff --git a/pw_preprocessor/BUILD b/pw_preprocessor/BUILD
index d0d14d7..46494d1 100644
--- a/pw_preprocessor/BUILD
+++ b/pw_preprocessor/BUILD
@@ -32,6 +32,7 @@
 TESTS = [
     "arguments_test",
     "boolean_test",
+    "compiler_test",
     "concat_test",
     "util_test",
 ]
diff --git a/pw_preprocessor/BUILD.gn b/pw_preprocessor/BUILD.gn
index 6f9a48f..95459e4 100644
--- a/pw_preprocessor/BUILD.gn
+++ b/pw_preprocessor/BUILD.gn
@@ -43,6 +43,7 @@
   tests = [
     ":arguments_test",
     ":boolean_test",
+    ":compiler_test",
     ":concat_test",
     ":util_test",
   ]
@@ -58,6 +59,11 @@
   sources = [ "boolean_test.cc" ]
 }
 
+pw_test("compiler_test") {
+  deps = [ ":pw_preprocessor" ]
+  sources = [ "compiler_test.cc" ]
+}
+
 pw_test("concat_test") {
   deps = [ ":pw_preprocessor" ]
   sources = [ "concat_test.cc" ]
diff --git a/pw_preprocessor/compiler_test.cc b/pw_preprocessor/compiler_test.cc
new file mode 100644
index 0000000..2fd79cf
--- /dev/null
+++ b/pw_preprocessor/compiler_test.cc
@@ -0,0 +1,50 @@
+// Copyright 2021 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.
+
+#include "pw_preprocessor/compiler.h"
+
+#include <cstdint>
+
+#include "gtest/gtest.h"
+
+namespace pw::preprocessor {
+namespace {
+
+PW_MODIFY_DIAGNOSTICS_PUSH();
+PW_MODIFY_DIAGNOSTIC(ignored, "-Wunused-variable");
+
+int this_variable_is_unused;
+
+PW_MODIFY_DIAGNOSTICS_POP();
+
+class Foo {
+  PW_MODIFY_DIAGNOSTICS_PUSH();
+  PW_MODIFY_DIAGNOSTIC(ignored, "-Wunused");
+
+  int this_field_is_unused;
+
+  PW_MODIFY_DIAGNOSTICS_POP();
+};
+
+TEST(CompilerMacros, ModifyDiagnostics) {
+  PW_MODIFY_DIAGNOSTICS_PUSH();
+  PW_MODIFY_DIAGNOSTIC(ignored, "-Wunused-variable");
+
+  int this_variable_also_is_unused;
+
+  PW_MODIFY_DIAGNOSTICS_POP();
+}
+
+}  // namespace
+}  // namespace pw::preprocessor
diff --git a/pw_preprocessor/docs.rst b/pw_preprocessor/docs.rst
index d4ce4c2..070a2bd 100644
--- a/pw_preprocessor/docs.rst
+++ b/pw_preprocessor/docs.rst
@@ -67,6 +67,48 @@
 --------------------------
 Macros for compiler-specific features, such as attributes or builtins.
 
+Modifying compiler diagnostics
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+``pw_preprocessor/compiler.h`` provides macros for enabling or disabling
+compiler diagnostics (warnings or errors).
+
+.. c:macro:: PW_MODIFY_DIAGNOSTICS_PUSH()
+
+  Starts a new group of :c:macro:`PW_MODIFY_DIAGNOSTIC` statements. A
+  :c:macro:`PW_MODIFY_DIAGNOSTICS_POP` statement must follow.
+
+.. c:macro:: PW_MODIFY_DIAGNOSTICS_POP()
+
+  :c:macro:`PW_MODIFY_DIAGNOSTIC` statements since the most recent
+  :c:macro:`PW_MODIFY_DIAGNOSTICS_PUSH` no longer apply after this statement.
+
+.. c:macro:: PW_MODIFY_DIAGNOSTIC(kind, option)
+
+  Changes how a diagnostic (warning or error) is handled. Most commonly used to
+  disable warnings. ``PW_MODIFY_DIAGNOSTIC`` should be used between
+  :c:macro:`PW_MODIFY_DIAGNOSTICS_PUSH` and :c:macro:`PW_MODIFY_DIAGNOSTICS_POP`
+  statements to avoid applying the modifications too broadly.
+
+  ``kind`` may be ``warning``, ``error``, or ``ignored``.
+
+These macros can be used to disable warnings for precise sections of code, even
+a single line if necessary.
+
+.. code-block:: c
+
+  PW_MODIFY_DIAGNOSTICS_PUSH();
+  PW_MODIFY_DIAGNOSTIC(ignored, "-Wunused-variable");
+
+  static int this_variable_is_never_used;
+
+  PW_MODIFY_DIAGNOSTICS_POP();
+
+.. tip::
+
+  :c:macro:`PW_MODIFY_DIAGNOSTIC` and related macros should rarely be used.
+  Whenever possible, fix the underlying issues about which the compiler is
+  warning, rather than silencing the diagnostics.
+
 pw_preprocessor/concat.h
 ------------------------
 Defines the ``PW_CONCAT(...)`` macro, which expands its arguments if they are
diff --git a/pw_preprocessor/public/pw_preprocessor/compiler.h b/pw_preprocessor/public/pw_preprocessor/compiler.h
index 00d3ccd..91fbc13 100644
--- a/pw_preprocessor/public/pw_preprocessor/compiler.h
+++ b/pw_preprocessor/public/pw_preprocessor/compiler.h
@@ -16,6 +16,8 @@
 // This file is used by both C++ and C code.
 #pragma once
 
+#include <assert.h>
+
 // Marks a struct or class as packed.
 #define PW_PACKED(declaration) declaration __attribute__((packed))
 
@@ -115,3 +117,37 @@
 #else
 #define PW_HAVE_ATTRIBUTE(x) 0
 #endif
+
+#define _PW_REQUIRE_SEMICOLON \
+  static_assert(1, "This macro must be terminated with a semicolon")
+
+// PW_MODIFY_DIAGNOSTICS_PUSH and PW_MODIFY_DIAGNOSTICS_POP are used to turn off
+// or on diagnostics (warnings or errors) for a section of code. Use
+// PW_MODIFY_DIAGNOSTICS_PUSH, use PW_MODIFY_DIAGNOSTIC as many times as needed,
+// then use PW_MODIFY_DIAGNOSTICS_POP to restore the previous settings.
+#define PW_MODIFY_DIAGNOSTICS_PUSH() \
+  _Pragma("GCC diagnostic push") _PW_REQUIRE_SEMICOLON
+#define PW_MODIFY_DIAGNOSTICS_POP() \
+  _Pragma("GCC diagnostic pop") _PW_REQUIRE_SEMICOLON
+
+// Changes how a diagnostic (warning or error) is handled. Most commonly used to
+// disable warnings. PW_MODIFY_DIAGNOSTIC should be used between
+// PW_MODIFY_DIAGNOSTICS_PUSH and PW_MODIFY_DIAGNOSTICS_POP statements to avoid
+// applying the modifications too broadly.
+//
+// 'kind' must be one of warning, error, or ignored.
+#define PW_MODIFY_DIAGNOSTIC(kind, option) \
+  PW_PRAGMA(GCC diagnostic kind option) _PW_REQUIRE_SEMICOLON
+
+// Applies PW_MODIFY_DIAGNOSTIC only for GCC. This is useful for warnings that
+// aren't supported by or don't need to be changed in other compilers.
+#ifdef __clang__
+#define PW_MODIFY_DIAGNOSTIC_GCC(kind, option) _PW_REQUIRE_SEMICOLON
+#else
+#define PW_MODIFY_DIAGNOSTIC_GCC(kind, option) \
+  PW_MODIFY_DIAGNOSTIC(kind, option)
+#endif  // __clang__
+
+// Expands to a _Pragma with the contents as a string. _Pragma must take a
+// single string literal; this can be used to construct a _Pragma argument.
+#define PW_PRAGMA(contents) _Pragma(#contents)