diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index db65c14a7a5af99d3652b9fb5b2c86e9e8b53857..9cfd2a6bc4e00a590156ac28fb89256756ed2364 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -379,8 +379,8 @@ C ObjC C++ ObjC++ Warning Alias(Wbidi-chars=,any,none)
 ;
 
 Wbidi-chars=
-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)
--Wbidi-chars=[none|unpaired|any] Warn about UTF-8 bidirectional control characters.
+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) EnumSet
+-Wbidi-chars=[none|unpaired|any|ucn] Warn about UTF-8 bidirectional control characters.
 
 ; Required for these enum values.
 SourceInclude
@@ -390,13 +390,16 @@ Enum
 Name(cpp_bidirectional_level) Type(int) UnknownError(argument %qs to %<-Wbidi-chars%> not recognized)
 
 EnumValue
-Enum(cpp_bidirectional_level) String(none) Value(bidirectional_none)
+Enum(cpp_bidirectional_level) String(none) Value(bidirectional_none) Set(1)
 
 EnumValue
-Enum(cpp_bidirectional_level) String(unpaired) Value(bidirectional_unpaired)
+Enum(cpp_bidirectional_level) String(unpaired) Value(bidirectional_unpaired) Set(1)
 
 EnumValue
-Enum(cpp_bidirectional_level) String(any) Value(bidirectional_any)
+Enum(cpp_bidirectional_level) String(any) Value(bidirectional_any) Set(1)
+
+EnumValue
+Enum(cpp_bidirectional_level) String(ucn) Value(bidirectional_ucn) Set(2)
 
 Wbool-compare
 C ObjC C++ ObjC++ Var(warn_bool_compare) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 309f5e38a8599811aa79ed88ad861d3d71230723..9e588db4fce6f7bcbfda8a98e80910b4cacabf25 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -328,7 +328,7 @@ Objective-C and Objective-C++ Dialects}.
 -Warray-bounds  -Warray-bounds=@var{n}  -Warray-compare @gol
 -Wno-attributes  -Wattribute-alias=@var{n} -Wno-attribute-alias @gol
 -Wno-attribute-warning  @gol
--Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{]} @gol
+-Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{|}ucn@r{]} @gol
 -Wbool-compare  -Wbool-operation @gol
 -Wno-builtin-declaration-mismatch @gol
 -Wno-builtin-macro-redefined  -Wc90-c99-compat  -Wc99-c11-compat @gol
@@ -7803,7 +7803,7 @@ Attributes considered include @code{alloc_align}, @code{alloc_size},
 This is the default.  You can disable these warnings with either
 @option{-Wno-attribute-alias} or @option{-Wattribute-alias=0}.
 
-@item -Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{]}
+@item -Wbidi-chars=@r{[}none@r{|}unpaired@r{|}any@r{|}ucn@r{]}
 @opindex Wbidi-chars=
 @opindex Wbidi-chars
 @opindex Wno-bidi-chars
@@ -7820,6 +7820,10 @@ bidi contexts.  @option{-Wbidi-chars=none} turns the warning off.
 @option{-Wbidi-chars=any} warns about any use of bidirectional control
 characters.
 
+By default, this warning does not warn about UCNs.  It is, however, possible
+to turn on such checking by using @option{-Wbidi-chars=unpaired,ucn} or
+@option{-Wbidi-chars=any,ucn}.
+
 @item -Wbool-compare
 @opindex Wno-bool-compare
 @opindex Wbool-compare
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-10.c b/gcc/testsuite/c-c++-common/Wbidi-chars-10.c
index 3f851b69e658104caddeb027b396b91d1face28c..cdcdce2be089c4c09635d4423da9b3bf3e3fe4e0 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-10.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-10.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
 /* More nesting testing.  */
 
 /* RLE‫ LRI⁦ PDF‬ PDI⁩*/
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-11.c b/gcc/testsuite/c-c++-common/Wbidi-chars-11.c
index 270ce2368a95f994dc5521b5f0bbf0771826e982..ea83029d6b99718c7851d4ba2cb0178ef7fb9b40 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-11.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-11.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
 /* Test that we warn when mixing UCN and UTF-8.  */
 
 int LRE_‪_PDF_\u202c;
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-14.c b/gcc/testsuite/c-c++-common/Wbidi-chars-14.c
index ba5f75d9553cfefdd3fc9a1d998cfc75da302b0e..cb6b05efac1bc56af66686bf9809f111ff88bf6a 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-14.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-14.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
 /* Test PDI handling, which also pops any subsequent LREs, RLEs, LROs,
    or RLOs.  */
 
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-16.c b/gcc/testsuite/c-c++-common/Wbidi-chars-16.c
index baa0159861c099fc0dc0cac34f95f7cdd8150e28..eaf0ec9a77717632f0c8d6e2701c3fb01f0ac1a2 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-16.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-16.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=any" } */
+/* { dg-options "-Wbidi-chars=any,ucn" } */
 /* Test LTR/RTL chars.  */
 
 /* LTR<‎> */
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-17.c b/gcc/testsuite/c-c++-common/Wbidi-chars-17.c
index 07cb4321f964522f7ac44afec956b20e6928b149..341922146a711bdc9453f4c1af178869cd70b794 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-17.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-17.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
 /* Test LTR/RTL chars.  */
 
 /* LTR<‎> */
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-18.c b/gcc/testsuite/c-c++-common/Wbidi-chars-18.c
new file mode 100644
index 0000000000000000000000000000000000000000..ae586d5e08c55306d4971a79831b8fa805a3d215
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-18.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* By default, don't warn about UCNs.  */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-bogus "unpaired" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-19.c b/gcc/testsuite/c-c++-common/Wbidi-chars-19.c
new file mode 100644
index 0000000000000000000000000000000000000000..9985c3be7a58e9998b808b900a47b37da0fa199a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-19.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-20.c b/gcc/testsuite/c-c++-common/Wbidi-chars-20.c
new file mode 100644
index 0000000000000000000000000000000000000000..859f3d53779dcc6d9c99eb6cce94432fd50fd96f
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-20.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* { dg-options "-Wbidi-chars=any" } */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-bogus "U\\+202D" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-21.c b/gcc/testsuite/c-c++-common/Wbidi-chars-21.c
new file mode 100644
index 0000000000000000000000000000000000000000..2720b8a883ec35a0712f00c2b29b9b5257e00371
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-21.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* { dg-options "-Wbidi-chars=ucn,any" } */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-warning "U\\+202D" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-22.c b/gcc/testsuite/c-c++-common/Wbidi-chars-22.c
new file mode 100644
index 0000000000000000000000000000000000000000..f960e597c5938f51e40b44c1e6d9a8cc95e11ad7
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-22.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* { dg-options "-Wbidi-chars=none,ucn" } */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-bogus "" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-23.c b/gcc/testsuite/c-c++-common/Wbidi-chars-23.c
new file mode 100644
index 0000000000000000000000000000000000000000..7de0a11070aaf3ba49b5ee00a279cb2f3f7d09fa
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-23.c
@@ -0,0 +1,11 @@
+/* PR preprocessor/104030 */
+/* { dg-do compile } */
+/* { dg-options "-Wbidi-chars=ucn" } */
+
+const char *
+fn ()
+{
+  const char *aText = "\u202D" "abc";
+/* { dg-warning "unpaired" "" { target *-*-* } .-1 } */
+  return aText;
+}
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-4.c b/gcc/testsuite/c-c++-common/Wbidi-chars-4.c
index 639e5c62e8886515754c2ebf00198f40e526304e..d2f0739dae06bc1a7b5168a03326021b8fe3712a 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-4.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-4.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=any -Wno-multichar -Wno-overflow" } */
+/* { dg-options "-Wbidi-chars=any,ucn -Wno-multichar -Wno-overflow" } */
 /* Test all bidi chars in various contexts (identifiers, comments,
    string literals, character constants), both UCN and UTF-8.  The bidi
    chars here are properly terminated, except for the character constants.  */
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-5.c b/gcc/testsuite/c-c++-common/Wbidi-chars-5.c
index 68cb053144ba46a72f30a5252bf3bfdb35ab75fe..ad49498fe234e5bbb5f8e89be0aebb96e1d2eff5 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-5.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-5.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired -Wno-multichar -Wno-overflow" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn -Wno-multichar -Wno-overflow" } */
 /* Test all bidi chars in various contexts (identifiers, comments,
    string literals, character constants), both UCN and UTF-8.  The bidi
    chars here are properly terminated, except for the character constants.  */
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-6.c b/gcc/testsuite/c-c++-common/Wbidi-chars-6.c
index 0ce6fff2deed0643a2191bd2a1f952d38ab4d7c5..8c1c1b2a2705f222a5b1f4fc29642cdf95ef38f7 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-6.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-6.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=ucn,unpaired" } */
 /* Test nesting of bidi chars in various contexts.  */
 
 /* Terminated by the wrong char:  */
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-7.c b/gcc/testsuite/c-c++-common/Wbidi-chars-7.c
index d012d420ec04fe1755965ddd853d86da3746a84a..3270952a09a45c209834136af74634ab47792c65 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-7.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-7.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=any" } */
+/* { dg-options "-Wbidi-chars=any,ucn" } */
 /* Test we ignore UCNs in comments.  */
 
 // a b c \u202a 1 2 3
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-8.c b/gcc/testsuite/c-c++-common/Wbidi-chars-8.c
index 4f54c5092eca1296f8953d44bef37f37564d6bad..3983168c9f1e3a08ba88283692b8b0c81c280b66 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-8.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-8.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=any" } */
+/* { dg-options "-Wbidi-chars=any,ucn" } */
 /* Test \u vs \U.  */
 
 int a_\u202A;
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-9.c b/gcc/testsuite/c-c++-common/Wbidi-chars-9.c
index e2af1b1ca97f77d6048ad044c7b5ef54450ef831..0ddb0d931088c9207e7faa2332473f7dfb157f8d 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-9.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-9.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn" } */
 /* Test that we properly separate bidi contexts (comment/identifier/character
    constant/string literal).  */
 
diff --git a/gcc/testsuite/c-c++-common/Wbidi-chars-ranges.c b/gcc/testsuite/c-c++-common/Wbidi-chars-ranges.c
index 298750a2a64c881b447552f4b2f72330a2e8dc3b..0c71f306dbc73c5911e25075295ef43c536ecc2c 100644
--- a/gcc/testsuite/c-c++-common/Wbidi-chars-ranges.c
+++ b/gcc/testsuite/c-c++-common/Wbidi-chars-ranges.c
@@ -1,6 +1,6 @@
 /* PR preprocessor/103026 */
 /* { dg-do compile } */
-/* { dg-options "-Wbidi-chars=unpaired -fdiagnostics-show-caret" } */
+/* { dg-options "-Wbidi-chars=unpaired,ucn -fdiagnostics-show-caret" } */
 /* Verify that we escape and underline pertinent bidirectional
    control characters when quoting the source.  */
 
diff --git a/libcpp/include/cpplib.h b/libcpp/include/cpplib.h
index 940c79f98c12ab233b249e3da2fe32b0b72fa137..3eba6f74b5726c13489b11db9807060f4d37daf3 100644
--- a/libcpp/include/cpplib.h
+++ b/libcpp/include/cpplib.h
@@ -319,15 +319,16 @@ enum cpp_main_search
   CMS_system,  /* Search the system INCLUDE path.  */
 };
 
-/* The possible bidirectional control characters checking levels, from least
-   restrictive to most.  */
+/* The possible bidirectional control characters checking levels.  */
 enum cpp_bidirectional_level {
   /* No checking.  */
-  bidirectional_none,
+  bidirectional_none = 0,
   /* Only detect unpaired uses of bidirectional control characters.  */
-  bidirectional_unpaired,
+  bidirectional_unpaired = 1,
   /* Detect any use of bidirectional control characters.  */
-  bidirectional_any
+  bidirectional_any = 2,
+  /* Also warn about UCNs.  */
+  bidirectional_ucn = 4
 };
 
 /* This structure is nested inside struct cpp_reader, and
diff --git a/libcpp/internal.h b/libcpp/internal.h
index 364c41c8149cd9c7544e2b828fcad051fcbc0325..badfd1b40daa54d791105204be5d587a493d8dba 100644
--- a/libcpp/internal.h
+++ b/libcpp/internal.h
@@ -605,7 +605,8 @@ struct cpp_reader
      characters.  */
   bool warn_bidi_p () const
   {
-    return CPP_OPTION (this, cpp_warn_bidirectional) != bidirectional_none;
+    return (CPP_OPTION (this, cpp_warn_bidirectional)
+	    & (bidirectional_unpaired|bidirectional_any));
   }
 };
 
diff --git a/libcpp/lex.cc b/libcpp/lex.cc
index 4d736576cc1acf955eb057473270b851ae19c802..fb1dfabb7afef0d52fe4a42703370482e14cb037 100644
--- a/libcpp/lex.cc
+++ b/libcpp/lex.cc
@@ -1560,8 +1560,11 @@ class unpaired_bidi_rich_location : public rich_location
 static void
 maybe_warn_bidi_on_close (cpp_reader *pfile, const uchar *p)
 {
-  if (CPP_OPTION (pfile, cpp_warn_bidirectional) == bidirectional_unpaired
-      && bidi::vec.count () > 0)
+  const auto warn_bidi = CPP_OPTION (pfile, cpp_warn_bidirectional);
+  if (bidi::vec.count () > 0
+      && (warn_bidi & bidirectional_unpaired
+	  && (!bidi::current_ctx_ucn_p ()
+	      || (warn_bidi & bidirectional_ucn))))
     {
       const location_t loc
 	= linemap_position_for_column (pfile->line_table,
@@ -1597,7 +1600,7 @@ maybe_warn_bidi_on_char (cpp_reader *pfile, bidi::kind kind,
 
   const auto warn_bidi = CPP_OPTION (pfile, cpp_warn_bidirectional);
 
-  if (warn_bidi != bidirectional_none)
+  if (warn_bidi & (bidirectional_unpaired|bidirectional_any))
     {
       rich_location rich_loc (pfile->line_table, loc);
       rich_loc.set_escape_on_output (true);
@@ -1605,10 +1608,10 @@ maybe_warn_bidi_on_char (cpp_reader *pfile, bidi::kind kind,
       /* It seems excessive to warn about a PDI/PDF that is closing
 	 an opened context because we've already warned about the
 	 opening character.  Except warn when we have a UCN x UTF-8
-	 mismatch.  */
+	 mismatch, if UCN checking is enabled.  */
       if (kind == bidi::current_ctx ())
 	{
-	  if (warn_bidi == bidirectional_unpaired
+	  if (warn_bidi == (bidirectional_unpaired|bidirectional_ucn)
 	      && bidi::current_ctx_ucn_p () != ucn_p)
 	    {
 	      rich_loc.add_range (bidi::current_ctx_loc ());
@@ -1617,7 +1620,8 @@ maybe_warn_bidi_on_char (cpp_reader *pfile, bidi::kind kind,
 			      "a context by \"%s\"", bidi::to_str (kind));
 	    }
 	}
-      else if (warn_bidi == bidirectional_any)
+      else if (warn_bidi & bidirectional_any
+	       && (!ucn_p || (warn_bidi & bidirectional_ucn)))
 	{
 	  if (kind == bidi::kind::PDF || kind == bidi::kind::PDI)
 	    cpp_warning_at (pfile, CPP_W_BIDIRECTIONAL, &rich_loc,