Blame SOURCES/gcc8-Wbidi-chars.patch

2cca73
commit 51c500269bf53749b107807d84271385fad35628
2cca73
Author: Marek Polacek <polacek@redhat.com>
2cca73
Date:   Wed Oct 6 14:33:59 2021 -0400
2cca73
2cca73
    libcpp: Implement -Wbidi-chars for CVE-2021-42574 [PR103026]
2cca73
    
2cca73
    From a link below:
2cca73
    "An issue was discovered in the Bidirectional Algorithm in the Unicode
2cca73
    Specification through 14.0. It permits the visual reordering of
2cca73
    characters via control sequences, which can be used to craft source code
2cca73
    that renders different logic than the logical ordering of tokens
2cca73
    ingested by compilers and interpreters. Adversaries can leverage this to
2cca73
    encode source code for compilers accepting Unicode such that targeted
2cca73
    vulnerabilities are introduced invisibly to human reviewers."
2cca73
    
2cca73
    More info:
2cca73
    https://nvd.nist.gov/vuln/detail/CVE-2021-42574
2cca73
    https://trojansource.codes/
2cca73
    
2cca73
    This is not a compiler bug.  However, to mitigate the problem, this patch
2cca73
    implements -Wbidi-chars=[none|unpaired|any] to warn about possibly
2cca73
    misleading Unicode bidirectional control characters the preprocessor may
2cca73
    encounter.
2cca73
    
2cca73
    The default is =unpaired, which warns about improperly terminated
2cca73
    bidirectional control characters; e.g. a LRE without its corresponding PDF.
2cca73
    The level =any warns about any use of bidirectional control characters.
2cca73
    
2cca73
    This patch handles both UCNs and UTF-8 characters.  UCNs designating
2cca73
    bidi characters in identifiers are accepted since r204886.  Then r217144
2cca73
    enabled -fextended-identifiers by default.  Extended characters in C/C++
2cca73
    identifiers have been accepted since r275979.  However, this patch still
2cca73
    warns about mixing UTF-8 and UCN bidi characters; there seems to be no
2cca73
    good reason to allow mixing them.
2cca73
    
2cca73
    We warn in different contexts: comments (both C and C++-style), string
2cca73
    literals, character constants, and identifiers.  Expectedly, UCNs are ignored
2cca73
    in comments and raw string literals.  The bidirectional control characters
2cca73
    can nest so this patch handles that as well.
2cca73
    
2cca73
    I have not included nor tested this at all with Fortran (which also has
2cca73
    string literals and line comments).
2cca73
    
2cca73
    Dave M. posted patches improving diagnostic involving Unicode characters.
2cca73
    This patch does not make use of this new infrastructure yet.
2cca73
    
2cca73
            PR preprocessor/103026
2cca73
    
2cca73
    gcc/c-family/ChangeLog:
2cca73
    
2cca73
            * c.opt (Wbidi-chars, Wbidi-chars=): New option.
2cca73
    
2cca73
    gcc/ChangeLog:
2cca73
    
2cca73
            * doc/invoke.texi: Document -Wbidi-chars.
2cca73
    
2cca73
    libcpp/ChangeLog:
2cca73
    
2cca73
            * include/cpplib.h (enum cpp_bidirectional_level): New.
2cca73
            (struct cpp_options): Add cpp_warn_bidirectional.
2cca73
            (enum cpp_warning_reason): Add CPP_W_BIDIRECTIONAL.
2cca73
            * internal.h (struct cpp_reader): Add warn_bidi_p member
2cca73
            function.
2cca73
            * init.c (cpp_create_reader): Set cpp_warn_bidirectional.
2cca73
            * lex.c (bidi): New namespace.
2cca73
            (get_bidi_utf8): New function.
2cca73
            (get_bidi_ucn): Likewise.
2cca73
            (maybe_warn_bidi_on_close): Likewise.
2cca73
            (maybe_warn_bidi_on_char): Likewise.
2cca73
            (_cpp_skip_block_comment): Implement warning about bidirectional
2cca73
            control characters.
2cca73
            (skip_line_comment): Likewise.
2cca73
            (forms_identifier_p): Likewise.
2cca73
            (lex_identifier): Likewise.
2cca73
            (lex_string): Likewise.
2cca73
            (lex_raw_string): Likewise.
2cca73
    
2cca73
    gcc/testsuite/ChangeLog:
2cca73
    
2cca73
            * c-c++-common/Wbidi-chars-1.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-2.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-3.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-4.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-5.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-6.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-7.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-8.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-9.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-10.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-11.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-12.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-13.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-14.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-15.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-16.c: New test.
2cca73
            * c-c++-common/Wbidi-chars-17.c: New test.
2cca73
2cca73
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
2cca73
index f591b39be5a..cf922812198 100644
2cca73
--- a/gcc/c-family/c.opt
2cca73
+++ b/gcc/c-family/c.opt
2cca73
@@ -334,6 +334,30 @@ Wbad-function-cast
2cca73
 C ObjC Var(warn_bad_function_cast) Warning
2cca73
 Warn about casting functions to incompatible types.
2cca73
 
2cca73
+Wbidi-chars
2cca73
+C ObjC C++ ObjC++ Warning Alias(Wbidi-chars=,any,none)
2cca73
+;
2cca73
+
2cca73
+Wbidi-chars=
2cca73
+C ObjC C++ ObjC++ RejectNegative Joined Warning CPP(cpp_warn_bidirectional) CppReason(CPP_W_BIDIRECTIONAL) Var(warn_bidirectional) Init(bidirectional_unpaired) Enum(cpp_bidirectional_level)
2cca73
+-Wbidi-chars=[none|unpaired|any] Warn about UTF-8 bidirectional control characters.
2cca73
+
2cca73
+; Required for these enum values.
2cca73
+SourceInclude
2cca73
+cpplib.h
2cca73
+
2cca73
+Enum
2cca73
+Name(cpp_bidirectional_level) Type(int) UnknownError(argument %qs to %<-Wbidi-chars%> not recognized)
2cca73
+
2cca73
+EnumValue
2cca73
+Enum(cpp_bidirectional_level) String(none) Value(bidirectional_none)
2cca73
+
2cca73
+EnumValue
2cca73
+Enum(cpp_bidirectional_level) String(unpaired) Value(bidirectional_unpaired)
2cca73
+
2cca73
+EnumValue
2cca73
+Enum(cpp_bidirectional_level) String(any) Value(bidirectional_any)
2cca73
+
2cca73
 Wbool-compare
2cca73
 C ObjC C++ ObjC++ Var(warn_bool_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
2cca73
 Warn about boolean expression compared with an integer value different from true/false.
2cca73
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
2cca73
index 78ca7738df2..cc85c53aede 100644
2cca73
--- a/gcc/doc/invoke.texi
2cca73
+++ b/gcc/doc/invoke.texi
2cca73
@@ -264,7 +264,8 @@ Objective-C and Objective-C++ Dialects}.
2cca73
 -Walloc-zero  -Walloc-size-larger-than=@var{n}
2cca73
 -Walloca  -Walloca-larger-than=@var{n} @gol
2cca73
 -Wno-aggressive-loop-optimizations  -Warray-bounds  -Warray-bounds=@var{n} @gol
2cca73
--Wno-attributes  -Wbool-compare  -Wbool-operation @gol
2cca73
+-Wno-attributes  -Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{]} @gol 
2cca73
+-Wbool-compare  -Wbool-operation @gol
2cca73
 -Wno-builtin-declaration-mismatch @gol
2cca73
 -Wno-builtin-macro-redefined  -Wc90-c99-compat  -Wc99-c11-compat @gol
2cca73
 -Wc++-compat  -Wc++11-compat  -Wc++14-compat  @gol
2cca73
@@ -5606,6 +5607,23 @@ Warn about declarations using the @code{alias} and similar attributes whose
2cca73
 target is incompatible with the type of the alias.  @xref{Function Attributes,
2cca73
 ,Declaring Attributes of Functions}.
2cca73
 
