]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
gccrs: resolve: Add "break rust" Easter egg
authorSergey Bugaev <bugaevc@gmail.com>
Mon, 3 Apr 2023 15:58:43 +0000 (18:58 +0300)
committerArthur Cohen <arthur.cohen@embecosm.com>
Tue, 16 Jan 2024 17:34:09 +0000 (18:34 +0100)
When we encounter a "break rust" statement, emit a funny error message
and intentionally cause an ICE. This matches the corresponding Easter
egg in rustc. As a GNU extension, "break gcc" is also supported.

The conditions for this to happen are:
* The break expression must be literally "rust" or "gcc". For instance,
  "break (rust)" will not trigger the Easter egg.
* The name ("rust" or "gcc") must not be in scope; if it is, no error
  is emitted, and the compilation proceeds as usual. In other words,
  this only affects how GCC diagnoses programs that would fail to
  compile anyway.

Note that this is different from the conditions under which rustc emits
its ICE. For rustc, it matters whether or not the "break" is inside a
loop, and for us it matters whether or not the name resolves. The end
result should be the same anyway: valid programs continue to compile,
and typing in

fn main() {
    break rust;
}

triggers a funny ICE.

Closes https://github.com/Rust-GCC/gccrs/issues/1996

gcc/rust/ChangeLog:
* resolve/rust-ast-resolve-expr.cc: Add "break rust" Easter egg

gcc/testsuite/ChangeLog:
* lib/prune.exp (prune_ices):
Also prune "You have broken GCC Rust. This is a feature."
* rust/compile/break-rust1.rs: New test
* rust/compile/break-rust2.rs: New test
* rust/compile/break-rust3.rs: New test

Signed-off-by: Sergey Bugaev <bugaevc@gmail.com>
gcc/rust/resolve/rust-ast-resolve-expr.cc
gcc/testsuite/lib/prune.exp
gcc/testsuite/rust/compile/break-rust1.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/break-rust2.rs [new file with mode: 0644]
gcc/testsuite/rust/compile/break-rust3.rs [new file with mode: 0644]

index e5ee976213422780b02ee90080af3466c16b710a..6f45f459c4ef5faeb6672fd4dfeb7382ebafa1f7 100644 (file)
@@ -23,6 +23,7 @@
 #include "rust-ast-resolve-type.h"
 #include "rust-ast-resolve-pattern.h"
 #include "rust-ast-resolve-path.h"
