Skip to content

break rust 💥 #2086

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Apr 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion gcc/rust/ast/rust-ast.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ class ASTVisitor;
using AttrVec = std::vector<Attribute>;

// The available kinds of AST Nodes
enum Kind
enum class Kind
{
UNKNOWN,
MACRO_RULES_DEFINITION,
MACRO_INVOCATION,
IDENTIFIER,
};

class Visitable
Expand Down Expand Up @@ -1072,6 +1073,8 @@ class IdentifierExpr : public ExprWithoutBlock
outer_attrs = std::move (new_attrs);
}

Kind get_ast_kind () const override { return Kind::IDENTIFIER; }

protected:
// Clone method implementation
IdentifierExpr *clone_expr_without_block_impl () const final override
Expand Down
2 changes: 1 addition & 1 deletion gcc/rust/expand/rust-macro-builtins.cc
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ MacroBuiltin::concat_handler (Location invoc_locus, AST::MacroInvocData &invoc)
for (auto &expr : expanded_expr)
{
if (!expr->is_literal ()
&& expr->get_ast_kind () != AST::MACRO_INVOCATION)
&& expr->get_ast_kind () != AST::Kind::MACRO_INVOCATION)
{
has_error = true;
rust_error_at (expr->get_locus (), "expected a literal");
Expand Down
80 changes: 75 additions & 5 deletions gcc/rust/resolve/rust-ast-resolve-expr.cc
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@
#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 {

void
ResolveExpr::go (AST::Expr *expr, const CanonicalPath &prefix,
const CanonicalPath &canonical_prefix)
const CanonicalPath &canonical_prefix, bool funny_error)
{
ResolveExpr resolver (prefix, canonical_prefix);
ResolveExpr resolver (prefix, canonical_prefix, funny_error);
expr->accept_vis (resolver);
}

Expand Down Expand Up @@ -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, 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)
{
Expand All @@ -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",
Expand Down Expand Up @@ -376,7 +427,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
Expand Down Expand Up @@ -643,8 +711,10 @@ ResolveExpr::resolve_closure_param (AST::ClosureParam &param,
}

ResolveExpr::ResolveExpr (const CanonicalPath &prefix,
const CanonicalPath &canonical_prefix)
: ResolverBase (), prefix (prefix), canonical_prefix (canonical_prefix)
const CanonicalPath &canonical_prefix,
bool funny_error)
: ResolverBase (), prefix (prefix), canonical_prefix (canonical_prefix),
funny_error (funny_error)
{}

} // namespace Resolver
Expand Down
6 changes: 4 additions & 2 deletions gcc/rust/resolve/rust-ast-resolve-expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ class ResolveExpr : public ResolverBase

public:
static void go (AST::Expr *expr, const CanonicalPath &prefix,
const CanonicalPath &canonical_prefix);
const CanonicalPath &canonical_prefix,
bool funny_error = false);

void visit (AST::TupleIndexExpr &expr) override;
void visit (AST::TupleExpr &expr) override;
Expand Down Expand Up @@ -83,10 +84,11 @@ class ResolveExpr : public ResolverBase

private:
ResolveExpr (const CanonicalPath &prefix,
const CanonicalPath &canonical_prefix);
const CanonicalPath &canonical_prefix, bool funny_error);

const CanonicalPath &prefix;
const CanonicalPath &canonical_prefix;
bool funny_error;
};

} // namespace Resolver
Expand Down
1 change: 1 addition & 0 deletions gcc/testsuite/lib/prune.exp
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,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
}

Expand Down
7 changes: 7 additions & 0 deletions gcc/testsuite/rust/compile/break-rust1.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
let rust = "crab";
let res = loop {
// { dg-warning "unused name" "" { target *-*-* } .-1 }
break rust;
};
}
4 changes: 4 additions & 0 deletions gcc/testsuite/rust/compile/break-rust2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
break (rust);
// { dg-error "failed to find name: rust" "" { target *-*-* } .-1 }
}
4 changes: 4 additions & 0 deletions gcc/testsuite/rust/compile/break-rust3.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
break rust;
// { dg-ice "are you trying to break rust? how dare you?" }
}