2cca73
+@item -Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{]}
2cca73
+@opindex Wbidi-chars=
2cca73
+@opindex Wbidi-chars
2cca73
+@opindex Wno-bidi-chars
2cca73
+Warn about possibly misleading UTF-8 bidirectional control characters in
2cca73
+comments, string literals, character constants, and identifiers.  Such
2cca73
+characters can change left-to-right writing direction into right-to-left
2cca73
+(and vice versa), which can cause confusion between the logical order and
2cca73
+visual order.  This may be dangerous; for instance, it may seem that a piece
2cca73
+of code is not commented out, whereas it in fact is.
2cca73
+
2cca73
+There are three levels of warning supported by GCC@.  The default is
2cca73
+@option{-Wbidi-chars=unpaired}, which warns about improperly terminated
2cca73
+bidi contexts.  @option{-Wbidi-chars=none} turns the warning off.
2cca73
+@option{-Wbidi-chars=any} warns about any use of bidirectional control
2cca73
+characters.
2cca73
+
2cca73
 @item -Wbool-compare
2cca73
 @opindex Wno-bool-compare
2cca73
 @opindex Wbool-compare
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-1.c b/gcc/testsuite/c-c++-common/Wbidi-chars-1.c
2cca73
new file mode 100644
2cca73
index 00000000000..34f5ac19271
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-1.c
2cca73
@@ -0,0 +1,12 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+
2cca73
+int main() {
2cca73
+    int isAdmin = 0;
2cca73
+    /*‮ } ⁦if (isAdmin)⁩ ⁦ begin admins only */
2cca73
+/* { dg-warning "bidirectional" "" { target *-*-* } .-1 } */
2cca73
+        __builtin_printf("You are an admin.\n");
2cca73
+    /* end admins only ‮ { ⁦*/
2cca73
+/* { dg-warning "bidirectional" "" { target *-*-* } .-1 } */
2cca73
+    return 0;
2cca73
+}
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-10.c b/gcc/testsuite/c-c++-common/Wbidi-chars-10.c
2cca73
new file mode 100644
2cca73
index 00000000000..3f851b69e65
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-10.c
2cca73
@@ -0,0 +1,27 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* More nesting testing.  */
2cca73
+
2cca73
+/* RLE‫ LRI⁦ PDF‬ PDI⁩*/
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int LRE_\u202a_PDF_\u202c;
2cca73
+int LRE_\u202a_PDF_\u202c_LRE_\u202a_PDF_\u202c;
2cca73
+int LRE_\u202a_LRI_\u2066_PDF_\u202c_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLE_\u202b_RLI_\u2067_PDF_\u202c_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLE_\u202b_RLI_\u2067_PDI_\u2069_PDF_\u202c;
2cca73
+int FSI_\u2068_LRO_\u202d_PDI_\u2069_PDF_\u202c;
2cca73
+int FSI_\u2068;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int FSI_\u2068_PDI_\u2069;
2cca73
+int FSI_\u2068_FSI_\u2068_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069;
2cca73
+int RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDF_\u202c;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLI_\u2067_RLI_\u2067_RLI_\u2067_RLI_\u2067_FSI_\u2068_PDI_\u2069_PDI_\u2069_PDI_\u2069_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-11.c b/gcc/testsuite/c-c++-common/Wbidi-chars-11.c
2cca73
new file mode 100644
2cca73
index 00000000000..44d044d82de
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-11.c
2cca73
@@ -0,0 +1,9 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test that we warn when mixing UCN and UTF-8.  */
2cca73
+
2cca73
+const char *s1 = "LRE_‪_PDF_\u202c";
2cca73
+/* { dg-warning "mismatch" "" { target *-*-* } .-1 } */
2cca73
+const char *s2 = "LRE_\u202a_PDF_‬";
2cca73
+/* { dg-warning "mismatch" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-12.c b/gcc/testsuite/c-c++-common/Wbidi-chars-12.c
2cca73
new file mode 100644
2cca73
index 00000000000..b07eec1da91
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-12.c
2cca73
@@ -0,0 +1,19 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile { target { c || c++11 } } } */
2cca73
+/* { dg-options "-Wbidi-chars=any" } */
2cca73
+/* Test raw strings.  */
2cca73
+
2cca73
+const char *s1 = R"(a b c LRE‪ 1 2 3 PDF‬ x y z)";
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+const char *s2 = R"(a b c RLE‫ 1 2 3 PDF‬ x y z)";
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+const char *s3 = R"(a b c LRO‭ 1 2 3 PDF‬ x y z)";
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+const char *s4 = R"(a b c RLO‮ 1 2 3 PDF‬ x y z)";
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+const char *s7 = R"(a b c FSI⁨ 1 2 3 PDI⁩ x y) z";
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+const char *s8 = R"(a b c PDI⁩ x y )z";
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
+const char *s9 = R"(a b c PDF‬ x y z)";
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-13.c b/gcc/testsuite/c-c++-common/Wbidi-chars-13.c
2cca73
new file mode 100644
2cca73
index 00000000000..b2dd9fde752
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-13.c
2cca73
@@ -0,0 +1,17 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile { target { c || c++11 } } } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test raw strings.  */
2cca73
+
2cca73
+const char *s1 = R"(a b c LRE‪ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s2 = R"(a b c RLE‫ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s3 = R"(a b c LRO‭ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s4 = R"(a b c FSI⁨ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s5 = R"(a b c LRI⁦ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s6 = R"(a b c RLI⁧ 1 2 3)";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-14.c b/gcc/testsuite/c-c++-common/Wbidi-chars-14.c
2cca73
new file mode 100644
2cca73
index 00000000000..ba5f75d9553
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-14.c
2cca73
@@ -0,0 +1,38 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test PDI handling, which also pops any subsequent LREs, RLEs, LROs,
2cca73
+   or RLOs.  */
2cca73
+
2cca73
+/* LRI_⁦_LRI_⁦_RLE_‫_RLE_‫_RLE_‫_PDI_⁩*/
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// LRI_⁦_RLE_‫_RLE_‫_RLE_‫_PDI_⁩
2cca73
+// LRI_⁦_RLO_‮_RLE_‫_RLE_‫_PDI_⁩
2cca73
+// LRI_⁦_RLO_‮_RLE_‫_PDI_⁩
2cca73
+// FSI_⁨_RLO_‮_PDI_⁩
2cca73
+// FSI_⁨_FSI_⁨_RLO_‮_PDI_⁩
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+int LRI_\u2066_LRI_\u2066_LRE_\u202a_LRE_\u202a_LRE_\u202a_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int LRI_\u2066_LRI_\u2066_LRE_\u202a_LRE_\u202a_LRE_\u202a_PDI_\u2069_PDI_\u2069;
2cca73
+int LRI_\u2066_LRI_\u2066_LRI_\u2066_LRE_\u202a_LRE_\u202a_LRE_\u202a_PDI_\u2069_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int PDI_\u2069;
2cca73
+int LRI_\u2066_PDI_\u2069;
2cca73
+int RLI_\u2067_PDI_\u2069;
2cca73
+int LRE_\u202a_LRI_\u2066_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int LRI_\u2066_LRE_\u202a_PDF_\u202c_PDI_\u2069;
2cca73
+int LRI_\u2066_LRE_\u202a_LRE_\u202a_PDF_\u202c_PDI_\u2069;
2cca73
+int RLI_\u2067_LRI_\u2066_LRE_\u202a_LRE_\u202a_PDF_\u202c_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int FSI_\u2068_LRI_\u2066_LRE_\u202a_LRE_\u202a_PDF_\u202c_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLO_\u202e_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int RLI_\u2067_PDI_\u2069_RLI_\u2067;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int FSI_\u2068_PDF_\u202c_PDI_\u2069;
2cca73
+int FSI_\u2068_FSI_\u2068_PDF_\u202c_PDI_\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-15.c b/gcc/testsuite/c-c++-common/Wbidi-chars-15.c
2cca73
new file mode 100644
2cca73
index 00000000000..a0ce8ff5e2c
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-15.c
2cca73
@@ -0,0 +1,59 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test unpaired bidi control chars in multiline comments.  */
2cca73
+
2cca73
+/*
2cca73
+ * LRE‪ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * RLE‫ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * LRO‭ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * RLO‮ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * LRI⁦ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * RLI⁧ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/*
2cca73
+ * FSI⁨ end
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* LRE‪
2cca73
+   PDF‬ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* FSI⁨
2cca73
+   PDI⁩ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+
2cca73
+/* LRE<‪>
2cca73
+ *
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-3 } */
2cca73
+
2cca73
+/*
2cca73
+ * LRE<‪>
2cca73
+ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+
2cca73
+/*
2cca73
+ *
2cca73
+ * LRE<‪> */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* RLI<⁧> */ /* PDI<⁩> */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* LRE<‪> */ /* PDF<‬> */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-16.c b/gcc/testsuite/c-c++-common/Wbidi-chars-16.c
2cca73
new file mode 100644
2cca73
index 00000000000..baa0159861c
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-16.c
2cca73
@@ -0,0 +1,26 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=any" } */
2cca73
+/* Test LTR/RTL chars.  */
2cca73
+
2cca73
+/* LTR<‎> */
2cca73
+/* { dg-warning "U\\+200E" "" { target *-*-* } .-1 } */
2cca73
+// LTR<‎>
2cca73
+/* { dg-warning "U\\+200E" "" { target *-*-* } .-1 } */
2cca73
+/* RTL<‏> */
2cca73
+/* { dg-warning "U\\+200F" "" { target *-*-* } .-1 } */
2cca73
+// RTL<‏>
2cca73
+/* { dg-warning "U\\+200F" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+const char *s1 = "LTR<‎>";
2cca73
+/* { dg-warning "U\\+200E" "" { target *-*-* } .-1 } */
2cca73
+const char *s2 = "LTR\u200e";
2cca73
+/* { dg-warning "U\\+200E" "" { target *-*-* } .-1 } */
2cca73
+const char *s3 = "LTR\u200E";
2cca73
+/* { dg-warning "U\\+200E" "" { target *-*-* } .-1 } */
2cca73
+const char *s4 = "RTL<‏>";
2cca73
+/* { dg-warning "U\\+200F" "" { target *-*-* } .-1 } */
2cca73
+const char *s5 = "RTL\u200f";
2cca73
+/* { dg-warning "U\\+200F" "" { target *-*-* } .-1 } */
2cca73
+const char *s6 = "RTL\u200F";
2cca73
+/* { dg-warning "U\\+200F" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-17.c b/gcc/testsuite/c-c++-common/Wbidi-chars-17.c
2cca73
new file mode 100644
2cca73
index 00000000000..07cb4321f96
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-17.c
2cca73
@@ -0,0 +1,30 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test LTR/RTL chars.  */
2cca73
+
2cca73
+/* LTR<‎> */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// LTR<‎>
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* RTL<‏> */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// RTL<‏>
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int ltr_\u200e;
2cca73
+/* { dg-error "universal character " "" { target *-*-* } .-1 } */
2cca73
+int rtl_\u200f;
2cca73
+/* { dg-error "universal character " "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+const char *s1 = "LTR<‎>";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s2 = "LTR\u200e";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s3 = "LTR\u200E";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s4 = "RTL<‏>";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s5 = "RTL\u200f";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s6 = "RTL\u200F";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-2.c b/gcc/testsuite/c-c++-common/Wbidi-chars-2.c
2cca73
new file mode 100644
2cca73
index 00000000000..2340374f276
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-2.c
2cca73
@@ -0,0 +1,9 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+
2cca73
+int main() {
2cca73
+    /* Say hello; newline⁧/*/ return 0 ;
2cca73
+/* { dg-warning "bidirectional" "" { target *-*-* } .-1 } */
2cca73
+    __builtin_printf("Hello world.\n");
2cca73
+    return 0;
2cca73
+}
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-3.c b/gcc/testsuite/c-c++-common/Wbidi-chars-3.c
2cca73
new file mode 100644
2cca73
index 00000000000..9dc7edb6e64
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-3.c
2cca73
@@ -0,0 +1,11 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+
2cca73
+int main() {
2cca73
+    const char* access_level = "user";
2cca73
+    if (__builtin_strcmp(access_level, "user‮ ⁦// Check if admin⁩ ⁦")) {
2cca73
+/* { dg-warning "bidirectional" "" { target *-*-* } .-1 } */
2cca73
+        __builtin_printf("You are an admin.\n");
2cca73
+    }
2cca73
+    return 0;
2cca73
+}
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-4.c b/gcc/testsuite/c-c++-common/Wbidi-chars-4.c
2cca73
new file mode 100644
2cca73
index 00000000000..49f856b9bfe
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-4.c
2cca73
@@ -0,0 +1,172 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=any -Wno-multichar -Wno-overflow" } */
2cca73
+/* Test all bidi chars in various contexts (identifiers, comments,
2cca73
+   string literals, character constants), both UCN and UTF-8.  The bidi
2cca73
+   chars here are properly terminated, except for the character constants.  */
2cca73
+
2cca73
+/* a b c LRE‪ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLE‫ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRO‭ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLO‮ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRI⁦ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLI⁧ 1 2 3 PDI⁩ x y */
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+/* a b c FSI⁨ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Same but C++ comments instead.  */
2cca73
+// a b c LRE‪ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLE‫ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRO‭ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLO‮ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRI⁦ 1 2 3 PDI⁩ x y z
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLI⁧ 1 2 3 PDI⁩ x y
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+// a b c FSI⁨ 1 2 3 PDI⁩ x y z
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Here we're closing an unopened context, warn when =any.  */
2cca73
+/* a b c PDI⁩ x y z */
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
+/* a b c PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+// a b c PDI⁩ x y z
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
+// a b c PDF‬ x y z
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Multiline comments.  */
2cca73
+/* a b c PDI⁩ x y z
2cca73
+   */
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-2 } */
2cca73
+/* a b c PDF‬ x y z
2cca73
+   */
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDI⁩ x y z
2cca73
+   */
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDF‬ x y z
2cca73
+   */
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDI⁩ x y z */
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
+/* first
2cca73
+   a b c PDF‬ x y z */
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+void
2cca73
+g1 ()
2cca73
+{
2cca73
+  const char *s1 = "a b c LRE‪ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+  const char *s2 = "a b c RLE‫ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+  const char *s3 = "a b c LRO‭ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+  const char *s4 = "a b c RLO‮ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+  const char *s5 = "a b c LRI⁦ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+  const char *s6 = "a b c RLI⁧ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+  const char *s7 = "a b c FSI⁨ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+  const char *s8 = "a b c PDI⁩ x y z";
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
+  const char *s9 = "a b c PDF‬ x y z";
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+  const char *s10 = "a b c LRE\u202a 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+  const char *s11 = "a b c LRE\u202A 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+  const char *s12 = "a b c RLE\u202b 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+  const char *s13 = "a b c RLE\u202B 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+  const char *s14 = "a b c LRO\u202d 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+  const char *s15 = "a b c LRO\u202D 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+  const char *s16 = "a b c RLO\u202e 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+  const char *s17 = "a b c RLO\u202E 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+  const char *s18 = "a b c LRI\u2066 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+  const char *s19 = "a b c RLI\u2067 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+  const char *s20 = "a b c FSI\u2068 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+}
2cca73
+
2cca73
+void
2cca73
+g2 ()
2cca73
+{
2cca73
+  const char c1 = '\u202a';
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+  const char c2 = '\u202A';
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+  const char c3 = '\u202b';
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+  const char c4 = '\u202B';
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+  const char c5 = '\u202d';
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+  const char c6 = '\u202D';
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+  const char c7 = '\u202e';
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+  const char c8 = '\u202E';
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+  const char c9 = '\u2066';
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+  const char c10 = '\u2067';
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+  const char c11 = '\u2068';
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+}
2cca73
+
2cca73
+int A\u202cY;
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+int A\u202CY2;
2cca73
+/* { dg-warning "U\\+202C" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+int d\u202ae\u202cf;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Ae\u202cf2;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+int d\u202be\u202cf;
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Be\u202cf2;
2cca73
+/* { dg-warning "U\\+202B" "" { target *-*-* } .-1 } */
2cca73
+int d\u202de\u202cf;
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+int d\u202De\u202cf2;
2cca73
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
2cca73
+int d\u202ee\u202cf;
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Ee\u202cf2;
2cca73
+/* { dg-warning "U\\+202E" "" { target *-*-* } .-1 } */
2cca73
+int d\u2066e\u2069f;
2cca73
+/* { dg-warning "U\\+2066" "" { target *-*-* } .-1 } */
2cca73
+int d\u2067e\u2069f;
2cca73
+/* { dg-warning "U\\+2067" "" { target *-*-* } .-1 } */
2cca73
+int d\u2068e\u2069f;
2cca73
+/* { dg-warning "U\\+2068" "" { target *-*-* } .-1 } */
2cca73
+int X\u2069;
2cca73
+/* { dg-warning "U\\+2069" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-5.c b/gcc/testsuite/c-c++-common/Wbidi-chars-5.c
2cca73
new file mode 100644
2cca73
index 00000000000..f5776806c79
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-5.c
2cca73
@@ -0,0 +1,172 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired -Wno-multichar -Wno-overflow" } */
2cca73
+/* Test all bidi chars in various contexts (identifiers, comments,
2cca73
+   string literals, character constants), both UCN and UTF-8.  The bidi
2cca73
+   chars here are properly terminated, except for the character constants.  */
2cca73
+
2cca73
+/* a b c LRE‪ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLE‫ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRO‭ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLO‮ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRI⁦ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLI⁧ 1 2 3 PDI⁩ x y */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c FSI⁨ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Same but C++ comments instead.  */
2cca73
+// a b c LRE‪ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLE‫ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRO‭ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLO‮ 1 2 3 PDF‬ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRI⁦ 1 2 3 PDI⁩ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLI⁧ 1 2 3 PDI⁩ x y
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c FSI⁨ 1 2 3 PDI⁩ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Here we're closing an unopened context, warn when =any.  */
2cca73
+/* a b c PDI⁩ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c PDI⁩ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c PDF‬ x y z
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* Multiline comments.  */
2cca73
+/* a b c PDI⁩ x y z
2cca73
+   */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* a b c PDF‬ x y z
2cca73
+   */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDI⁩ x y z
2cca73
+   */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDF‬ x y z
2cca73
+   */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-2 } */
2cca73
+/* first
2cca73
+   a b c PDI⁩ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* first
2cca73
+   a b c PDF‬ x y z */
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+void
2cca73
+g1 ()
2cca73
+{
2cca73
+  const char *s1 = "a b c LRE‪ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s2 = "a b c RLE‫ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s3 = "a b c LRO‭ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s4 = "a b c RLO‮ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s5 = "a b c LRI⁦ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s6 = "a b c RLI⁧ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s7 = "a b c FSI⁨ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s8 = "a b c PDI⁩ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s9 = "a b c PDF‬ x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+  const char *s10 = "a b c LRE\u202a 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s11 = "a b c LRE\u202A 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s12 = "a b c RLE\u202b 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s13 = "a b c RLE\u202B 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s14 = "a b c LRO\u202d 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s15 = "a b c LRO\u202D 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s16 = "a b c RLO\u202e 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s17 = "a b c RLO\u202E 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s18 = "a b c LRI\u2066 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s19 = "a b c RLI\u2067 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s20 = "a b c FSI\u2068 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+}
2cca73
+
2cca73
+void
2cca73
+g2 ()
2cca73
+{
2cca73
+  const char c1 = '\u202a';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c2 = '\u202A';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c3 = '\u202b';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c4 = '\u202B';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c5 = '\u202d';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c6 = '\u202D';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c7 = '\u202e';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c8 = '\u202E';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c9 = '\u2066';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c10 = '\u2067';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char c11 = '\u2068';
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+}
2cca73
+
2cca73
+int A\u202cY;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int A\u202CY2;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+int d\u202ae\u202cf;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Ae\u202cf2;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202be\u202cf;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Be\u202cf2;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202de\u202cf;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202De\u202cf2;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202ee\u202cf;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u202Ee\u202cf2;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u2066e\u2069f;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u2067e\u2069f;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int d\u2068e\u2069f;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int X\u2069;
2cca73
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-6.c b/gcc/testsuite/c-c++-common/Wbidi-chars-6.c
2cca73
new file mode 100644
2cca73
index 00000000000..a65d6faf60e
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-6.c
2cca73
@@ -0,0 +1,130 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test nesting of bidi chars in various contexts.  */
2cca73
+
2cca73
+/* Terminated by the wrong char:  */
2cca73
+/* a b c LRE‪ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLE‫ 1 2 3 PDI⁩ x y  z*/
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRO‭ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLO‮ 1 2 3 PDI⁩ x y z */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c LRI⁦ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c RLI⁧ 1 2 3 PDF‬ x y z */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* a b c FSI⁨ 1 2 3 PDF‬ x y  z*/
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+/* LRE‪ PDF‬ */
2cca73
+/* LRE‪ LRE‪ PDF‬ PDF‬ */
2cca73
+/* PDF‬ LRE‪ PDF‬ */
2cca73
+/* LRE‪ PDF‬ LRE‪ PDF‬ */
2cca73
+/* LRE‪ LRE‪ PDF‬ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* PDF‬ LRE‪ */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+// a b c LRE‪ 1 2 3 PDI⁩ x y z
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLE‫ 1 2 3 PDI⁩ x y  z*/
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRO‭ 1 2 3 PDI⁩ x y z 
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLO‮ 1 2 3 PDI⁩ x y z 
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c LRI⁦ 1 2 3 PDF‬ x y z 
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c RLI⁧ 1 2 3 PDF‬ x y z 
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// a b c FSI⁨ 1 2 3 PDF‬ x y  z
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+// LRE‪ PDF‬ 
2cca73
+// LRE‪ LRE‪ PDF‬ PDF‬
2cca73
+// PDF‬ LRE‪ PDF‬
2cca73
+// LRE‪ PDF‬ LRE‪ PDF‬
2cca73
+// LRE‪ LRE‪ PDF‬
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+// PDF‬ LRE‪
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+void
2cca73
+g1 ()
2cca73
+{
2cca73
+  const char *s1 = "a b c LRE‪ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s2 = "a b c LRE\u202a 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s3 = "a b c RLE‫ 1 2 3 PDI⁩ x y ";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s4 = "a b c RLE\u202b 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s5 = "a b c LRO‭ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s6 = "a b c LRO\u202d 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s7 = "a b c RLO‮ 1 2 3 PDI⁩ x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s8 = "a b c RLO\u202e 1 2 3 PDI\u2069 x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s9 = "a b c LRI⁦ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s10 = "a b c LRI\u2066 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s11 = "a b c RLI⁧ 1 2 3 PDF‬ x y z\
2cca73
+    ";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-2 } */
2cca73
+  const char *s12 = "a b c RLI\u2067 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s13 = "a b c FSI⁨ 1 2 3 PDF‬ x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s14 = "a b c FSI\u2068 1 2 3 PDF\u202c x y z";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s15 = "PDF‬ LRE‪";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s16 = "PDF\u202c LRE\u202a";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s17 = "LRE‪ PDF‬";
2cca73
+  const char *s18 = "LRE\u202a PDF\u202c";
2cca73
+  const char *s19 = "LRE‪ LRE‪ PDF‬ PDF‬";
2cca73
+  const char *s20 = "LRE\u202a LRE\u202a PDF\u202c PDF\u202c";
2cca73
+  const char *s21 = "PDF‬ LRE‪ PDF‬";
2cca73
+  const char *s22 = "PDF\u202c LRE\u202a PDF\u202c";
2cca73
+  const char *s23 = "LRE‪ LRE‪ PDF‬";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s24 = "LRE\u202a LRE\u202a PDF\u202c";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s25 = "PDF‬ LRE‪";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s26 = "PDF\u202c LRE\u202a";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s27 = "PDF‬ LRE\u202a";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+  const char *s28 = "PDF\u202c LRE‪";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+}
2cca73
+
2cca73
+int A\u202aB\u2069C;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u202bB\u2069c;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u202db\u2069c2;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u202eb\u2069;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u2066b\u202c;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u2067b\u202c;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int a\u2068b\u202c;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int A\u202aB\u202c;
2cca73
+int A\u202aA\u202aB\u202cB\u202c;
2cca73
+int a_\u202C_\u202a_\u202c;
2cca73
+int a_\u202a_\u202c_\u202a_\u202c_;
2cca73
+int a_\u202a_\u202c_\u202a_;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-7.c b/gcc/testsuite/c-c++-common/Wbidi-chars-7.c
2cca73
new file mode 100644
2cca73
index 00000000000..d012d420ec0
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-7.c
2cca73
@@ -0,0 +1,9 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=any" } */
2cca73
+/* Test we ignore UCNs in comments.  */
2cca73
+
2cca73
+// a b c \u202a 1 2 3
2cca73
+// a b c \u202A 1 2 3
2cca73
+/* a b c \u202a 1 2 3 */
2cca73
+/* a b c \u202A 1 2 3 */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-8.c b/gcc/testsuite/c-c++-common/Wbidi-chars-8.c
2cca73
new file mode 100644
2cca73
index 00000000000..4f54c5092ec
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-8.c
2cca73
@@ -0,0 +1,13 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=any" } */
2cca73
+/* Test \u vs \U.  */
2cca73
+
2cca73
+int a_\u202A;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+int a_\u202a_2;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+int a_\U0000202A_3;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
+int a_\U0000202a_4;
2cca73
+/* { dg-warning "U\\+202A" "" { target *-*-* } .-1 } */
2cca73
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-9.c b/gcc/testsuite/c-c++-common/Wbidi-chars-9.c
2cca73
new file mode 100644
2cca73
index 00000000000..e2af1b1ca97
2cca73
--- /dev/null
2cca73
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-9.c
2cca73
@@ -0,0 +1,29 @@
2cca73
+/* PR preprocessor/103026 */
2cca73
+/* { dg-do compile } */
2cca73
+/* { dg-options "-Wbidi-chars=unpaired" } */
2cca73
+/* Test that we properly separate bidi contexts (comment/identifier/character
2cca73
+   constant/string literal).  */
2cca73
+
2cca73
+/* LRE ->‪<- */ int pdf_\u202c_1;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* RLE ->‫<- */ int pdf_\u202c_2;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* LRO ->‭<- */ int pdf_\u202c_3;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* RLO ->‮<- */ int pdf_\u202c_4;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* LRI ->⁦<-*/ int pdi_\u2069_1;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* RLI ->⁧<- */ int pdi_\u2069_12;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* FSI ->⁨<- */ int pdi_\u2069_3;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+
2cca73
+const char *s1 = "LRE\u202a"; /* PDF ->‬<- */
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+/* LRE ->‪<- */ const char *s2 = "PDF\u202c";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+const char *s3 = "LRE\u202a"; int pdf_\u202c_5;
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
+int lre_\u202a; const char *s4 = "PDF\u202c";
2cca73
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
2cca73
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
2cca73
index 3ad52d5e01e..e0dcb7f0529 100644
2cca73
--- a/libcpp/include/cpplib.h
2cca73
+++ b/libcpp/include/cpplib.h
2cca73
@@ -305,6 +305,17 @@ enum cpp_normalize_level {
2cca73
   normalized_none
2cca73
 };