+#include "diagnostic.h"
 
 namespace Rust {
 namespace Resolver {
@@ -105,6 +106,45 @@ ResolveExpr::visit (AST::AssignmentExpr &expr)
   VerifyAsignee::go (expr.get_left_expr ().get ());
 }
 
+/* The "break rust" Easter egg.
+
+   Backstory: once upon a time, there used to be a bug in rustc: it would ICE
+   during typechecking on a 'break' with an expression outside of a loop.  The
+   issue has been reported [0] and fixed [1], but in recognition of this, as a
+   special Easter egg, "break rust" was made to intentionally cause an ICE.
+
+   [0]: https://github.com/rust-lang/rust/issues/43162
+   [1]: https://github.com/rust-lang/rust/pull/43745
+
+   This was made in a way that does not break valid programs: namely, it only
+   happens when the 'break' is outside of a loop (so invalid anyway).
+
+   GCC Rust supports this essential feature as well, but in a slightly
+   different way.  Instead of delaying the error until type checking, we emit
+   it here in the resolution phase.  We, too, only do this to programs that
+   are already invalid: we only emit our funny ICE if the name "rust" (which
+   must be immediately inside a break-with-a-value expression) fails to
+   resolve.  Note that "break (rust)" does not trigger our ICE, only using
+   "break rust" directly does, and only if there's no "rust" in scope.  We do
+   this in the same way regardless of whether the "break" is outside of a loop
+   or inside one.
+
+   As a GNU extension, we also support "break gcc", much to the same effect,
+   subject to the same rules.  */
+
+/* The finalizer for our funny ICE.  This prints a custom message instead of
+   the default bug reporting instructions, as there is no bug to report.  */
+
+static void ATTRIBUTE_NORETURN
+funny_ice_finalizer (diagnostic_context *context,
+                    const diagnostic_info *diagnostic, diagnostic_t diag_kind)
+{
+  gcc_assert (diag_kind == DK_ICE_NOBT);
+  default_diagnostic_finalizer (context, diagnostic, diag_kind);
+  fnotice (stderr, "You have broken GCC Rust. This is a feature.\n");
+  exit (ICE_EXIT_CODE);
+}
+
 void
 ResolveExpr::visit (AST::IdentifierExpr &expr)
 {
@@ -120,6 +160,17 @@ ResolveExpr::visit (AST::IdentifierExpr &expr)
     {
       resolver->insert_resolved_type (expr.get_node_id (), resolved_node);
     }
+  else if (funny_error)
+    {
+      /* This was a "break rust" or "break gcc", and the identifier failed to
+        resolve.  Emit a funny ICE.  We set the finalizer to our custom one,
+        and use the lower-level emit_diagnostic () instead of the more common
+        internal_error_no_backtrace () in order to pass our locus.  */
+      diagnostic_finalizer (global_dc) = funny_ice_finalizer;
+      emit_diagnostic (DK_ICE_NOBT, expr.get_locus ().gcc_location (), -1,
+                      "are you trying to break %s? how dare you?",
+                      expr.as_string ().c_str ());
+    }
   else
     {
       rust_error_at (expr.get_locus (), "failed to find name: %s",
@@ -407,7 +458,24 @@ ResolveExpr::visit (AST::BreakExpr &expr)
     }
 
   if (expr.has_break_expr ())
-    ResolveExpr::go (expr.get_break_expr ().get (), prefix, canonical_prefix);
+    {
+      bool funny_error = false;
+      AST::Expr &break_expr = *expr.get_break_expr ().get ();
+      if (break_expr.get_ast_kind () == AST::Kind::IDENTIFIER)
+       {
+         /* This is a break with an expression, and the expression is just a
+            single identifier.  See if the identifier is either "rust" or
+            "gcc", in which case we have "break rust" or "break gcc", and so
+            may need to emit our funny error.  We cannot yet emit the error
+            here though, because the identifier may still be in scope, and
+            ICE'ing on valid programs would not be very funny.  */
+         std::string ident
+           = static_cast<AST::IdentifierExpr &> (break_expr).as_string ();
+         if (ident == "rust" || ident == "gcc")
+           funny_error = true;
+       }
+      ResolveExpr::go (&break_expr, prefix, canonical_prefix, funny_error);
+    }
 }
 
 void
index c46f30514a8387789ebd7ec0fad553076a867fed..f3d3c99fbcb8122917f9f8b4f845afe88a27e98e 100644 (file)
@@ -151,6 +151,7 @@ proc prune_file_path { text } {
 proc prune_ices { text } {
   regsub -all "(^|\n)\[^\n\]*: internal compiler error:.*\nSee \[^\n\]*" $text "" text
   regsub -all "(^|\n|')*Internal compiler error:.*\nSee \[^\n\]*" $text "" text
+  regsub -all "(^|\n)\[^\n\]*: internal compiler error:.*\nYou have broken GCC Rust. This is a feature." $text "" text
   return $text
 }
 
diff --git a/gcc/testsuite/rust/compile/break-rust1.rs b/gcc/testsuite/rust/compile/break-rust1.rs
new file mode 100644 (file)
index 0000000..65d64f9
--- /dev/null
@@ -0,0 +1,7 @@
+fn main() {
+    let rust = "crab";
+    let res = loop {
+        // { dg-warning "unused name" "" { target *-*-* } .-1 }
+        break rust;
+    };
+}
diff --git a/gcc/testsuite/rust/compile/break-rust2.rs b/gcc/testsuite/rust/compile/break-rust2.rs
new file mode 100644 (file)
index 0000000..d02589e
--- /dev/null
@@ -0,0 +1,4 @@
+fn main() {
+    break (rust);
+    // { dg-error "failed to find name: rust" "" { target *-*-* } .-1 }
+}
diff --git a/gcc/testsuite/rust/compile/break-rust3.rs b/gcc/testsuite/rust/compile/break-rust3.rs
new file mode 100644 (file)
index 0000000..b18666a
--- /dev/null
@@ -0,0 +1,4 @@
+fn main() {
+    break rust;
+    // { dg-ice "are you trying to break rust? how dare you?" }
+}