From 1087274e7dc3ea6864af39a46091160b98e809f1 Mon Sep 17 00:00:00 2001
From: Protobuf Team Bot <protobuf-github-bot@google.com>
Date: Fri, 22 Mar 2024 09:38:04 -0700
Subject: [PATCH] Add an RAII ifndef helper for C++ codegen.

This can be used in headers to automatically generate ifdef guards.

PiperOrigin-RevId: 618204633
---
 pkg/BUILD.bazel                               |  1 +
 src/google/protobuf/io/cpp_utils/BUILD.bazel  | 58 ++++++++++++
 .../protobuf/io/cpp_utils/ifndef_guard.cc     | 71 ++++++++++++++
 .../protobuf/io/cpp_utils/ifndef_guard.h      | 61 ++++++++++++
 .../io/cpp_utils/ifndef_guard_unittest.cc     | 92 +++++++++++++++++++
 5 files changed, 283 insertions(+)
 create mode 100644 src/google/protobuf/io/cpp_utils/BUILD.bazel
 create mode 100644 src/google/protobuf/io/cpp_utils/ifndef_guard.cc
 create mode 100644 src/google/protobuf/io/cpp_utils/ifndef_guard.h
 create mode 100644 src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc

diff --git a/pkg/BUILD.bazel b/pkg/BUILD.bazel
index b87b4e9745..e95d974e15 100644
--- a/pkg/BUILD.bazel
+++ b/pkg/BUILD.bazel
@@ -174,6 +174,7 @@ cc_dist_library(
         "//src/google/protobuf:arena_align",
         "//src/google/protobuf:cmake_wkt_cc_proto",
         "//src/google/protobuf/compiler:importer",
+        "//src/google/protobuf/io/cpp_utils:ifndef_guard",
         "//src/google/protobuf/json",
         "//src/google/protobuf/util:delimited_message_util",
         "//src/google/protobuf/util:differencer",
diff --git a/src/google/protobuf/io/cpp_utils/BUILD.bazel b/src/google/protobuf/io/cpp_utils/BUILD.bazel
new file mode 100644
index 0000000000..87f603dec9
--- /dev/null
+++ b/src/google/protobuf/io/cpp_utils/BUILD.bazel
@@ -0,0 +1,58 @@
+# Utilities for generating C++ code
+
+load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")
+load("@rules_pkg//pkg:mappings.bzl", "pkg_files", "strip_prefix")
+load("//build_defs:cpp_opts.bzl", "COPTS")
+
+package(
+    default_visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "ifndef_guard",
+    srcs = ["ifndef_guard.cc"],
+    hdrs = ["ifndef_guard.h"],
+    copts = COPTS,
+    strip_include_prefix = "/src",
+    deps = [
+        "//src/google/protobuf/io:printer",
+        "@com_google_absl//absl/functional:any_invocable",
+        "@com_google_absl//absl/log:die_if_null",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:string_view",
+    ],
+)
+
+cc_test(
+    name = "ifndef_guard_unittest",
+    srcs = ["ifndef_guard_unittest.cc"],
+    deps = [
+        ":ifndef_guard",
+        "//src/google/protobuf/io",
+        "//src/google/protobuf/io:printer",
+        "@com_google_absl//absl/log:absl_check",
+        "@com_google_absl//absl/strings:string_view",
+        "@com_google_absl//absl/types:optional",
+        "@com_google_googletest//:gtest",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+################################################################################
+# Distribution packaging
+################################################################################
+
+pkg_files(
+    name = "dist_files",
+    srcs = glob(["**/*"]),
+    strip_prefix = strip_prefix.from_root(""),
+    visibility = ["//src:__pkg__"],
+)
+
+filegroup(
+    name = "test_srcs",
+    srcs = glob([
+        "*unittest.cc",
+    ]),
+    visibility = ["//pkg:__pkg__"],
+)
diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard.cc b/src/google/protobuf/io/cpp_utils/ifndef_guard.cc
new file mode 100644
index 0000000000..2960689e48
--- /dev/null
+++ b/src/google/protobuf/io/cpp_utils/ifndef_guard.cc
@@ -0,0 +1,71 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2024 Google LLC.  All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+#include "google/protobuf/io/cpp_utils/ifndef_guard.h"
+
+#include <string>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/log/die_if_null.h"
+#include "absl/strings/ascii.h"
+#include "absl/strings/str_cat.h"
+#include "absl/strings/str_replace.h"
+#include "absl/strings/string_view.h"
+#include "absl/strings/substitute.h"
+#include "google/protobuf/io/printer.h"
+
+namespace google {
+namespace protobuf {
+namespace io {
+namespace cpp {
+
+namespace {
+
+std::string MakeIfdefGuardIdentifier(const absl::string_view header_path) {
+  return absl::StrCat(absl::AsciiStrToUpper(absl::StrReplaceAll(header_path,
+                                                                {
+                                                                    {"/", "_"},
+                                                                    {".", "_"},
+                                                                })),
+                      "_");
+}
+
+}  // namespace
+
+IfdefGuardPrinter::IfdefGuardPrinter(google::protobuf::io::Printer* const p,
+                                     const absl::string_view filename)
+    : IfdefGuardPrinter(p, filename, MakeIfdefGuardIdentifier) {}
+
+IfdefGuardPrinter::IfdefGuardPrinter(
+    google::protobuf::io::Printer* const p, const absl::string_view filename,
+    absl::AnyInvocable<std::string(absl::string_view)> make_ifdef_identifier)
+    : p_(ABSL_DIE_IF_NULL(p)),
+      ifdef_identifier_(make_ifdef_identifier(filename)) {
+  // We can't use variable substitution, because we don't know what delimiter
+  // to use.
+  p->Print(absl::Substitute(
+      R"(#ifndef $0
+#define $0
+
+)",
+      ifdef_identifier_));
+}
+
+IfdefGuardPrinter::~IfdefGuardPrinter() {
+  // We can't use variable substitution, because we don't know what delimiter
+  // to use.
+  p_->Print(absl::Substitute(
+      R"(
+#endif  // $0
+)",
+      ifdef_identifier_));
+}
+
+}  // namespace cpp
+}  // namespace io
+}  // namespace protobuf
+}  // namespace google
diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard.h b/src/google/protobuf/io/cpp_utils/ifndef_guard.h
new file mode 100644
index 0000000000..e82548ee7c
--- /dev/null
+++ b/src/google/protobuf/io/cpp_utils/ifndef_guard.h
@@ -0,0 +1,61 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2024 Google Inc.  All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file or at
+// https://developers.google.com/open-source/licenses/bsd
+
+// An RAII type for printing an ifdef guard.
+//
+// This can be used to ensure that appropriate ifdef guards are applied in
+// a generated header file.
+//
+// Example:
+// {
+//   Printer printer(output_stream.get(), '$');
+//   const IfdefGuardPrinter ifdef_guard(&printer, output_path);
+//   // #ifdef guard will be emitted here
+//   ...
+//   // #endif will be emitted here
+// }
+//
+// By default, the filename will be converted to a macro by substituting '/' and
+// '.' characters with '_'.  If a different transformation is required, an
+// optional transformation function can be provided.
+
+#ifndef GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__
+#define GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__
+
+#include <string>
+
+#include "absl/functional/any_invocable.h"
+#include "absl/strings/string_view.h"
+#include "google/protobuf/io/printer.h"
+
+namespace google {
+namespace protobuf {
+namespace io {
+namespace cpp {
+
+class IfdefGuardPrinter final {
+ public:
+  explicit IfdefGuardPrinter(google::protobuf::io::Printer* p,
+                             absl::string_view filename);
+
+  explicit IfdefGuardPrinter(
+      google::protobuf::io::Printer* p, absl::string_view filename,
+      absl::AnyInvocable<std::string(absl::string_view)> make_ifdef_identifier);
+
+  ~IfdefGuardPrinter();
+
+ private:
+  google::protobuf::io::Printer* const p_;
+  const std::string ifdef_identifier_;
+};
+
+}  // namespace cpp
+}  // namespace io
+}  // namespace protobuf
+}  // namespace google
+
+#endif  // GOOGLE_PROTOBUF_IO_CPP_UTILS_IFNDEF_GUARD_H__
diff --git a/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc b/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc
new file mode 100644
index 0000000000..49c73bb317
--- /dev/null
+++ b/src/google/protobuf/io/cpp_utils/ifndef_guard_unittest.cc
@@ -0,0 +1,92 @@
+#include "google/protobuf/io/cpp_utils/ifndef_guard.h"
+
+#include <string>
+
+#include <gtest/gtest.h>
+#include "absl/log/absl_check.h"
+#include "absl/strings/string_view.h"
+#include "absl/types/optional.h"
+#include "google/protobuf/io/printer.h"
+#include "google/protobuf/io/zero_copy_stream.h"
+#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
+
+namespace google {
+namespace protobuf {
+namespace io {
+namespace cpp {
+
+namespace {
+
+class IfnDefGuardTest : public testing::Test {
+ protected:
+  ZeroCopyOutputStream* output() {
+    ABSL_CHECK(stream_.has_value());
+    return &*stream_;
+  }
+  absl::string_view written() {
+    stream_.reset();
+    return out_;
+  }
+
+  std::string out_;
+  absl::optional<StringOutputStream> stream_{&out_};
+};
+
+TEST_F(IfnDefGuardTest, Basic) {
+  {
+    Printer printer(output(), '$');
+
+    const IfdefGuardPrinter ifdef_guard(&printer, "A/B/E/alpha");
+
+    EXPECT_FALSE(printer.failed());
+  }
+
+  EXPECT_EQ(written(),
+            "#ifndef A_B_E_ALPHA_\n"
+            "#define A_B_E_ALPHA_\n"
+            "\n"
+            "\n"
+            "#endif  // A_B_E_ALPHA_\n");
+}
+
+TEST_F(IfnDefGuardTest, DifferentDelim) {
+  {
+    Printer printer(output(), '\0');
+
+    const IfdefGuardPrinter ifdef_guard(&printer, "A/B/E/alpha");
+
+    EXPECT_FALSE(printer.failed());
+  }
+
+  EXPECT_EQ(written(),
+            "#ifndef A_B_E_ALPHA_\n"
+            "#define A_B_E_ALPHA_\n"
+            "\n"
+            "\n"
+            "#endif  // A_B_E_ALPHA_\n");
+}
+
+TEST_F(IfnDefGuardTest, DifferentSubstitutionFunction) {
+  {
+    Printer printer(output(), '$');
+
+    const IfdefGuardPrinter ifdef_guard(
+        &printer, "A/B/E/alpha", [](absl::string_view) { return "FOO_BAR_"; });
+
+    EXPECT_FALSE(printer.failed());
+  }
+
+  EXPECT_EQ(written(),
+            "#ifndef FOO_BAR_\n"
+            "#define FOO_BAR_\n"
+            "\n"
+            "\n"
+            "#endif  // FOO_BAR_\n");
+}
+
+}  // namespace
+}  // namespace cpp
+}  // namespace io
+
+}  // namespace protobuf
+}  // namespace google