2cca73
 
2cca73
+/* The possible bidirectional control characters checking levels, from least
2cca73
+   restrictive to most.  */
2cca73
+enum cpp_bidirectional_level {
2cca73
+  /* No checking.  */
2cca73
+  bidirectional_none,
2cca73
+  /* Only detect unpaired uses of bidirectional control characters.  */
2cca73
+  bidirectional_unpaired,
2cca73
+  /* Detect any use of bidirectional control characters.  */
2cca73
+  bidirectional_any
2cca73
+};
2cca73
+
2cca73
 /* This structure is nested inside struct cpp_reader, and
2cca73
    carries all the options visible to the command line.  */
2cca73
 struct cpp_options
2cca73
@@ -506,6 +517,10 @@ struct cpp_options
2cca73
   /* True if warn about differences between C++98 and C++11.  */
2cca73
   bool cpp_warn_cxx11_compat;
2cca73
 
2cca73
+  /* Nonzero if bidirectional control characters checking is on.  See enum
2cca73
+     cpp_bidirectional_level.  */
2cca73
+  unsigned char cpp_warn_bidirectional;
2cca73
+
2cca73
   /* Dependency generation.  */
2cca73
   struct
2cca73
   {
2cca73
@@ -1063,7 +1078,8 @@ enum {
2cca73
   CPP_W_PEDANTIC,
2cca73
   CPP_W_C90_C99_COMPAT,
2cca73
   CPP_W_CXX11_COMPAT,
2cca73
-  CPP_W_EXPANSION_TO_DEFINED
2cca73
+  CPP_W_EXPANSION_TO_DEFINED,
2cca73
+  CPP_W_BIDIRECTIONAL
2cca73
 };
2cca73
 
2cca73
 /* Output a diagnostic of some kind.  */
2cca73
diff --git a/libcpp/init.c b/libcpp/init.c
2cca73
index ca3fbaa5c05..5c15da82ff8 100644
2cca73
--- a/libcpp/init.c
2cca73
+++ b/libcpp/init.c
2cca73
@@ -208,6 +208,7 @@ cpp_create_reader (enum c_lang lang, cpp_hash_table *table,
2cca73
       = ENABLE_CANONICAL_SYSTEM_HEADERS;
2cca73
   CPP_OPTION (pfile, ext_numeric_literals) = 1;
2cca73
   CPP_OPTION (pfile, warn_date_time) = 0;
2cca73
+  CPP_OPTION (pfile, cpp_warn_bidirectional) = bidirectional_unpaired;
2cca73
 
2cca73
   /* Default CPP arithmetic to something sensible for the host for the
2cca73
      benefit of dumb users like fix-header.  */
2cca73
diff --git a/libcpp/internal.h b/libcpp/internal.h
2cca73
index 4f74f995cec..53b4c0f4af7 100644
2cca73
--- a/libcpp/internal.h
2cca73
+++ b/libcpp/internal.h
2cca73
@@ -576,6 +576,13 @@ struct cpp_reader
2cca73
   /* If non-null, the lexer will use this location for the next token
2cca73
      instead of getting a location from the linemap.  */
2cca73
   source_location *forced_token_location_p;
2cca73
+
2cca73
+  /* Returns true iff we should warn about UTF-8 bidirectional control
2cca73
+     characters.  */
2cca73
+  bool warn_bidi_p () const
2cca73
+  {
2cca73
+    return CPP_OPTION (this, cpp_warn_bidirectional) != bidirectional_none;
2cca73
+  }
2cca73
 };
2cca73
 
2cca73
 /* Character classes.  Based on the more primitive macros in safe-ctype.h.
2cca73
diff --git a/libcpp/lex.c b/libcpp/lex.c
2cca73
index a408f912c5c..ea7f75e842e 100644
2cca73
--- a/libcpp/lex.c
2cca73
+++ b/libcpp/lex.c
2cca73
@@ -1164,6 +1164,324 @@ _cpp_process_line_notes (cpp_reader *pfile, int in_comment)
2cca73
     }
2cca73
 }
2cca73
 
2cca73
+namespace bidi {
2cca73
+  enum kind {
2cca73
+    NONE, LRE, RLE, LRO, RLO, LRI, RLI, FSI, PDF, PDI, LTR, RTL
2cca73
+  };
2cca73
+
2cca73
+  /* All the UTF-8 encodings of bidi characters start with E2.  */
2cca73
+  const uchar utf8_start = 0xe2;
2cca73
+
2cca73
+  /* A vector holding currently open bidi contexts.  We use a char for
2cca73
+     each context, its LSB is 1 if it represents a PDF context, 0 if it
2cca73
+     represents a PDI context.  The next bit is 1 if this context was open
2cca73
+     by a bidi character written as a UCN, and 0 when it was UTF-8.  */
2cca73
+  semi_embedded_vec <unsigned char, 16> vec;
2cca73
+
2cca73
+  /* Close the whole comment/identifier/string literal/character constant
2cca73
+     context.  */
2cca73
+  void on_close ()
2cca73
+  {
2cca73
+    vec.truncate (0);
2cca73
+  }
2cca73
+
2cca73
+  /* Pop the last element in the vector.  */
2cca73
+  void pop ()
2cca73
+  {
2cca73
+    unsigned int len = vec.count ();
2cca73
+    gcc_checking_assert (len > 0);
2cca73
+    vec.truncate (len - 1);
2cca73
+  }
2cca73
+
2cca73
+  /* Return the context of the Ith element.  */
2cca73
+  kind ctx_at (unsigned int i)
2cca73
+  {
2cca73
+    return (vec[i] & 1) ? PDF : PDI;
2cca73
+  }
2cca73
+
2cca73
+  /* Return which context is currently opened.  */
2cca73
+  kind current_ctx ()
2cca73
+  {
2cca73
+    unsigned int len = vec.count ();
2cca73
+    if (len == 0)
2cca73
+      return NONE;
2cca73
+    return ctx_at (len - 1);
2cca73
+  }
2cca73
+
2cca73
+  /* Return true if the current context comes from a UCN origin, that is,
2cca73
+     the bidi char which started this bidi context was written as a UCN.  */
2cca73
+  bool current_ctx_ucn_p ()
2cca73
+  {
2cca73
+    unsigned int len = vec.count ();
2cca73
+    gcc_checking_assert (len > 0);
2cca73
+    return (vec[len - 1] >> 1) & 1;
2cca73
+  }
2cca73
+
2cca73
+  /* We've read a bidi char, update the current vector as necessary.  */
2cca73
+  void on_char (kind k, bool ucn_p)
2cca73
+  {
2cca73
+    switch (k)
2cca73
+      {
2cca73
+      case LRE:
2cca73
+      case RLE:
2cca73
+      case LRO:
2cca73
+      case RLO:
2cca73
+	vec.push (ucn_p ? 3u : 1u);
2cca73
+	break;
2cca73
+      case LRI:
2cca73
+      case RLI:
2cca73
+      case FSI:
2cca73
+	vec.push (ucn_p ? 2u : 0u);
2cca73
+	break;
2cca73
+      /* PDF terminates the scope of the last LRE, RLE, LRO, or RLO
2cca73
+	 whose scope has not yet been terminated.  */
2cca73
+      case PDF:
2cca73
+	if (current_ctx () == PDF)
2cca73
+	  pop ();
2cca73
+	break;
2cca73
+      /* PDI terminates the scope of the last LRI, RLI, or FSI whose
2cca73
+	 scope has not yet been terminated, as well as the scopes of
2cca73
+	 any subsequent LREs, RLEs, LROs, or RLOs whose scopes have not
2cca73
+	 yet been terminated.  */
2cca73
+      case PDI:
2cca73
+	for (int i = vec.count () - 1; i >= 0; --i)
2cca73
+	  if (ctx_at (i) == PDI)
2cca73
+	    {
2cca73
+	      vec.truncate (i);
2cca73
+	      break;
2cca73
+	    }
2cca73
+	break;
2cca73
+      case LTR:
2cca73
+      case RTL:
2cca73
+	/* These aren't popped by a PDF/PDI.  */
2cca73
+	break;
2cca73
+      [[likely]] case NONE:
2cca73
+	break;
2cca73
+      default:
2cca73
+	abort ();
2cca73
+      }
2cca73
+  }
2cca73
+
2cca73
+  /* Return a descriptive string for K.  */
2cca73
+  const char *to_str (kind k)
2cca73
+  {
2cca73
+    switch (k)
2cca73
+      {
2cca73
+      case LRE:
2cca73
+	return "U+202A (LEFT-TO-RIGHT EMBEDDING)";
2cca73
+      case RLE:
2cca73
+	return "U+202B (RIGHT-TO-LEFT EMBEDDING)";
2cca73
+      case LRO:
2cca73
+	return "U+202D (LEFT-TO-RIGHT OVERRIDE)";
2cca73
+      case RLO:
2cca73
+	return "U+202E (RIGHT-TO-LEFT OVERRIDE)";
2cca73
+      case LRI:
2cca73
+	return "U+2066 (LEFT-TO-RIGHT ISOLATE)";
2cca73
+      case RLI:
2cca73
+	return "U+2067 (RIGHT-TO-LEFT ISOLATE)";
2cca73
+      case FSI:
2cca73
+	return "U+2068 (FIRST STRONG ISOLATE)";
2cca73
+      case PDF:
2cca73
+	return "U+202C (POP DIRECTIONAL FORMATTING)";
2cca73
+      case PDI:
2cca73
+	return "U+2069 (POP DIRECTIONAL ISOLATE)";
2cca73
+      case LTR:
2cca73
+	return "U+200E (LEFT-TO-RIGHT MARK)";
2cca73
+      case RTL:
2cca73
+	return "U+200F (RIGHT-TO-LEFT MARK)";
2cca73
+      default:
2cca73
+	abort ();
2cca73
+      }
2cca73
+  }
2cca73
+}
2cca73
+
2cca73
+/* Parse a sequence of 3 bytes starting with P and return its bidi code.  */
2cca73
+
2cca73
+static bidi::kind
2cca73
+get_bidi_utf8 (const unsigned char *const p)
2cca73
+{
2cca73
+  gcc_checking_assert (p[0] == bidi::utf8_start);
2cca73
+
2cca73
+  if (p[1] == 0x80)
2cca73
+    switch (p[2])
2cca73
+      {
2cca73
+      case 0xaa:
2cca73
+	return bidi::LRE;
2cca73
+      case 0xab:
2cca73
+	return bidi::RLE;
2cca73
+      case 0xac:
2cca73
+	return bidi::PDF;
2cca73
+      case 0xad:
2cca73
+	return bidi::LRO;
2cca73
+      case 0xae:
2cca73
+	return bidi::RLO;
2cca73
+      case 0x8e:
2cca73
+	return bidi::LTR;
2cca73
+      case 0x8f:
2cca73
+	return bidi::RTL;
2cca73
+      default:
2cca73
+	break;
2cca73
+      }
2cca73
+  else if (p[1] == 0x81)
2cca73
+    switch (p[2])
2cca73
+      {
2cca73
+      case 0xa6:
2cca73
+	return bidi::LRI;
2cca73
+      case 0xa7:
2cca73
+	return bidi::RLI;
2cca73
+      case 0xa8:
2cca73
+	return bidi::FSI;
2cca73
+      case 0xa9:
2cca73
+	return bidi::PDI;
2cca73
+      default:
2cca73
+	break;
2cca73
+      }
2cca73
+
2cca73
+  return bidi::NONE;
2cca73
+}
2cca73
+
2cca73
+/* Parse a UCN where P points just past \u or \U and return its bidi code.  */
2cca73
+
2cca73
+static bidi::kind
2cca73
+get_bidi_ucn (const unsigned char *p, bool is_U)
2cca73
+{
2cca73
+  /* 6.4.3 Universal Character Names
2cca73
+      \u hex-quad
2cca73
+      \U hex-quad hex-quad
2cca73
+     where \unnnn means \U0000nnnn.  */
2cca73
+
2cca73
+  if (is_U)
2cca73
+    {
2cca73
+      if (p[0] != '0' || p[1] != '0' || p[2] != '0' || p[3] != '0')
2cca73
+	return bidi::NONE;
2cca73
+      /* Skip 4B so we can treat \u and \U the same below.  */
2cca73
+      p += 4;
2cca73
+    }
2cca73
+
2cca73
+  /* All code points we are looking for start with 20xx.  */
2cca73
+  if (p[0] != '2' || p[1] != '0')
2cca73
+    return bidi::NONE;
2cca73
+  else if (p[2] == '2')
2cca73
+    switch (p[3])
2cca73
+      {
2cca73
+      case 'a':
2cca73
+      case 'A':
2cca73
+	return bidi::LRE;
2cca73
+      case 'b':
2cca73
+      case 'B':
2cca73
+	return bidi::RLE;
2cca73
+      case 'c':
2cca73
+      case 'C':
2cca73
+	return bidi::PDF;
2cca73
+      case 'd':
2cca73
+      case 'D':
2cca73
+	return bidi::LRO;
2cca73
+      case 'e':
2cca73
+      case 'E':
2cca73
+	return bidi::RLO;
2cca73
+      default:
2cca73
+	break;
2cca73
+      }
2cca73
+  else if (p[2] == '6')
2cca73
+    switch (p[3])
2cca73
+      {
2cca73
+      case '6':
2cca73
+	return bidi::LRI;
2cca73
+      case '7':
2cca73
+	return bidi::RLI;
2cca73
+      case '8':
2cca73
+	return bidi::FSI;
2cca73
+      case '9':
2cca73
+	return bidi::PDI;
2cca73
+      default:
2cca73
+	break;
2cca73
+      }
2cca73
+  else if (p[2] == '0')
2cca73
+    switch (p[3])
2cca73
+      {
2cca73
+      case 'e':
2cca73
+      case 'E':
2cca73
+	return bidi::LTR;
2cca73
+      case 'f':
2cca73
+      case 'F':
2cca73
+	return bidi::RTL;
2cca73
+      default:
2cca73
+	break;
2cca73
+      }
2cca73
+
2cca73
+  return bidi::NONE;
2cca73
+}
2cca73
+
2cca73
+/* We're closing a bidi context, that is, we've encountered a newline,
2cca73
+   are closing a C-style comment, or are at the end of a string literal,
2cca73
+   character constant, or identifier.  Warn if this context was not
2cca73
+   properly terminated by a PDI or PDF.  P points to the last character
2cca73
+   in this context.  */
2cca73
+
2cca73
+static void
2cca73
+maybe_warn_bidi_on_close (cpp_reader *pfile, const uchar *p)
2cca73
+{
2cca73
+  if (CPP_OPTION (pfile, cpp_warn_bidirectional) == bidirectional_unpaired
2cca73
+      && bidi::vec.count () > 0)
2cca73
+    {
2cca73
+      const source_location loc
2cca73
+	= linemap_position_for_column (pfile->line_table,
2cca73
+				       CPP_BUF_COLUMN (pfile->buffer, p));
2cca73
+      cpp_warning_with_line (pfile, CPP_W_BIDIRECTIONAL, loc, 0,
2cca73
+			     "unpaired UTF-8 bidirectional control character "
2cca73
+			     "detected");
2cca73
+    }
2cca73
+  /* We're done with this context.  */
2cca73
+  bidi::on_close ();
2cca73
+}
2cca73
+
2cca73
+/* We're at the beginning or in the middle of an identifier/comment/string
2cca73
+   literal/character constant.  Warn if we've encountered a bidi character.
2cca73
+   KIND says which bidi character it was; P points to it in the character
2cca73
+   stream.  UCN_P is true iff this bidi character was written as a UCN.  */
2cca73
+
2cca73
+static void
2cca73
+maybe_warn_bidi_on_char (cpp_reader *pfile, const uchar *p, bidi::kind kind,
2cca73
+			 bool ucn_p)
2cca73
+{
2cca73
+  if (__builtin_expect (kind == bidi::NONE, 1))
2cca73
+    return;
2cca73
+
2cca73
+  const unsigned char warn_bidi = CPP_OPTION (pfile, cpp_warn_bidirectional);
2cca73
+
2cca73
+  if (warn_bidi != bidirectional_none)
2cca73
+    {
2cca73
+      const source_location loc
2cca73
+	= linemap_position_for_column (pfile->line_table,
2cca73
+				       CPP_BUF_COLUMN (pfile->buffer, p));
2cca73
+      /* It seems excessive to warn about a PDI/PDF that is closing
2cca73
+	 an opened context because we've already warned about the
2cca73
+	 opening character.  Except warn when we have a UCN x UTF-8
2cca73
+	 mismatch.  */
2cca73
+      if (kind == bidi::current_ctx ())
2cca73
+	{
2cca73
+	  if (warn_bidi == bidirectional_unpaired
2cca73
+	      && bidi::current_ctx_ucn_p () != ucn_p)
2cca73
+	    cpp_warning_with_line (pfile, CPP_W_BIDIRECTIONAL, loc, 0,
2cca73
+				   "UTF-8 vs UCN mismatch when closing "
2cca73
+				   "a context by \"%s\"", bidi::to_str (kind));
2cca73
+	}
2cca73
+      else if (warn_bidi == bidirectional_any)
2cca73
+	{
2cca73
+	  if (kind == bidi::PDF || kind == bidi::PDI)
2cca73
+	    cpp_warning_with_line (pfile, CPP_W_BIDIRECTIONAL, loc, 0,
2cca73
+				   "\"%s\" is closing an unopened context",
2cca73
+				   bidi::to_str (kind));
2cca73
+	  else
2cca73
+	    cpp_warning_with_line (pfile, CPP_W_BIDIRECTIONAL, loc, 0,
2cca73
+				   "found problematic Unicode character \"%s\"",
2cca73
+				   bidi::to_str (kind));
2cca73
+	}
2cca73
+    }
2cca73
+  /* We're done with this context.  */
2cca73
+  bidi::on_char (kind, ucn_p);
2cca73
+}
2cca73
+
2cca73
 /* Skip a C-style block comment.  We find the end of the comment by
2cca73
    seeing if an asterisk is before every '/' we encounter.  Returns
2cca73
    nonzero if comment terminated by EOF, zero otherwise.
2cca73
@@ -1175,6 +1493,7 @@ _cpp_skip_block_comment (cpp_reader *pfile)
2cca73
   cpp_buffer *buffer = pfile->buffer;
2cca73
   const uchar *cur = buffer->cur;
2cca73
   uchar c;
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
 
2cca73
   cur++;
2cca73
   if (*cur == '/')
2cca73
@@ -1189,7 +1508,11 @@ _cpp_skip_block_comment (cpp_reader *pfile)
2cca73
       if (c == '/')
2cca73
 	{
2cca73
 	  if (cur[-2] == '*')
2cca73
-	    break;
2cca73
+	    {
2cca73
+	      if (warn_bidi_p)
2cca73
+		maybe_warn_bidi_on_close (pfile, cur);
2cca73
+	      break;
2cca73
+	    }
2cca73
 
2cca73
 	  /* Warn about potential nested comments, but not if the '/'
2cca73
 	     comes immediately before the true comment delimiter.
2cca73
@@ -1208,6 +1531,8 @@ _cpp_skip_block_comment (cpp_reader *pfile)
2cca73
 	{
2cca73
 	  unsigned int cols;
2cca73
 	  buffer->cur = cur - 1;
2cca73
+	  if (warn_bidi_p)
2cca73
+	    maybe_warn_bidi_on_close (pfile, cur);
2cca73
 	  _cpp_process_line_notes (pfile, true);
2cca73
 	  if (buffer->next_line >= buffer->rlimit)
2cca73
 	    return true;
2cca73
@@ -1218,6 +1543,13 @@ _cpp_skip_block_comment (cpp_reader *pfile)
2cca73
 
2cca73
 	  cur = buffer->cur;
2cca73
 	}
2cca73
+      /* If this is a beginning of a UTF-8 encoding, it might be
2cca73
+	 a bidirectional control character.  */
2cca73
+      else if (__builtin_expect (c == bidi::utf8_start, 0) && warn_bidi_p)
2cca73
+	{
2cca73
+	  bidi::kind kind = get_bidi_utf8 (cur - 1);
2cca73
+	  maybe_warn_bidi_on_char (pfile, cur, kind, /*ucn_p=*/false);
2cca73
+	}
2cca73
     }
