Bug 989460 - Part 1: Add MacroArgs.h for macros related to implementing variadic macros. r=Waldo
authorBirunthan Mohanathas <birunthan@mohanathas.com>
Thu, 24 Apr 2014 10:48:00 +0200
changeset 180476 c7399918527aae630408026e801464308a95419c
parent 180475 e4f8969d11422863be77c4842a64e609a4c8d42d
child 180477 910de4b746382f35a350f534889c3b47743adbcb
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersWaldo
bugs989460
milestone31.0a1
Bug 989460 - Part 1: Add MacroArgs.h for macros related to implementing variadic macros. r=Waldo This also converts MOZ_RELEASE_ASSERT and MOZ_BEGIN_NESTED_ENUM_CLASS to use the new helper macros.
mfbt/Assertions.h
mfbt/MacroArgs.h
mfbt/TypedEnum.h
mfbt/moz.build
mfbt/tests/TestMacroArgs.cpp
mfbt/tests/moz.build
--- a/mfbt/Assertions.h
+++ b/mfbt/Assertions.h
@@ -7,16 +7,17 @@
 /* Implementations of runtime and static assertion macros for C and C++. */
 
 #ifndef mozilla_Assertions_h
 #define mozilla_Assertions_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Compiler.h"
 #include "mozilla/Likely.h"
+#include "mozilla/MacroArgs.h"
 
 #include <stddef.h>
 #include <stdio.h>
 #include <stdlib.h>
 #ifdef WIN32
    /*
     * TerminateProcess and GetCurrentProcess are defined in <winbase.h>, which
     * further depends on <windef.h>.  We hardcode these few definitions manually
@@ -290,40 +291,23 @@ MOZ_ReportCrash(const char* s, const cha
 /* Now the two-argument form. */
 #define MOZ_ASSERT_HELPER2(expr, explain) \
    do { \
      if (MOZ_UNLIKELY(!(expr))) { \
        MOZ_ReportAssertionFailure(#expr " (" explain ")", __FILE__, __LINE__); \
        MOZ_REALLY_CRASH(); \
      } \
    } while (0)
-/* And now, helper macrology up the wazoo. */
-/*
- * Count the number of arguments passed to MOZ_ASSERT, very carefully
- * tiptoeing around an MSVC bug where it improperly expands __VA_ARGS__ as a
- * single token in argument lists.  See these URLs for details:
- *
- *   http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement
- *   http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644
- */
-#define MOZ_COUNT_ASSERT_ARGS_IMPL2(_1, _2, count, ...) \
-   count
-#define MOZ_COUNT_ASSERT_ARGS_IMPL(args) \
-       MOZ_COUNT_ASSERT_ARGS_IMPL2 args
-#define MOZ_COUNT_ASSERT_ARGS(...) \
-   MOZ_COUNT_ASSERT_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))
-/* Pick the right helper macro to invoke. */
-#define MOZ_ASSERT_CHOOSE_HELPER2(count) MOZ_ASSERT_HELPER##count
-#define MOZ_ASSERT_CHOOSE_HELPER1(count) MOZ_ASSERT_CHOOSE_HELPER2(count)
-#define MOZ_ASSERT_CHOOSE_HELPER(count) MOZ_ASSERT_CHOOSE_HELPER1(count)
-/* The actual macros. */
-#define MOZ_ASSERT_GLUE(x, y) x y
+
+#define MOZ_RELEASE_ASSERT_GLUE(a, b) a b
 #define MOZ_RELEASE_ASSERT(...) \
