/* ws_diag_control.h
 * Turn compiler diagnostic messages on and off.
 *
 * From FreeRADIUS build.h.
 *
 * @copyright 2013 The FreeRADIUS server project
 *
 * That project is covered by the GPLv2, so:
 *
 * SPDX-License-Identifier: GPL-2.0-or-later
 */

#ifndef __WS_DIAG_CONTROL_H__
#define __WS_DIAG_CONTROL_H__

#include "ws_compiler_tests.h"

#ifdef __cplusplus
extern "C" {
#endif

#define XSTRINGIFY(x) #x

/*
 *	Macros for controlling warnings in various compilers.
 */
#define DIAG_JOINSTR(x,y) XSTRINGIFY(x ## y)

/*
 * XXX - this is only for GCC or GCC-compatible compilers, and we only use
 * it to have a macro that takes a warning as an argument and turns it
 * off in the appropriate fashion for Clang and GCC; it should only be
 * used internally in this header.
 */
#define DIAG_DO_PRAGMA(x) _Pragma (#x)

#if defined(__clang__)
  /*
   * Clang, so we'd use _Pragma("clang diagnostic XXX"), if it's
   * supported.
   */
  #if WS_IS_AT_LEAST_CLANG_VERSION(2,8)
    /*
     * This is Clang 2.8 or later: we can use "clang diagnostic ignored -Wxxx"
     * and "clang diagnostic push/pop".
     */
    #define DIAG_PRAGMA(x) DIAG_DO_PRAGMA(clang diagnostic x)
    #define DIAG_OFF(x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored DIAG_JOINSTR(-W,x))
    #define DIAG_WARN(x) DIAG_PRAGMA(push) DIAG_PRAGMA(warning DIAG_JOINSTR(-W,x))
    #define DIAG_ON(x) DIAG_PRAGMA(pop)
  #endif

  /*
   * Not all versions of Clang understand -Wpedantic.  Clang 4.0 appears
   * to be the first version to do so.
   */
  #if WS_IS_AT_LEAST_CLANG_VERSION(4,0)
    #define DIAG_OFF_PEDANTIC DIAG_OFF(pedantic)
    #define DIAG_ON_PEDANTIC DIAG_ON(pedantic)
  #else
    #define DIAG_OFF_PEDANTIC
    #define DIAG_ON_PEDANTIC
  #endif

  /*
   * Do any pre-5.0 versions of Clang understand -Winitializer-overrides?
   */
  #if WS_IS_AT_LEAST_CLANG_VERSION(4,0)
    #define DIAG_OFF_INIT_TWICE DIAG_OFF(initializer-overrides)
    #define DIAG_ON_INIT_TWICE DIAG_ON(initializer-overrides)
  #else
    #define DIAG_OFF_INIT_TWICE
    #define DIAG_ON_INIT_TWICE
  #endif
#elif defined(__GNUC__)
  /*
   * GCC, or a compiler (other than Clang) that claims to be GCC.
   * We assume that the compiler accepts _Pragma("GCC diagnostic xxx")
   * even if it's only claiming to be GCC.
   */
  #if WS_IS_AT_LEAST_GNUC_VERSION(4,8)
    /*
     * This is GCC 4.8 or later, or a compiler claiming to be that.
     * We can use "GCC diagnostic ignored -Wxxx" (introduced in 4.2)
     * and "GCC diagnostic push/pop" (introduced in 4.6), *and* gcc
     * supports "-Wpedantic" (introduced in 4.8), allowing us to
     * turn off pedantic warnings with DIAG_OFF().
     */
    #define DIAG_PRAGMA(x) DIAG_DO_PRAGMA(GCC diagnostic x)
    #define DIAG_OFF(x) DIAG_PRAGMA(push) DIAG_PRAGMA(ignored DIAG_JOINSTR(-W,x))
    #define DIAG_WARN(x) DIAG_PRAGMA(push) DIAG_PRAGMA(warning DIAG_JOINSTR(-W,x))
    #define DIAG_ON(x) DIAG_PRAGMA(pop)

    /*
     * We assume GCC 4.8 and later understand -Wpedantic.
     */
    #define DIAG_OFF_PEDANTIC DIAG_OFF(pedantic)
    #define DIAG_ON_PEDANTIC DIAG_ON(pedantic)

    /*
     * GCC 4.2 and later understand -Woverride-init.
     */
    #define DIAG_OFF_INIT_TWICE DIAG_OFF(override-init)
    #define DIAG_ON_INIT_TWICE DIAG_ON(override-init)
  #else
    #define DIAG_OFF_PEDANTIC
    #define DIAG_ON_PEDANTIC

    #define DIAG_OFF_INIT_TWICE
    #define DIAG_ON_INIT_TWICE
  #endif
#endif

/* This applies to both Clang and GCC, and any other compiler that claims
 * GNU C support. The version is GCC specific, we just assume Clang understands
 * this warning. */
#if defined(__clang__) || WS_IS_AT_LEAST_GNUC_VERSION(14,1)
  #define DIAG_OFF_TYPE_LIMITS() DIAG_OFF(type-limits)
  #define DIAG_ON_TYPE_LIMITS() DIAG_ON(type-limits)
#else
  #define DIAG_OFF_TYPE_LIMITS()
  #define DIAG_ON_TYPE_LIMITS()
#endif

#ifndef DIAG_OFF
  /*
   * This is none of the above; we don't have any way to turn diagnostics
   * on or off.
   *
   * XXX - you can do that in MSVC, but it's done differently; we'd
   * have to have macros for *particular* diagnostics, using the
   * warning flag for GCC and Clang and the error number for MSVC.
   */
  #define DIAG_OFF(x)
  #define DIAG_WARN(x)
  #define DIAG_ON(x)
  #define DIAG_OFF_PEDANTIC
  #define DIAG_ON_PEDANTIC
  #define DIAG_OFF_INIT_TWICE
  #define DIAG_ON_INIT_TWICE
#endif

/* Use for clang specific pragmas, so we can keep -Wpragmas enabled */
#ifdef __clang__
#  define DIAG_OFF_CLANG(x) DIAG_OFF(x)
#  define DIAG_WARN_CLANG(x) DIAG_WARN(x)
#  define DIAG_ON_CLANG(x)  DIAG_ON(x)
#else
#  define DIAG_OFF_CLANG(x)
#  define DIAG_ON_CLANG(x)
#endif

/*
 * Suppress complaints about narrowing conversations and about signed vs.
 * unsigned comparison.
 *
 * XXX - this is done solely to squelch complaints from code generated
 * by Flex, but newer versions of Flex might fix the code; can we
 * check the version of Flex and suppress only the checks that code
 * generated by that version of Flex triggers?
 */
#if defined(_MSC_VER)
  /*
   * Suppress:
   *
   *   warning C4018: signed/unsigned mismatch
   *   warning C4244: 'initializing' : conversion from '__int64' to 'int', possible loss of data
   *   warning C4267: 'argument' : conversion from 'size_t' to 'int', possible loss of data
   *
   * as well as Visual Studio Code Analyzer warnings:
   *
   *   warning C6011: Dereferencing NULL pointer
   *   warning C6308: 'realloc' might return null pointer
   *   warning C6386: Buffer overrun
   *   warning C6387: 'XXX' could be '0'
   *   warning C28182: Dereferencing NULL pointer
   */
  #define DIAG_OFF_FLEX() \
    __pragma(warning(push)) \
    __pragma(warning(disable:4018)) \
    __pragma(warning(disable:4244)) \
    __pragma(warning(disable:4267)) \
    __pragma(warning(disable:6011)) \
    __pragma(warning(disable:6308)) \
    __pragma(warning(disable:6386)) \
    __pragma(warning(disable:6387)) \
    __pragma(warning(disable:28182))
  #define DIAG_ON_FLEX() \
    __pragma(warning(pop))
#else
  /*
   * Suppress:
   *
   *   -Wsigned-compare warnings
   *   -Wshorten-64-to-32 warnings, if the compiler *has* -Wshorten-64-to-32
   *   -Wunreachable-code warnings
   * The version of Flex in the macOS Intel build bots triggers documentation warnings.
   *   -Wdocumentation
   *
   * We use DIAG_OFF() and DIAG_ON(), so we only use features that the
   * compiler supports.
   *
   * We disable -Wshorten-64-to-32 if we're using Clang, or if __APPLE__
   * is defined; that option was originally added to an Apple version of
   * GCC, and at least some versions of Clang support it - given that
   * the Clang work started at Apple, it may be in all versions of Clang.
   *
   * (Does no version of GCC or Clang support the same generic "you're
   * narrowing a value, and you didn't throw in a cast to assert that
   * you know what you're doing" warning that MSVC does?)
   */
  #if defined(__clang__) || defined(__APPLE__)
    #define DIAG_OFF_FLEX() \
      DIAG_OFF(sign-compare) \
      DIAG_OFF(unused-parameter) \
      DIAG_OFF(shorten-64-to-32) \
      DIAG_OFF(unreachable-code) \
      DIAG_OFF(documentation)
    #define DIAG_ON_FLEX() \
      DIAG_ON(documentation)    \
      DIAG_ON(unreachable-code) \
      DIAG_ON(shorten-64-to-32) \
      DIAG_ON(unused-parameter) \
      DIAG_ON(sign-compare)
  #else
    #define DIAG_OFF_FLEX() \
      DIAG_OFF(sign-compare) \
      DIAG_OFF(unused-parameter)
    #define DIAG_ON_FLEX() \
      DIAG_ON(unused-parameter) \
      DIAG_ON(sign-compare)
  #endif
#endif

/* Disable Lemon warnings. */
#if defined(_MSC_VER)
  /*
   * Suppress:
   *
   *   warning C4100: unreferenced formal parameter
   *
   * Note https://gitlab.kitware.com/cmake/cmake/-/issues/18736
   * makes it better to suppress MSVC warnings here than in CMake.
   */
  #define DIAG_OFF_LEMON() \
    __pragma(warning(push)) \
    __pragma(warning(disable:4100))
  #define DIAG_ON_LEMON() \
    __pragma(warning(pop))
#else
  #define DIAG_OFF_LEMON() \
    DIAG_OFF_CLANG(unreachable-code) \
    DIAG_OFF_TYPE_LIMITS() \
    DIAG_OFF(unused-parameter)
  #define DIAG_ON_LEMON() \
    DIAG_ON_CLANG(unreachable-code) \
    DIAG_ON_TYPE_LIMITS() \
    DIAG_ON(unused-parameter)
#endif

/*
 * Suppress warnings about casting away constness.
 * Do this only if you know that the pointer is to something that can
 * be written and, in this context, should be writable.
 */
#if defined(__GNUC__) || defined(__clang__)
  /*
   * GCC or a compiler that claims to be GCC-compatible.
   * We throw in Clang just in case clang-cl doesn't define
   * __GNUC__; if it does, __GNUC__ should suffice.
   */
  #define DIAG_OFF_CAST_AWAY_CONST DIAG_OFF(cast-qual)
  #define DIAG_ON_CAST_AWAY_CONST DIAG_ON(cast-qual)
#elif defined(_MSC_VER)
  #define DIAG_OFF_CAST_AWAY_CONST
  #define DIAG_ON_CAST_AWAY_CONST
#else
  #define DIAG_OFF_CAST_AWAY_CONST
  #define DIAG_ON_CAST_AWAY_CONST
#endif

/*
 * This warning is only supported by GCC since version 7.1 (and not
 * Clang or other compilers that claim GNU C support).
 */
#if WS_GCC_VERSION >= 70100
  #define DIAG_OFF_STRINGOP_OVERFLOW() DIAG_OFF(stringop-overflow=)
  #define DIAG_ON_STRINGOP_OVERFLOW() DIAG_ON(stringop-overflow=)
#else
  #define DIAG_OFF_STRINGOP_OVERFLOW()
  #define DIAG_ON_STRINGOP_OVERFLOW()
#endif

/*
 *	For dealing with APIs which are only deprecated in macOS (like the
 *	OpenSSL and MIT/Heimdal Kerberos APIs).
 *
 *	Dear Apple: this is a cross-platform program, and we're not
 *	going to use your Shiny New Frameworks on macOS unless there's
 *	a sufficiently clear benefit to make it worth our while to have
 *	both macOS and non-macOS versions of the code.
 */
#ifdef __APPLE__
#  define USES_APPLE_DEPRECATED_API DIAG_OFF(deprecated-declarations)
#  define USES_APPLE_RST DIAG_ON(deprecated-declarations)
#else
#  define USES_APPLE_DEPRECATED_API
#  define USES_APPLE_RST
#endif

#ifdef __cplusplus
}
#endif
#endif /* __WS_DIAG_CONTROL_H__ */