2cca73
 
2cca73
   buffer->cur = cur;
2cca73
@@ -1233,9 +1565,31 @@ skip_line_comment (cpp_reader *pfile)
2cca73
 {
2cca73
   cpp_buffer *buffer = pfile->buffer;
2cca73
   source_location orig_line = pfile->line_table->highest_line;
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
 
2cca73
-  while (*buffer->cur != '\n')
2cca73
-    buffer->cur++;
2cca73
+  if (!warn_bidi_p)
2cca73
+    while (*buffer->cur != '\n')
2cca73
+      buffer->cur++;
2cca73
+  else
2cca73
+    {
2cca73
+      while (*buffer->cur != '\n'
2cca73
+	     && *buffer->cur != bidi::utf8_start)
2cca73
+	buffer->cur++;
2cca73
+      if (__builtin_expect (*buffer->cur == bidi::utf8_start, 0))
2cca73
+	{
2cca73
+	  while (*buffer->cur != '\n')
2cca73
+	    {
2cca73
+	      if (__builtin_expect (*buffer->cur == bidi::utf8_start, 0))
2cca73
+		{
2cca73
+		  bidi::kind kind = get_bidi_utf8 (buffer->cur);
2cca73
+		  maybe_warn_bidi_on_char (pfile, buffer->cur, kind,
2cca73
+					   /*ucn_p=*/false);
2cca73
+		}
2cca73
+	      buffer->cur++;
2cca73
+	    }
2cca73
+	  maybe_warn_bidi_on_close (pfile, buffer->cur);
2cca73
+	}
2cca73
+    }
2cca73
 
2cca73
   _cpp_process_line_notes (pfile, true);
2cca73
   return orig_line != pfile->line_table->highest_line;
2cca73
@@ -1315,11 +1669,13 @@ warn_about_normalization (cpp_reader *pfile,
2cca73
 
2cca73
 /* Returns TRUE if the sequence starting at buffer->cur is invalid in
2cca73
    an identifier.  FIRST is TRUE if this starts an identifier.  */
2cca73
+
2cca73
 static bool
2cca73
 forms_identifier_p (cpp_reader *pfile, int first,
2cca73
 		    struct normalize_state *state)
2cca73
 {
2cca73
   cpp_buffer *buffer = pfile->buffer;
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
 
2cca73
   if (*buffer->cur == '$')
2cca73
     {
2cca73
@@ -1343,6 +1699,12 @@ forms_identifier_p (cpp_reader *pfile, int first,
2cca73
     {
2cca73
       cppchar_t s;
2cca73
       buffer->cur += 2;
2cca73
+      if (warn_bidi_p)
2cca73
+	{
2cca73
+	  bidi::kind kind = get_bidi_ucn (buffer->cur,
2cca73
+					  buffer->cur[-1] == 'U');
2cca73
+	  maybe_warn_bidi_on_char (pfile, buffer->cur, kind, /*ucn_p=*/true);
2cca73
+	}
2cca73
       if (_cpp_valid_ucn (pfile, &buffer->cur, buffer->rlimit, 1 + !first,
2cca73
 			  state, &s, NULL, NULL))
2cca73
 	return true;
2cca73
@@ -1450,6 +1812,7 @@ lex_identifier (cpp_reader *pfile, const uchar *base, bool starts_ucn,
2cca73
   const uchar *cur;
2cca73
   unsigned int len;
2cca73
   unsigned int hash = HT_HASHSTEP (0, *base);
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
 
2cca73
   cur = pfile->buffer->cur;
2cca73
   if (! starts_ucn)
2cca73
@@ -1472,6 +1835,8 @@ lex_identifier (cpp_reader *pfile, const uchar *base, bool starts_ucn,
2cca73
 	    pfile->buffer->cur++;
2cca73
 	  }
2cca73
       } while (forms_identifier_p (pfile, false, nst));
2cca73
+      if (warn_bidi_p)
2cca73
+	maybe_warn_bidi_on_close (pfile, pfile->buffer->cur);
2cca73
       result = _cpp_interpret_identifier (pfile, base,
2cca73
 					  pfile->buffer->cur - base);
2cca73
       *spelling = cpp_lookup (pfile, base, pfile->buffer->cur - base);
2cca73
@@ -1673,6 +2038,7 @@ lex_raw_string (cpp_reader *pfile, cpp_token *token, const uchar *base,
2cca73
   _cpp_buff *first_buff = NULL, *last_buff = NULL;
2cca73
   size_t raw_prefix_start;
2cca73
   _cpp_line_note *note = &pfile->buffer->notes[pfile->buffer->cur_note];
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
 
2cca73
   type = (*base == 'L' ? CPP_WSTRING :
2cca73
 	  *base == 'U' ? CPP_STRING32 :
2cca73
@@ -1909,8 +2275,15 @@ lex_raw_string (cpp_reader *pfile, cpp_token *token, const uchar *base,
2cca73
 	  cur = base = pfile->buffer->cur;
2cca73
 	  note = &pfile->buffer->notes[pfile->buffer->cur_note];
2cca73
 	}
2cca73
+      else if (__builtin_expect ((unsigned char) c == bidi::utf8_start, 0)
2cca73
+	       && warn_bidi_p)
2cca73
+	maybe_warn_bidi_on_char (pfile, cur - 1, get_bidi_utf8 (cur - 1),
2cca73
+				 /*ucn_p=*/false);
2cca73
     }
2cca73
 
2cca73
+  if (warn_bidi_p)
2cca73
+    maybe_warn_bidi_on_close (pfile, cur);
2cca73
+
2cca73
   if (CPP_OPTION (pfile, user_literals))
2cca73
     {
2cca73
       /* If a string format macro, say from inttypes.h, is placed touching
2cca73
@@ -2005,15 +2378,27 @@ lex_string (cpp_reader *pfile, cpp_token *token, const uchar *base)
2cca73
   else
2cca73
     terminator = '>', type = CPP_HEADER_NAME;
2cca73
 
2cca73
+  const bool warn_bidi_p = pfile->warn_bidi_p ();
2cca73
   for (;;)
2cca73
     {
2cca73
       cppchar_t c = *cur++;
2cca73
 
2cca73
       /* In #include-style directives, terminators are not escapable.  */
2cca73
       if (c == '\\' && !pfile->state.angled_headers && *cur != '\n')
2cca73
-	cur++;
2cca73
+	{
2cca73
+	  if ((cur[0] == 'u' || cur[0] == 'U') && warn_bidi_p)
2cca73
+	    {
2cca73
+	      bidi::kind kind = get_bidi_ucn (cur + 1, cur[0] == 'U');
2cca73
+	      maybe_warn_bidi_on_char (pfile, cur, kind, /*ucn_p=*/true);
2cca73
+	    }
2cca73
+	  cur++;
2cca73
+	}
2cca73
       else if (c == terminator)
2cca73
-	break;
2cca73
+	{
2cca73
+	  if (warn_bidi_p)
2cca73
+	    maybe_warn_bidi_on_close (pfile, cur - 1);
2cca73
+	  break;
2cca73
+	}
2cca73
       else if (c == '\n')
2cca73
 	{
2cca73
 	  cur--;
2cca73
@@ -2030,6 +2415,11 @@ lex_string (cpp_reader *pfile, cpp_token *token, const uchar *base)
2cca73
 	}
2cca73
       else if (c == '\0')
2cca73
 	saw_NUL = true;
2cca73
+      else if (__builtin_expect (c == bidi::utf8_start, 0) && warn_bidi_p)
2cca73
+	{
2cca73
+	  bidi::kind kind = get_bidi_utf8 (cur - 1);
2cca73
+	  maybe_warn_bidi_on_char (pfile, cur - 1, kind, /*ucn_p=*/false);
2cca73
+	}
2cca73
     }
2cca73
 
2cca73
   if (saw_NUL && !pfile->state.skipping)