-   MOZ_ASSERT_GLUE(MOZ_ASSERT_CHOOSE_HELPER(MOZ_COUNT_ASSERT_ARGS(__VA_ARGS__)), \
-                   (__VA_ARGS__))
+   MOZ_RELEASE_ASSERT_GLUE( \
+     MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_ASSERT_HELPER, __VA_ARGS__), \
+     (__VA_ARGS__))
+
 #ifdef DEBUG
 #  define MOZ_ASSERT(...) MOZ_RELEASE_ASSERT(__VA_ARGS__)
 #else
 #  define MOZ_ASSERT(...) do { } while(0)
 #endif /* DEBUG */
 
 /*
  * MOZ_ASSERT_IF(cond1, cond2) is equivalent to MOZ_ASSERT(cond2) if cond1 is
new file mode 100644
--- /dev/null
+++ b/mfbt/MacroArgs.h
@@ -0,0 +1,105 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * Implements various macros meant to ease the use of variadic macros.
+ */
+
+#ifndef mozilla_MacroArgs_h
+#define mozilla_MacroArgs_h
+
+/*
+ * MOZ_PASTE_PREFIX_AND_ARG_COUNT(aPrefix, ...) counts the number of variadic
+ * arguments and prefixes it with |aPrefix|. For example:
+ *
+ *   MOZ_PASTE_PREFIX_AND_ARG_COUNT(, foo, 42) expands to 2
+ *   MOZ_PASTE_PREFIX_AND_ARG_COUNT(A, foo, 42, bar) expands to A3
+ *
+ * You must pass in between 1 and 50 (inclusive) variadic arguments, past
+ * |aPrefix|. It is not legal to do
+ *
+ *   MOZ_PASTE_PREFIX_AND_ARG_COUNT(prefix)
+ *
+ * (that is, pass in 0 variadic arguments). To ensure that a compile-time
+ * error occurs when these constraints are violated, use the
+ * MOZ_STATIC_ASSERT_VALID_ARG_COUNT macro with the same variaidc arguments
+ * wherever this macro is used.
+ *
+ * Passing (__VA_ARGS__, <rest of arguments>) rather than simply calling
+ * MOZ_MACROARGS_ARG_COUNT_HELPER2(__VA_ARGS__, <rest of arguments>) very
+ * carefully tiptoes around a MSVC bug where it improperly expands __VA_ARGS__
+ * as a single token in argument lists. For details, see:
+ *
+ *   http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement
+ *   http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644
+ */
+#define MOZ_PASTE_PREFIX_AND_ARG_COUNT(aPrefix, ...) \
+  MOZ_MACROARGS_ARG_COUNT_HELPER((__VA_ARGS__, \
+    aPrefix##50, aPrefix##49, aPrefix##48, aPrefix##47, aPrefix##46, \
+    aPrefix##45, aPrefix##44, aPrefix##43, aPrefix##42, aPrefix##41, \
+    aPrefix##40, aPrefix##39, aPrefix##38, aPrefix##37, aPrefix##36, \
+    aPrefix##35, aPrefix##34, aPrefix##33, aPrefix##32, aPrefix##31, \
+    aPrefix##30, aPrefix##29, aPrefix##28, aPrefix##27, aPrefix##26, \
+    aPrefix##25, aPrefix##24, aPrefix##23, aPrefix##22, aPrefix##21, \
+    aPrefix##20, aPrefix##19, aPrefix##18, aPrefix##17, aPrefix##16, \
+    aPrefix##15, aPrefix##14, aPrefix##13, aPrefix##12, aPrefix##11, \
+    aPrefix##10, aPrefix##9,  aPrefix##8,  aPrefix##7,  aPrefix##6,  \
+    aPrefix##5,  aPrefix##4,  aPrefix##3,  aPrefix##2,  aPrefix##1, aPrefix##0))
+
+#define MOZ_MACROARGS_ARG_COUNT_HELPER(aArgs) \
+  MOZ_MACROARGS_ARG_COUNT_HELPER2 aArgs
+
+#define MOZ_MACROARGS_ARG_COUNT_HELPER2( \
+   a1,  a2,  a3,  a4,  a5,  a6,  a7,  a8,  a9, a10, \
+  a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, \
+  a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, \
+  a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, \
+  a41, a42, a43, a44, a45, a46, a47, a48, a49, a50, \
+  a51, ...) a51
+
+/*
+ * MOZ_STATIC_ASSERT_VALID_ARG_COUNT ensures that a compile-time error occurs
+ * when the argument count constraints of MOZ_PASTE_PREFIX_AND_ARG_COUNT are
+ * violated. Use this macro wherever MOZ_PASTE_PREFIX_AND_ARG_COUNT is used
+ * and pass it the same variadic arguments.
+ *
+ * This macro employs a few dirty tricks to function. To detect the zero
+ * argument case, |(__VA_ARGS__)| is stringified, sizeof-ed, and compared to
+ * what it should be in the absence of arguments.
+ *
+ * Detecting too many arguments is a little trickier. With a valid argument
+ * count and a prefix of 1, MOZ_PASTE_PREFIX_AND_ARG_COUNT expands to e.g. 14.
+ * With a prefix of 0.0, it expands to e.g. 0.04. If there are too many
+ * arguments, it expands to the first argument over the limit. If this
+ * exceeding argument is a number, the assertion will fail as there is no
+ * number than can simultaneously be both > 10 and < 0.1. If the exceeding
+ * argument is not a number, a compile-time error will still occur because the
+ * exceeding argument is compared to an int and a double.
+ */
+#define MOZ_MACROARGS_STRINGIFY_HELPER(x) #x
+#define MOZ_STATIC_ASSERT_VALID_ARG_COUNT(...) \
+  static_assert( \
+    sizeof(MOZ_MACROARGS_STRINGIFY_HELPER((__VA_ARGS__))) != sizeof("()") && \
+      MOZ_PASTE_PREFIX_AND_ARG_COUNT(1, __VA_ARGS__) > 10 && \
+      MOZ_PASTE_PREFIX_AND_ARG_COUNT(0.0, __VA_ARGS__) < 0.1, \
+    "MOZ_STATIC_ASSERT_VALID_ARG_COUNT requires 1 to 50 arguments") /* ; */
+
+/*
+ * MOZ_ARGS_AFTER_N expands to its arguments excluding the first |N|
+ * arguments. For example:
+ *
+ *   MOZ_ARGS_AFTER_2(a, b, c, d) expands to: c, d
+ */
+#define MOZ_ARGS_AFTER_1(a1, ...) __VA_ARGS__
+#define MOZ_ARGS_AFTER_2(a1, a2, ...) __VA_ARGS__
+
+/*
+ * MOZ_ARG_N expands to its |N|th argument.
+ */
+#define MOZ_ARG_1(a1, ...) a1
+#define MOZ_ARG_2(a1, a2, ...) a2
+
+#endif /* mozilla_MacroArgs_h */
--- a/mfbt/TypedEnum.h
+++ b/mfbt/TypedEnum.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Macros to emulate C++11 typed enums and enum classes. */
 
 #ifndef mozilla_TypedEnum_h
 #define mozilla_TypedEnum_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/MacroArgs.h"
 
 #if defined(__cplusplus)
 
 #if defined(__clang__)
    /*
     * Per Clang documentation, "Note that marketing version numbers should not
     * be used to check for language features, as different vendors use different
     * numbering schemes. Instead, use the feature checking macros."
@@ -285,43 +286,22 @@
    * as T depends on template parameters (more specifically here, T _is_ a template
    * parameter) so as MOZ_ENUM_CLASS_ENUM_TYPE(T) expands to T::Enum, we are missing
    * the required "typename" keyword. So here, MOZ_TEMPLATE_ENUM_CLASS_ENUM_TYPE
    * is needed.
    */
 #  define MOZ_TEMPLATE_ENUM_CLASS_ENUM_TYPE(Name) typename Name::Enum
 #endif
 
-   /*
-    * Count the number of arguments passed to MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS,
-    * very carefully tiptoeing around an MSVC bug where it improperly expands
-    * __VA_ARGS__ as a single token in argument lists. See these URLs for
-    * details:
-    *
-    *   http://connect.microsoft.com/VisualStudio/feedback/details/380090/variadic-macro-replacement
-    *   http://cplusplus.co.il/2010/07/17/variadic-macro-to-count-number-of-arguments/#comment-644
-    */
-#  define MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS_IMPL2(_1, _2, count, ...) \
-     count
-#  define MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS_IMPL(args) \
-     MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS_IMPL2 args
-#  define MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS(...) \
-     MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS_IMPL((__VA_ARGS__, 2, 1, 0))
-   /* Pick the right helper macro to invoke. */
-#  define MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER2(count) \
-    MOZ_BEGIN_NESTED_ENUM_CLASS_HELPER##count
-#  define MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER1(count) \
-     MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER2(count)
-#  define MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER(count) \
-     MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER1(count)
-   /* The actual macro. */
-#  define MOZ_BEGIN_NESTED_ENUM_CLASS_GLUE(x, y) x y
+#  define MOZ_BEGIN_NESTED_ENUM_CLASS_GLUE(a, b) a b
 #  define MOZ_BEGIN_NESTED_ENUM_CLASS(...) \
-     MOZ_BEGIN_NESTED_ENUM_CLASS_GLUE(MOZ_BEGIN_NESTED_ENUM_CLASS_CHOOSE_HELPER(MOZ_COUNT_BEGIN_ENUM_CLASS_ARGS(__VA_ARGS__)), \
-                                      (__VA_ARGS__))
+     MOZ_BEGIN_NESTED_ENUM_CLASS_GLUE( \
+       MOZ_PASTE_PREFIX_AND_ARG_COUNT(MOZ_BEGIN_NESTED_ENUM_CLASS_HELPER, \
+                                      __VA_ARGS__), \
+       (__VA_ARGS__))
 
 #  define MOZ_BEGIN_ENUM_CLASS(...) MOZ_BEGIN_NESTED_ENUM_CLASS(__VA_ARGS__)
 #  define MOZ_END_ENUM_CLASS(Name) \
      MOZ_END_NESTED_ENUM_CLASS(Name) \
      MOZ_FINISH_NESTED_ENUM_CLASS(Name)
 
 #endif /* __cplusplus */
 
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -31,16 +31,17 @@ EXPORTS.mozilla = [
     'EnumSet.h',
     'FloatingPoint.h',
     'GuardObjects.h',
     'HashFunctions.h',
     'IntegerPrintfMacros.h',
     'IntegerTypeTraits.h',
     'Likely.h',
     'LinkedList.h',
+    'MacroArgs.h',
     'MathAlgorithms.h',
     'Maybe.h',
     'MemoryChecking.h',
     'MemoryReporting.h',
     'Move.h',
     'MSIntTypes.h',
     'NullPtr.h',
     'NumericLimits.h',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestMacroArgs.cpp
@@ -0,0 +1,26 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/MacroArgs.h"
+
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(100, a, b, c) == 1003, "");
+
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, a, b, c) == 3, "");
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, a) == 1, "");
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, !a) == 1, "");
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, (a, b)) == 1, "");
+
+static_assert(MOZ_PASTE_PREFIX_AND_ARG_COUNT(, MOZ_ARGS_AFTER_1(a, b, c)) == 2,
+              "MOZ_ARGS_AFTER_1(a, b, c) should expand to 'b, c'");
+static_assert(MOZ_ARGS_AFTER_2(a, b, 3) == 3,
+              "MOZ_ARGS_AFTER_2(a, b, 3) should expand to '3'");
+
+static_assert(MOZ_ARG_1(10, 20, 30) == 10, "");
+static_assert(MOZ_ARG_2(10, 20, 30) == 20, "");
+
+int main()
+{
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -12,16 +12,17 @@ CPP_UNIT_TESTS += [
     'TestCeilingFloor.cpp',
     'TestCheckedInt.cpp',
     'TestCountPopulation.cpp',
     'TestCountZeroes.cpp',
     'TestEndian.cpp',
     'TestEnumSet.cpp',
     'TestFloatingPoint.cpp',
     'TestIntegerPrintfMacros.cpp',
+    'TestMacroArgs.cpp',
     'TestRollingMean.cpp',
     'TestSHA1.cpp',
     'TestTypedEnum.cpp',
     'TestTypeTraits.cpp',
     'TestWeakPtr.cpp',
 ]
 
 if not CONFIG['MOZ_ASAN']: