Skip to content

[BUG] bionic <uchar.h> fails to parse as C++98/C++03 when included directly (char16_t/char32_t undeclared) #2250

Description

@zbdd6fb8

Description

Summary

The bionic C header <uchar.h> cannot be parsed as C++98/C++03: it suppresses
its char16_t/char32_t typedefs in all C++ modes (assuming they are built-in
language types), but they only became built-in types in C++11. In pre-C++11
the type names used by c16rtomb, c32rtomb, mbrtoc16, and mbrtoc32
resolve to nothing, causing four parse errors.

This is normally masked because the NDK's default C++ include path resolves
<uchar.h> to the libc++ wrapper (c++/v1/uchar.h), which is intentionally a
no-op before C++11 and never reaches the bionic header. The bionic header is
reached — and fails — in any configuration without the libc++ include path,
including the supported ndk-build configuration APP_STL := none, which adds
-nostdinc++ itself (build/core/stl.mk:22-23).

Environment

  • NDK: r30 (30.0.16248370)
  • Host: Linux x86_64
  • Compiler: the NDK toolchain's clang++
  • Target: any (tested aarch64)

Reproducer

ndk-build (APP_STL := none)

// jni/test.cpp
#include <uchar.h>

int foo() {
    return 0;
}
# jni/Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := test
LOCAL_SRC_FILES := test.cpp
include $(BUILD_SHARED_LIBRARY)
# jni/Application.mk
APP_ABI := arm64-v8a
APP_STL := none
APP_CPPFLAGS := -std=c++98
$ ndk-build
[arm64-v8a] Compile++      : test <= test.cpp
.../sysroot/usr/include/uchar.h:65:40: error: unknown type name 'char16_t'
.../sysroot/usr/include/uchar.h:74:40: error: unknown type name 'char32_t'
.../sysroot/usr/include/uchar.h:80:17: error: unknown type name 'char16_t'
.../sysroot/usr/include/uchar.h:86:17: error: unknown type name 'char32_t'
4 errors generated.

Command line (equivalent)

$ aarch64-linux-android29-clang++ -std=c++98 -nostdinc++ -c test.cpp

For comparison, the same project builds fine with the default APP_STL
(c++_static) and with APP_STL := system, because in both cases
<uchar.h> resolves to the libc++ wrapper. (Note that APP_STL := system
currently also uses the libc++ headers per the long-standing TODO in
build/core/stl.mk.)

Actual result (r30)

.../sysroot/usr/include/uchar.h:65:40: error: unknown type name 'char16_t'
size_t c16rtomb(char* __buf, char16_t __ch16, mbstate_t* __ps) ...
                                  ^
.../sysroot/usr/include/uchar.h:74:40: error: unknown type name 'char32_t'
.../sysroot/usr/include/uchar.h:80:17: error: unknown type name 'char16_t'
.../sysroot/usr/include/uchar.h:86:17: error: unknown type name 'char32_t'
4 errors generated.

Same failure with -std=c++03. C11 and C++11 and later compile fine.

Expected result

The bionic header should either compile in C++98/C++03, or hide its
declarations in pre-C++11 modes so that it at least remains includable in any
configuration.

Root cause

sysroot/usr/include/uchar.h (from bionic), lines 45-49 in r30:

#if !defined(__cplusplus)
/** The UTF-16 character type. */
typedef __CHAR16_TYPE__ char16_t;
/** The UTF-32 character type. */
typedef __CHAR32_TYPE__ char32_t;
#endif

The typedefs are suppressed in all C++ modes because char16_t/char32_t
are keywords in C++11 and later. But in C++98/C++03 they are not keywords,
and the header no longer typedefs them either, so the type names in the
function declarations below fail to resolve.

By contrast, the libc++ wrapper (c++/v1/uchar.h) treats <uchar.h> as a
C++11-only header: for pre-C++11 it includes nothing (or the frozen C++03
header when _LIBCPP_USE_FROZEN_CXX03_HEADERS is set). That is why default
builds never see the problem — and why the bionic header should not be
reached in that mode, which a __cplusplus guard would enforce.

Suggested fix

Hide the declarations in pre-C++11 modes, consistent with how libc++ treats
the header:

#if !defined(__cplusplus) || __cplusplus >= 201103L
size_t c16rtomb(char* __buf, char16_t __ch16, mbstate_t* __ps) ...;
size_t c32rtomb(char* __buf, char32_t __ch32, mbstate_t* __ps) ...;
size_t mbrtoc16(char16_t* __ch16, const char* __s, size_t __n, mbstate_t* __ps) ...;
size_t mbrtoc32(char32_t* __ch32, const char* __s, size_t __n, mbstate_t* __ps) ...;
#endif

(Alternatively, provide the typedefs for __cplusplus < 201103L so the
declarations parse and the C11 functions remain usable from C++98 — keeping
the keywords untouched for C++11+.)

Notes

The header originates from platform/bionic, so the fix likely needs to land
upstream there and then roll into the NDK sysroot.

I am using a supported NDK

  • I have checked and the NDK I'm using is currently supported

Affected versions

r30

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

Projects

  • Status
    Triaged

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions