root/tests/lib/libc/gen/t_ctype.c
/*      $NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $   */

/*-
 * Copyright (c) 2025 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Tests for the ctype(3) character classification macros.
 *
 * NOTE: These tests intentionally trigger undefined behaviour by
 * passing int values to the ctype(3) functions which are neither EOF
 * nor representable by unsigned char.  The purpose is to verify
 * NetBSD's intentional trapping of this undefined behaviour -- or
 * intentional allowing of this undefined behaviour, when the
 * environment variable LIBC_ALLOWCTYPEABUSE is set.
 */

#include <sys/cdefs.h>
__RCSID("$NetBSD: t_ctype.c,v 1.12 2025/09/15 00:11:55 riastradh Exp $");

#include <sys/wait.h>

#include <atf-c.h>
#include <ctype.h>
#include <locale.h>
#include <limits.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

#include "h_macros.h"

#ifdef __CHAR_UNSIGNED__
enum { CHAR_UNSIGNED = 1 };
#else
enum { CHAR_UNSIGNED = 0 };
#endif

/*
 * libc has a guard page for the LC_CTYPE=C ctype(3) tables only on
 * some platforms.  We skip it if char is unsigned (in which case the
 * common kind of ctype(3) abuse is unlikely).  We also skip it in
 * static builds -- this is determined in the Makefile.
 */
#ifndef _CTYPE_GUARD_PAGE
#  ifdef __CHAR_UNSIGNED__
#    define     _CTYPE_GUARD_PAGE       0
#  else
#    define     _CTYPE_GUARD_PAGE       1
#  endif
#endif

static const char *const locales[] = { "C.UTF-8", "fr_FR.ISO8859-1", "C" };

static int isalpha_wrapper(int ch) { return isalpha(ch); }
static int isupper_wrapper(int ch) { return isupper(ch); }
static int islower_wrapper(int ch) { return islower(ch); }
static int isdigit_wrapper(int ch) { return isdigit(ch); }
static int isxdigit_wrapper(int ch) { return isxdigit(ch); }
static int isalnum_wrapper(int ch) { return isalnum(ch); }
static int isspace_wrapper(int ch) { return isspace(ch); }
static int ispunct_wrapper(int ch) { return ispunct(ch); }
static int isprint_wrapper(int ch) { return isprint(ch); }
static int isgraph_wrapper(int ch) { return isgraph(ch); }
static int iscntrl_wrapper(int ch) { return iscntrl(ch); }
static int isblank_wrapper(int ch) { return isblank(ch); }
static int toupper_wrapper(int ch) { return toupper(ch); }
static int tolower_wrapper(int ch) { return tolower(ch); }

jmp_buf env;

static void
handle_signal(int signo)
{

        longjmp(env, 1);
}

static void
test_abuse(const char *name, int (*ctypefn)(int))
{
        volatile int ch;        /* for longjmp */

        for (ch = CHAR_MIN; ch < 0; ch++) {
                volatile int result;

                if (ch == EOF)
                        continue;
                ATF_REQUIRE_MSG(ch != (int)(unsigned char)ch, "ch=%d", ch);
                if (setjmp(env) == 0) {
                        REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR);
                        result = (*ctypefn)(ch);
                        REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
                        atf_tc_fail_nonfatal("%s failed to detect invalid %d,"
                            " returned %d",
                            name, ch, result);
                } else {
                        REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
                }
        }

        for (; ch <= CHAR_MAX; ch++)
                ATF_REQUIRE_MSG(ch == (int)(unsigned char)ch, "ch=%d", ch);
}

static void
test_abuse_in_locales(const char *name, int (*ctypefn)(int), bool macro)
{
        size_t i;

        for (i = 0; i < __arraycount(locales); i++) {
                char buf[128];

                if (!_CTYPE_GUARD_PAGE && macro &&
                    strcmp(locales[i], "C") == 0) {
                        fprintf(stderr, "skip LC_CTYPE=C ctype(3) abuse --"
                            " no libc guard page on this platform\n");
                }

                ATF_REQUIRE_MSG(setlocale(LC_CTYPE, locales[i]) != NULL,
                    "locales[i]=%s", locales[i]);
                snprintf(buf, sizeof(buf), "[%s]%s", locales[i], name);
                test_abuse(buf, ctypefn);
        }
}

static void
test_use(const char *name, int (*ctypefn)(int))
{
        volatile int ch;        /* for longjmp */

        for (ch = EOF; ch <= CHAR_MAX; ch = (ch == EOF ? 0 : ch + 1)) {
                volatile int result;

                if (setjmp(env) == 0) {
                        REQUIRE_LIBC(signal(SIGABRT, &handle_signal), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, &handle_signal), SIG_ERR);
                        result = (*ctypefn)(ch);
                        REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
                        (void)result;
                } else {
                        REQUIRE_LIBC(signal(SIGABRT, SIG_DFL), SIG_ERR);
                        REQUIRE_LIBC(signal(SIGSEGV, SIG_DFL), SIG_ERR);
                        atf_tc_fail_nonfatal("%s(%d) raised SIGSEGV",
                            name, ch);
                }
        }
}

static void
test_isalpha_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isalpha", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isupper_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isupper", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_islower_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("islower", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isdigit_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isdigit", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isxdigit_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isxdigit", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isalnum_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isalnum", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isspace_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isspace", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_ispunct_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("ispunct", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isprint_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isprint", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isgraph_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isgraph", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_iscntrl_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("iscntrl", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_isblank_locale(const char *L, int (*ctypefn)(int))
{

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("isblank", ctypefn);
        ATF_CHECK(!(*ctypefn)(EOF));
}

static void
test_toupper_locale(const char *L, int (*ctypefn)(int))
{
        int result;

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("toupper", ctypefn);
        ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
            "result=%d, expected EOF=%d", result, EOF);
}

static void
test_tolower_locale(const char *L, int (*ctypefn)(int))
{
        int result;

        ATF_REQUIRE_MSG(setlocale(LC_CTYPE, L) != NULL, "L=%s", L);
        test_use("tolower", ctypefn);
        ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
            "result=%d, expected EOF=%d", result, EOF);
}

static void
test_isalpha_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'a': case 'A':
                case 'b': case 'B':
                case 'c': case 'C':
                case 'd': case 'D':
                case 'e': case 'E':
                case 'f': case 'F':
                case 'g': case 'G':
                case 'h': case 'H':
                case 'i': case 'I':
                case 'j': case 'J':
                case 'k': case 'K':
                case 'l': case 'L':
                case 'm': case 'M':
                case 'n': case 'N':
                case 'o': case 'O':
                case 'p': case 'P':
                case 'q': case 'Q':
                case 'r': case 'R':
                case 's': case 'S':
                case 't': case 'T':
                case 'u': case 'U':
                case 'v': case 'V':
                case 'w': case 'W':
                case 'x': case 'X':
                case 'y': case 'Y':
                case 'z': case 'Z':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isupper_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'A':
                case 'B':
                case 'C':
                case 'D':
                case 'E':
                case 'F':
                case 'G':
                case 'H':
                case 'I':
                case 'J':
                case 'K':
                case 'L':
                case 'M':
                case 'N':
                case 'O':
                case 'P':
                case 'Q':
                case 'R':
                case 'S':
                case 'T':
                case 'U':
                case 'V':
                case 'W':
                case 'X':
                case 'Y':
                case 'Z':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_islower_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'a':
                case 'b':
                case 'c':
                case 'd':
                case 'e':
                case 'f':
                case 'g':
                case 'h':
                case 'i':
                case 'j':
                case 'k':
                case 'l':
                case 'm':
                case 'n':
                case 'o':
                case 'p':
                case 'q':
                case 'r':
                case 's':
                case 't':
                case 'u':
                case 'v':
                case 'w':
                case 'x':
                case 'y':
                case 'z':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isdigit_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isxdigit_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                case 'a': case 'A':
                case 'b': case 'B':
                case 'c': case 'C':
                case 'd': case 'D':
                case 'e': case 'E':
                case 'f': case 'F':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isalnum_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'a': case 'A':
                case 'b': case 'B':
                case 'c': case 'C':
                case 'd': case 'D':
                case 'e': case 'E':
                case 'f': case 'F':
                case 'g': case 'G':
                case 'h': case 'H':
                case 'i': case 'I':
                case 'j': case 'J':
                case 'k': case 'K':
                case 'l': case 'L':
                case 'm': case 'M':
                case 'n': case 'N':
                case 'o': case 'O':
                case 'p': case 'P':
                case 'q': case 'Q':
                case 'r': case 'R':
                case 's': case 'S':
                case 't': case 'T':
                case 'u': case 'U':
                case 'v': case 'V':
                case 'w': case 'W':
                case 'x': case 'X':
                case 'y': case 'Y':
                case 'z': case 'Z':
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isspace_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case ' ':
                case '\f':
                case '\n':
                case '\r':
                case '\t':
                case '\v':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_ispunct_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                default:
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                case 0 ... 0x1f:
                case 0x20:      /* space is printing but not punctuation */
                case 0x7f:
                case 0x80 ... 0xff:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                case 'a': case 'A':
                case 'b': case 'B':
                case 'c': case 'C':
                case 'd': case 'D':
                case 'e': case 'E':
                case 'f': case 'F':
                case 'g': case 'G':
                case 'h': case 'H':
                case 'i': case 'I':
                case 'j': case 'J':
                case 'k': case 'K':
                case 'l': case 'L':
                case 'm': case 'M':
                case 'n': case 'N':
                case 'o': case 'O':
                case 'p': case 'P':
                case 'q': case 'Q':
                case 'r': case 'R':
                case 's': case 'S':
                case 't': case 'T':
                case 'u': case 'U':
                case 'v': case 'V':
                case 'w': case 'W':
                case 'x': case 'X':
                case 'y': case 'Y':
                case 'z': case 'Z':
                case '0':
                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isprint_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 0x20:      /* space is printing but not graphic */
                default:
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                case 0 ... 0x1f:
                case 0x7f:
                case 0x80 ... 0xff:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isgraph_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                default:
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                case 0 ... 0x1f:
                case 0x20:      /* space is printing but not graphic */
                case 0x7f:
                case 0x80 ... 0xff:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_iscntrl_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 0 ... 0x1f:
                case 0x7f:
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_isblank_c(int (*ctypefn)(int))
{
        int ch;

        ATF_CHECK(!(*ctypefn)(EOF));
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case ' ':
                case '\t':
                        ATF_CHECK_MSG((*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                default:
                        ATF_CHECK_MSG(!(*ctypefn)(ch), "ch=0x%x", ch);
                        break;
                }
        }
}

static void
test_toupper_c(int (*ctypefn)(int))
{
        int ch, result, expected;

        ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
            "result=%d, expected EOF=%d", result, EOF);
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'a': case 'A': expected = 'A'; break;
                case 'b': case 'B': expected = 'B'; break;
                case 'c': case 'C': expected = 'C'; break;
                case 'd': case 'D': expected = 'D'; break;
                case 'e': case 'E': expected = 'E'; break;
                case 'f': case 'F': expected = 'F'; break;
                case 'g': case 'G': expected = 'G'; break;
                case 'h': case 'H': expected = 'H'; break;
                case 'i': case 'I': expected = 'I'; break;
                case 'j': case 'J': expected = 'J'; break;
                case 'k': case 'K': expected = 'K'; break;
                case 'l': case 'L': expected = 'L'; break;
                case 'm': case 'M': expected = 'M'; break;
                case 'n': case 'N': expected = 'N'; break;
                case 'o': case 'O': expected = 'O'; break;
                case 'p': case 'P': expected = 'P'; break;
                case 'q': case 'Q': expected = 'Q'; break;
                case 'r': case 'R': expected = 'R'; break;
                case 's': case 'S': expected = 'S'; break;
                case 't': case 'T': expected = 'T'; break;
                case 'u': case 'U': expected = 'U'; break;
                case 'v': case 'V': expected = 'V'; break;
                case 'w': case 'W': expected = 'W'; break;
                case 'x': case 'X': expected = 'X'; break;
                case 'y': case 'Y': expected = 'Y'; break;
                case 'z': case 'Z': expected = 'Z'; break;
                default:
                        expected = ch;
                        break;
                }
                ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected,
                    "result=%d expected=%d", result, expected);
        }
}

static void
test_tolower_c(int (*ctypefn)(int))
{
        int ch, result, expected;

        ATF_CHECK_MSG((result = (*ctypefn)(EOF)) == EOF,
            "result=%d, expected EOF=%d", result, EOF);
        for (ch = 0; ch <= UCHAR_MAX; ch++) {
                switch (ch) {
                case 'a': case 'A': expected = 'a'; break;
                case 'b': case 'B': expected = 'b'; break;
                case 'c': case 'C': expected = 'c'; break;
                case 'd': case 'D': expected = 'd'; break;
                case 'e': case 'E': expected = 'e'; break;
                case 'f': case 'F': expected = 'f'; break;
                case 'g': case 'G': expected = 'g'; break;
                case 'h': case 'H': expected = 'h'; break;
                case 'i': case 'I': expected = 'i'; break;
                case 'j': case 'J': expected = 'j'; break;
                case 'k': case 'K': expected = 'k'; break;
                case 'l': case 'L': expected = 'l'; break;
                case 'm': case 'M': expected = 'm'; break;
                case 'n': case 'N': expected = 'n'; break;
                case 'o': case 'O': expected = 'o'; break;
                case 'p': case 'P': expected = 'p'; break;
                case 'q': case 'Q': expected = 'q'; break;
                case 'r': case 'R': expected = 'r'; break;
                case 's': case 'S': expected = 's'; break;
                case 't': case 'T': expected = 't'; break;
                case 'u': case 'U': expected = 'u'; break;
                case 'v': case 'V': expected = 'v'; break;
                case 'w': case 'W': expected = 'w'; break;
                case 'x': case 'X': expected = 'x'; break;
                case 'y': case 'Y': expected = 'y'; break;
                case 'z': case 'Z': expected = 'z'; break;
                default:
                        expected = ch;
                        break;
                }
                ATF_CHECK_MSG((result = (*ctypefn)(ch)) == expected,
                    "result=%d expected=%d", result, expected);
        }
}

extern char **environ;

static void
test_abuse_override(const struct atf_tc *tc, const char *fn, const char *mode,
    const char *locale)
{
        char h_ctype_abuse[PATH_MAX];
        pid_t pid;
        int status;

        RL(snprintf(h_ctype_abuse, sizeof(h_ctype_abuse), "%s/h_ctype_abuse",
                atf_tc_get_config_var(tc, "srcdir")));

        RL(setenv("LIBC_ALLOWCTYPEABUSE", "", 1));

        RL(pid = vfork());
        if (pid == 0) {         /* child */
                char *const argv[] = {
                        h_ctype_abuse,
                        __UNCONST(fn),
                        __UNCONST(mode),
                        __UNCONST(locale),
                        NULL,
                };

                if (execve(argv[0], argv, environ) == -1)
                        _exit(1);
                _exit(2);
        }

        RL(waitpid(pid, &status, 0));
        if (WIFSIGNALED(status)) {
                atf_tc_fail_nonfatal("child exited on signal %d (%s)",
                    WTERMSIG(status), strsignal(WTERMSIG(status)));
        } else if (!WIFEXITED(status)) {
                atf_tc_fail_nonfatal("child exited status=0x%x", status);
        } else {
                ATF_CHECK_MSG(WEXITSTATUS(status) == 0,
                    "child exited with code %d",
                    WEXITSTATUS(status));
        }
}

static void
test_abuse_override_in_locales(const struct atf_tc *tc, const char *fn,
    const char *mode)
{
        size_t i;

        for (i = 0; i < __arraycount(locales); i++) {
                fprintf(stderr, "# locale %s\n", locales[i]);
                test_abuse_override(tc, fn, mode, locales[i]);
        }
}

#define ADD_TEST_ABUSE(TP, FN) do                                             \
{                                                                             \
        ATF_TP_ADD_TC(TP, abuse_##FN##_macro_c);                              \
        ATF_TP_ADD_TC(TP, abuse_##FN##_function_c);                           \
        ATF_TP_ADD_TC(TP, abuse_##FN##_macro_locale);                         \
        ATF_TP_ADD_TC(TP, abuse_##FN##_function_locale);                      \
} while (0)

#define DEF_TEST_ABUSE(FN)                                                    \
ATF_TC(abuse_##FN##_macro_c);                                                 \
ATF_TC_HEAD(abuse_##FN##_macro_c, tc)                                         \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test abusing "#FN" macro with default LC_CTYPE=C");              \
}                                                                             \
ATF_TC_BODY(abuse_##FN##_macro_c, tc)                                         \
{                                                                             \
        if (CHAR_UNSIGNED) {                                                  \
                atf_tc_skip("runtime ctype(3) abuse is impossible with"       \
                    " unsigned char");                                        \
        }                                                                     \
        if (!_CTYPE_GUARD_PAGE)                                               \
                atf_tc_skip("no LC_CTYPE=C guard page on this platform");     \
        test_abuse(#FN, &FN##_wrapper);                                       \
}                                                                             \
ATF_TC(abuse_##FN##_function_c);                                              \
ATF_TC_HEAD(abuse_##FN##_function_c, tc)                                      \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test abusing "#FN" function with default LC_CTYPE=C");           \
}                                                                             \
ATF_TC_BODY(abuse_##FN##_function_c, tc)                                      \
{                                                                             \
        if (CHAR_UNSIGNED) {                                                  \
                atf_tc_skip("runtime ctype(3) abuse is impossible with"       \
                    " unsigned char");                                        \
        }                                                                     \
        if (!_CTYPE_GUARD_PAGE)                                               \
                atf_tc_skip("no LC_CTYPE=C guard page on this platform");     \
        test_abuse(#FN, &FN);                                                 \
}                                                                             \
ATF_TC(abuse_##FN##_macro_locale);                                            \
ATF_TC_HEAD(abuse_##FN##_macro_locale, tc)                                    \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test abusing "#FN" macro with locales");                         \
}                                                                             \
ATF_TC_BODY(abuse_##FN##_macro_locale, tc)                                    \
{                                                                             \
        if (CHAR_UNSIGNED) {                                                  \
                atf_tc_skip("runtime ctype(3) abuse is impossible with"       \
                    " unsigned char");                                        \
        }                                                                     \
        test_abuse_in_locales(#FN, &FN##_wrapper, /*macro*/true);             \
}                                                                             \
ATF_TC(abuse_##FN##_function_locale);                                         \
ATF_TC_HEAD(abuse_##FN##_function_locale, tc)                                 \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test abusing "#FN" function with locales");                      \
}                                                                             \
ATF_TC_BODY(abuse_##FN##_function_locale, tc)                                 \
{                                                                             \
        if (CHAR_UNSIGNED) {                                                  \
                atf_tc_skip("runtime ctype(3) abuse is impossible with"       \
                    " unsigned char");                                        \
        }                                                                     \
        test_abuse_in_locales(#FN, &FN, /*macro*/false);                      \
}

#define ADD_TEST_ABUSE_OVERRIDE(TP, FN) do                                    \
{                                                                             \
        ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_c);                     \
        ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_c);                  \
        ATF_TP_ADD_TC(TP, abuse_override_##FN##_macro_locale);                \
        ATF_TP_ADD_TC(TP, abuse_override_##FN##_function_locale);             \
} while (0)

#define DEF_TEST_ABUSE_OVERRIDE(FN)                                           \
ATF_TC(abuse_override_##FN##_macro_c);                                        \
ATF_TC_HEAD(abuse_override_##FN##_macro_c, tc)                                \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test allowing abuse of "#FN" macro with default LC_CTYPE=C");    \
}                                                                             \
ATF_TC_BODY(abuse_override_##FN##_macro_c, tc)                                \
{                                                                             \
        test_abuse_override(tc, #FN, "macro", NULL);                          \
}                                                                             \
ATF_TC(abuse_override_##FN##_function_c);                                     \
ATF_TC_HEAD(abuse_override_##FN##_function_c, tc)                             \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test allowing abuse "#FN" function with default LC_CTYPE=C");    \
}                                                                             \
ATF_TC_BODY(abuse_override_##FN##_function_c, tc)                             \
{                                                                             \
        test_abuse_override(tc, #FN, "function", NULL);                       \
}                                                                             \
ATF_TC(abuse_override_##FN##_macro_locale);                                   \
ATF_TC_HEAD(abuse_override_##FN##_macro_locale, tc)                           \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test allowing abuse of "#FN" macro with locales");               \
}                                                                             \
ATF_TC_BODY(abuse_override_##FN##_macro_locale, tc)                           \
{                                                                             \
        test_abuse_override_in_locales(tc, #FN, "macro");                     \
}                                                                             \
ATF_TC(abuse_override_##FN##_function_locale);                                \
ATF_TC_HEAD(abuse_override_##FN##_function_locale, tc)                        \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test allowing abuse of "#FN" function with locales");            \
}                                                                             \
ATF_TC_BODY(abuse_override_##FN##_function_locale, tc)                        \
{                                                                             \
        test_abuse_override_in_locales(tc, #FN, "function");                  \
}

#define ADD_TEST_USE(TP, FN) do                                               \
{                                                                             \
        ATF_TP_ADD_TC(TP, FN##_macro_c);                                      \
        ATF_TP_ADD_TC(TP, FN##_function_c);                                   \
        ATF_TP_ADD_TC(TP, FN##_macro_locale);                                 \
        ATF_TP_ADD_TC(TP, FN##_function_locale);                              \
} while (0)

#define DEF_TEST_USE(FN)                                                      \
ATF_TC(FN##_macro_c);                                                         \
ATF_TC_HEAD(FN##_macro_c, tc)                                                 \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test "#FN" macro with default LC_CTYPE=C");                      \
}                                                                             \
ATF_TC_BODY(FN##_macro_c, tc)                                                 \
{                                                                             \
        test_##FN##_c(&FN##_wrapper);                                         \
}                                                                             \
ATF_TC(FN##_function_c);                                                      \
ATF_TC_HEAD(FN##_function_c, tc)                                              \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test "#FN" function with default LC_CTYPE=C");                   \
}                                                                             \
ATF_TC_BODY(FN##_function_c, tc)                                              \
{                                                                             \
        test_##FN##_c(&FN);                                                   \
}                                                                             \
ATF_TC(FN##_macro_locale);                                                    \
ATF_TC_HEAD(FN##_macro_locale, tc)                                            \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test "#FN" macro with locales");                                 \
}                                                                             \
ATF_TC_BODY(FN##_macro_locale, tc)                                            \
{                                                                             \
        size_t i;                                                             \
                                                                              \
        for (i = 0; i < __arraycount(locales); i++)                           \
                test_##FN##_locale(locales[i], &FN##_wrapper);                \
}                                                                             \
ATF_TC(FN##_function_locale);                                                 \
ATF_TC_HEAD(FN##_function_locale, tc)                                         \
{                                                                             \
        atf_tc_set_md_var(tc, "descr",                                        \
            "Test "#FN" function with locales");                              \
}                                                                             \
ATF_TC_BODY(FN##_function_locale, tc)                                         \
{                                                                             \
        size_t i;                                                             \
                                                                              \
        for (i = 0; i < __arraycount(locales); i++)                           \
                test_##FN##_locale(locales[i], &FN);                          \
}

DEF_TEST_ABUSE(isalpha)
DEF_TEST_ABUSE(isupper)
DEF_TEST_ABUSE(islower)
DEF_TEST_ABUSE(isdigit)
DEF_TEST_ABUSE(isxdigit)
DEF_TEST_ABUSE(isalnum)
DEF_TEST_ABUSE(isspace)
DEF_TEST_ABUSE(ispunct)
DEF_TEST_ABUSE(isprint)
DEF_TEST_ABUSE(isgraph)
DEF_TEST_ABUSE(iscntrl)
DEF_TEST_ABUSE(isblank)
DEF_TEST_ABUSE(toupper)
DEF_TEST_ABUSE(tolower)

DEF_TEST_ABUSE_OVERRIDE(isalpha)
DEF_TEST_ABUSE_OVERRIDE(isupper)
DEF_TEST_ABUSE_OVERRIDE(islower)
DEF_TEST_ABUSE_OVERRIDE(isdigit)
DEF_TEST_ABUSE_OVERRIDE(isxdigit)
DEF_TEST_ABUSE_OVERRIDE(isalnum)
DEF_TEST_ABUSE_OVERRIDE(isspace)
DEF_TEST_ABUSE_OVERRIDE(ispunct)
DEF_TEST_ABUSE_OVERRIDE(isprint)
DEF_TEST_ABUSE_OVERRIDE(isgraph)
DEF_TEST_ABUSE_OVERRIDE(iscntrl)
DEF_TEST_ABUSE_OVERRIDE(isblank)
DEF_TEST_ABUSE_OVERRIDE(toupper)
DEF_TEST_ABUSE_OVERRIDE(tolower)

DEF_TEST_USE(isalpha)
DEF_TEST_USE(isupper)
DEF_TEST_USE(islower)
DEF_TEST_USE(isdigit)
DEF_TEST_USE(isxdigit)
DEF_TEST_USE(isalnum)
DEF_TEST_USE(isspace)
DEF_TEST_USE(ispunct)
DEF_TEST_USE(isprint)
DEF_TEST_USE(isgraph)
DEF_TEST_USE(iscntrl)
DEF_TEST_USE(isblank)
DEF_TEST_USE(toupper)
DEF_TEST_USE(tolower)

ATF_TC(eof_confusion_iso8859_1);
ATF_TC_HEAD(eof_confusion_iso8859_1, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Test potential confusion with EOF in ISO-8859-1");
}
ATF_TC_BODY(eof_confusion_iso8859_1, tc)
{
        int ydots = 0xff;       /* ÿ, LATIN SMALL LETTER Y WITH DIAERESIS */
        int ch;

        /*
         * The LATIN SMALL LETTER Y WITH DIAERESIS code point 0xff in
         * ISO-8859-1 is curious primarily because its bit pattern
         * coincides with an 8-bit signed -1, which is to say, EOF as
         * an 8-bit quantity; of course, for EOF, all of the is*
         * functions are supposed to return false (as we test above).
         * It also has the curious property that it lacks any
         * corresponding uppercase code point in ISO-8859-1, so we
         * can't distinguish it from EOF by tolower/toupper.
         */
        ATF_REQUIRE(setlocale(LC_CTYPE, "fr_FR.ISO8859-1") != NULL);
        ATF_CHECK(isalpha(ydots));
        ATF_CHECK(!isupper(ydots));
        ATF_CHECK(islower(ydots));
        ATF_CHECK(!isdigit(ydots));
        ATF_CHECK(!isxdigit(ydots));
        ATF_CHECK(isalnum(ydots));
        ATF_CHECK(!isspace(ydots));
        ATF_CHECK(!ispunct(ydots));
        ATF_CHECK(isprint(ydots));
        ATF_CHECK(isgraph(ydots));
        ATF_CHECK(!iscntrl(ydots));
        ATF_CHECK(!isblank(ydots));
        ATF_CHECK_MSG((ch = toupper(ydots)) == ydots, "ch=0x%x", ch);
        ATF_CHECK_MSG((ch = tolower(ydots)) == ydots, "ch=0x%x", ch);
}

ATF_TC(eof_confusion_koi8_u);
ATF_TC_HEAD(eof_confusion_koi8_u, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Test potential confusion with EOF in KOI8-U");
}
ATF_TC_BODY(eof_confusion_koi8_u, tc)
{
        int Hard = 0xff;        /* Ъ, CYRILLIC CAPITAL LETTER HARD SIGN */
        int hard = 0xdf;        /* ъ, CYRILLIC SMALL LETTER HARD SIGN */
        int ch;

        /*
         * The CYRILLIC CAPITAL LETTER HARD SIGN code point 0xff in
         * KOI8-U (and KOI8-R) also coincides with the bit pattern of
         * an 8-bit signed -1.  Unlike LATIN SMALL LETTER Y WITH
         * DIAERESIS, it has a lowercase equivalent in KOI8-U.
         */
        ATF_REQUIRE(setlocale(LC_CTYPE, "uk_UA.KOI8-U") != NULL);
        ATF_CHECK(isalpha(Hard));
        ATF_CHECK(isupper(Hard));
        ATF_CHECK(!islower(Hard));
        ATF_CHECK(!isdigit(Hard));
        ATF_CHECK(!isxdigit(Hard));
        ATF_CHECK(isalnum(Hard));
        ATF_CHECK(!isspace(Hard));
        ATF_CHECK(!ispunct(Hard));
        ATF_CHECK(isprint(Hard));
        ATF_CHECK(isgraph(Hard));
        ATF_CHECK(!iscntrl(Hard));
        ATF_CHECK(!isblank(Hard));
        ATF_CHECK_MSG((ch = toupper(Hard)) == Hard, "ch=0x%x", ch);
        ATF_CHECK_MSG((ch = tolower(Hard)) == hard, "ch=0x%x", ch);
}

ATF_TC(eof_confusion_pt154);
ATF_TC_HEAD(eof_confusion_pt154, tc)
{
        atf_tc_set_md_var(tc, "descr",
            "Test potential confusion with EOF in PT154");
}
ATF_TC_BODY(eof_confusion_pt154, tc)
{
        int ya = 0xff;          /* я, CYRILLIC SMALL LETTER YA */
        int Ya = 0xdf;          /* Я, CYRILLIC CAPITAL LETTER YA */
        int ch;

        /*
         * The CYRILLIC SMALL LETTER YA code point 0xff in PT154 also
         * coincides with the bit pattern of an 8-bit signed -1, and is
         * lowercase with a corresponding uppercase code point in
         * PT154.
         */
        ATF_REQUIRE(setlocale(LC_CTYPE, "kk_KZ.PT154") != NULL);
        ATF_CHECK(isalpha(ya));
        ATF_CHECK(!isupper(ya));
        ATF_CHECK(islower(ya));
        ATF_CHECK(!isdigit(ya));
        ATF_CHECK(!isxdigit(ya));
        ATF_CHECK(isalnum(ya));
        ATF_CHECK(!isspace(ya));
        ATF_CHECK(!ispunct(ya));
        ATF_CHECK(isprint(ya));
        ATF_CHECK(isgraph(ya));
        ATF_CHECK(!iscntrl(ya));
        ATF_CHECK(!isblank(ya));
        ATF_CHECK_MSG((ch = toupper(ya)) == Ya, "ch=0x%x", ch);
        ATF_CHECK_MSG((ch = tolower(ya)) == ya, "ch=0x%x", ch);
}

ATF_TP_ADD_TCS(tp)
{

        ADD_TEST_ABUSE(tp, isalpha);
        ADD_TEST_ABUSE(tp, isupper);
        ADD_TEST_ABUSE(tp, islower);
        ADD_TEST_ABUSE(tp, isdigit);
        ADD_TEST_ABUSE(tp, isxdigit);
        ADD_TEST_ABUSE(tp, isalnum);
        ADD_TEST_ABUSE(tp, isspace);
        ADD_TEST_ABUSE(tp, ispunct);
        ADD_TEST_ABUSE(tp, isprint);
        ADD_TEST_ABUSE(tp, isgraph);
        ADD_TEST_ABUSE(tp, iscntrl);
        ADD_TEST_ABUSE(tp, isblank);
        ADD_TEST_ABUSE(tp, toupper);
        ADD_TEST_ABUSE(tp, tolower);

        ADD_TEST_ABUSE_OVERRIDE(tp, isalpha);
        ADD_TEST_ABUSE_OVERRIDE(tp, isupper);
        ADD_TEST_ABUSE_OVERRIDE(tp, islower);
        ADD_TEST_ABUSE_OVERRIDE(tp, isdigit);
        ADD_TEST_ABUSE_OVERRIDE(tp, isxdigit);
        ADD_TEST_ABUSE_OVERRIDE(tp, isalnum);
        ADD_TEST_ABUSE_OVERRIDE(tp, isspace);
        ADD_TEST_ABUSE_OVERRIDE(tp, ispunct);
        ADD_TEST_ABUSE_OVERRIDE(tp, isprint);
        ADD_TEST_ABUSE_OVERRIDE(tp, isgraph);
        ADD_TEST_ABUSE_OVERRIDE(tp, iscntrl);
        ADD_TEST_ABUSE_OVERRIDE(tp, isblank);
        ADD_TEST_ABUSE_OVERRIDE(tp, toupper);
        ADD_TEST_ABUSE_OVERRIDE(tp, tolower);

        ADD_TEST_USE(tp, isalpha);
        ADD_TEST_USE(tp, isupper);
        ADD_TEST_USE(tp, islower);
        ADD_TEST_USE(tp, isdigit);
        ADD_TEST_USE(tp, isxdigit);
        ADD_TEST_USE(tp, isalnum);
        ADD_TEST_USE(tp, isspace);
        ADD_TEST_USE(tp, ispunct);
        ADD_TEST_USE(tp, isprint);
        ADD_TEST_USE(tp, isgraph);
        ADD_TEST_USE(tp, iscntrl);
        ADD_TEST_USE(tp, isblank);
        ADD_TEST_USE(tp, toupper);
        ADD_TEST_USE(tp, tolower);

        ATF_TP_ADD_TC(tp, eof_confusion_iso8859_1);
        ATF_TP_ADD_TC(tp, eof_confusion_koi8_u);
        ATF_TP_ADD_TC(tp, eof_confusion_pt154);

        return atf_no_error();
}