Skip to content

Commit 4dc555b

Browse files
committed
resolve: Add "break rust" Easter egg
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 #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 <[email protected]>
1 parent e19179b commit 4dc555b

File tree

5 files changed

+86
-1
lines changed

5 files changed

+86
-1
lines changed

gcc/rust/resolve/rust-ast-resolve-expr.cc

+70-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
#include "rust-ast-resolve-type.h"
2424
#include "rust-ast-resolve-pattern.h"
2525
#include "rust-ast-resolve-path.h"
26+
#include "diagnostic.h"
2627

2728
namespace Rust {
2829
namespace Resolver {
@@ -105,6 +106,46 @@ ResolveExpr::visit (AST::AssignmentExpr &expr)
105106
VerifyAsignee::go (expr.get_left_expr ().get ());
106107
}
107108

109+
/* The "break rust" Easter egg.
110+
111+
Backstory: once upon a time, there used to be a bug in rustc: it would ICE
112+
during typechecking on a 'break' with an expression outside of a loop. The
113+
issue has been reported [0] and fixed [1], but in recognition of this, as a
114+
special Easter egg, "break rust" was made to intentionally cause an ICE.
115+
116+
[0]: https://github.com/rust-lang/rust/issues/43162
117+
[1]: https://github.com/rust-lang/rust/pull/43745
118+
119+
This was made in a way that does not break valid programs: namely, it only
120+
happens when the 'break' is outside of a loop (so invalid anyway).
121+
122+
GCC Rust supports this essential feature as well, but in a slightly
123+
different way. Instead of delaying the error until type checking, we emit
124+
it here in the resolution phase. We, too, only do this to programs that
125+
are already invalid: we only emit our funny ICE if the name "rust" (which
126+
must be immediately inside a break-with-a-value expression) fails to
127+
resolve. Note that "break (rust)" does not trigger our ICE, only using
128+
"break rust" directly does, and only if there's no "rust" in scope. We do
129+
this in the same way regardless of whether the "break" is outside of a loop
130+
or inside one.
131+
132+
As a GNU extension, we also support "break gcc", much to the same effect,
133+
subject to the same rules. */
134+
135+
136+
/* The finalizer for our funny ICE. This prints a custom message instead of
137+
the default bug reporting instructions, as there is no bug to report. */
138+
139+
static void ATTRIBUTE_NORETURN
140+
funny_ice_finalizer (diagnostic_context *context, diagnostic_info *diagnostic,
141+
diagnostic_t diag_kind)
142+
{
143+
gcc_assert (diag_kind == DK_ICE_NOBT);
144+
default_diagnostic_finalizer (context, diagnostic, diag_kind);
145+
fnotice (stderr, "You have broken GCC Rust. This is a feature.\n");
146+
exit (ICE_EXIT_CODE);
147+
}
148+
108149
void
109150
ResolveExpr::visit (AST::IdentifierExpr &expr)
110151
{
@@ -120,6 +161,17 @@ ResolveExpr::visit (AST::IdentifierExpr &expr)
120161
{
121162
resolver->insert_resolved_type (expr.get_node_id (), resolved_node);
122163
}
164+
else if (funny_error)
165+
{
166+
/* This was a "break rust" or "break gcc", and the identifier failed to
167+
resolve. Emit a funny ICE. We set the finalizer to our custom one,
168+
and use the lower-level emit_diagnostic () instead of the more common
169+
internal_error_no_backtrace () in order to pass our locus. */
170+
diagnostic_finalizer (global_dc) = funny_ice_finalizer;
171+
emit_diagnostic (DK_ICE_NOBT, expr.get_locus ().gcc_location (), -1,
172+
"are you trying to break %s? how dare you?",
173+
expr.as_string ().c_str ());
174+
}
123175
else
124176
{
125177
rust_error_at (expr.get_locus (), "failed to find name: %s",
@@ -376,7 +428,24 @@ ResolveExpr::visit (AST::BreakExpr &expr)
376428
}
377429

378430
if (expr.has_break_expr ())
379-
ResolveExpr::go (expr.get_break_expr ().get (), prefix, canonical_prefix);
431+
{
432+
bool funny_error = false;
433+
AST::Expr &break_expr = *expr.get_break_expr ().get ();
434+
if (break_expr.get_ast_kind () == AST::Kind::IDENTIFIER)
435+
{
436+
/* This is a break with an expression, and the expression is just a
437+
single identifier. See if the identifier is either "rust" or
438+
"gcc", in which case we have "break rust" or "break gcc", and so
439+
may need to emit our funny error. We cannot yet emit the error
440+
here though, because the identifier may still be in scope, and
441+
ICE'ing on valid programs would not be very funny. */
442+
std::string ident
443+
= static_cast<AST::IdentifierExpr &> (break_expr).as_string ();
444+
if (ident == "rust" || ident == "gcc")
445+
funny_error = true;
446+
}
447+
ResolveExpr::go (&break_expr, prefix, canonical_prefix, funny_error);
448+
}
380449
}
381450

382451
void

gcc/testsuite/lib/prune.exp

+1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ proc prune_file_path { text } {
158158
proc prune_ices { text } {
159159
regsub -all "(^|\n)\[^\n\]*: internal compiler error:.*\nSee \[^\n\]*" $text "" text
160160
regsub -all "(^|\n|')*Internal compiler error:.*\nSee \[^\n\]*" $text "" text
161+
regsub -all "(^|\n)\[^\n\]*: internal compiler error:.*\nYou have broken GCC Rust. This is a feature." $text "" text
161162
return $text
162163
}
163164

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
let rust = "crab";
3+
let res = loop {
4+
// { dg-warning "unused name" "" { target *-*-* } .-1 }
5+
break rust;
6+
};
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() {
2+
break (rust);
3+
// { dg-error "failed to find name: rust" "" { target *-*-* } .-1 }
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
fn main() {
2+
break rust;
3+
// { dg-ice "are you trying to break rust? how dare you?" }
4+
}

0 commit comments

Comments
 (0)