Skip to content

Commit

Permalink
Handle block scopes in compile-time binding check (#4379)
Browse files Browse the repository at this point in the history
This fixes a fuzzer-found crash when a compile-time binding occurs
inside a block (which is represented by an invalid inst ID).
  • Loading branch information
geoffromer authored Oct 7, 2024
1 parent 284b149 commit 6439f90
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 7 deletions.
20 changes: 14 additions & 6 deletions toolchain/check/handle_binding_pattern.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,20 @@ auto HandleParseNode(Context& context,
bool is_generic = true;
if (context.decl_introducer_state_stack().innermost().kind ==
Lex::TokenKind::Let) {
auto scope_inst = context.insts().Get(context.scope_stack().PeekInstId());
if (!scope_inst.Is<SemIR::InterfaceDecl>() &&
!scope_inst.Is<SemIR::FunctionDecl>()) {
context.TODO(node_id,
"`let` compile time binding outside function or interface");
is_generic = false;
// Disallow `let` outside of function and interface definitions.
// TODO: find a less brittle way of doing this. An invalid scope_inst_id
// can represent a block scope, but is also used for other kinds of scopes
// that aren't necessarily part of an interface or function decl.
auto scope_inst_id = context.scope_stack().PeekInstId();
if (scope_inst_id.is_valid()) {
auto scope_inst = context.insts().Get(scope_inst_id);
if (!scope_inst.Is<SemIR::InterfaceDecl>() &&
!scope_inst.Is<SemIR::FunctionDecl>()) {
context.TODO(
node_id,
"`let` compile time binding outside function or interface");
is_generic = false;
}
}
}

Expand Down
157 changes: 156 additions & 1 deletion toolchain/check/testdata/let/compile_time_bindings.carbon
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,18 @@ fn F() -> i32 {
return Zero;
}

// --- use_in_block.carbon

library "[[@TEST_NAME]]";

fn F() -> i32 {
if (true) {
let Zero:! i32 = 0;
return Zero;
}
return 1;
}

// --- fail_return_in_interface.carbon

library "[[@TEST_NAME]]";
Expand Down Expand Up @@ -131,11 +143,25 @@ library "[[@TEST_NAME]]";
// CHECK:STDERR: ^~~~~~~~
// CHECK:STDERR:
let T:! type = i32;
// CHECK:STDERR: fail_return_in_package_scope.carbon:[[@LINE+3]]:11: error: cannot evaluate type expression
// CHECK:STDERR: fail_return_in_package_scope.carbon:[[@LINE+4]]:11: error: cannot evaluate type expression
// CHECK:STDERR: fn F() -> T;
// CHECK:STDERR: ^
// CHECK:STDERR:
fn F() -> T;

// --- fail_use_in_impl.carbon

library "[[@TEST_NAME]]";

interface Empty {}

impl i32 as Empty {
// CHECK:STDERR: fail_use_in_impl.carbon:[[@LINE+3]]:7: error: semantics TODO: ``let` compile time binding outside function or interface`
// CHECK:STDERR: let Zero:! i32 = 0;
// CHECK:STDERR: ^~~~~~~~~~
let Zero:! i32 = 0;
}

// CHECK:STDOUT: --- fail_let_after.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
Expand Down Expand Up @@ -560,6 +586,70 @@ fn F() -> T;
// CHECK:STDOUT: return %Zero.ref
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- use_in_block.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %Int32.type: type = fn_type @Int32 [template]
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template]
// CHECK:STDOUT: %F.type: type = fn_type @F [template]
// CHECK:STDOUT: %F: %F.type = struct_value () [template]
// CHECK:STDOUT: %.2: bool = bool_literal true [template]
// CHECK:STDOUT: %.3: i32 = int_literal 0 [template]
// CHECK:STDOUT: %Zero: i32 = bind_symbolic_name Zero, 0 [symbolic]
// CHECK:STDOUT: %.4: i32 = int_literal 1 [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int32 = %import_ref
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/operators
// CHECK:STDOUT: import Core//prelude/types
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
// CHECK:STDOUT: import Core//prelude/operators/as
// CHECK:STDOUT: import Core//prelude/operators/bitwise
// CHECK:STDOUT: import Core//prelude/operators/comparison
// CHECK:STDOUT: import Core//prelude/types/bool
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .F = %F.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %F.decl: %F.type = fn_decl @F [template = constants.%F] {} {
// CHECK:STDOUT: %int.make_type_32.loc4: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc4_11.1: type = value_of_initializer %int.make_type_32.loc4 [template = i32]
// CHECK:STDOUT: %.loc4_11.2: type = converted %int.make_type_32.loc4, %.loc4_11.1 [template = i32]
// CHECK:STDOUT: %return: ref i32 = var <return slot>
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
// CHECK:STDOUT:
// CHECK:STDOUT: fn @F() -> i32 {
// CHECK:STDOUT: !entry:
// CHECK:STDOUT: %.loc5: bool = bool_literal true [template = constants.%.2]
// CHECK:STDOUT: if %.loc5 br !if.then else br !if.else
// CHECK:STDOUT:
// CHECK:STDOUT: !if.then:
// CHECK:STDOUT: %int.make_type_32.loc6: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc6_16.1: type = value_of_initializer %int.make_type_32.loc6 [template = i32]
// CHECK:STDOUT: %.loc6_16.2: type = converted %int.make_type_32.loc6, %.loc6_16.1 [template = i32]
// CHECK:STDOUT: %.loc6_22: i32 = int_literal 0 [template = constants.%.3]
// CHECK:STDOUT: %Zero: i32 = bind_symbolic_name Zero, 0, %.loc6_22 [symbolic = constants.%Zero]
// CHECK:STDOUT: %Zero.ref: i32 = name_ref Zero, %Zero [symbolic = constants.%Zero]
// CHECK:STDOUT: return %Zero.ref
// CHECK:STDOUT:
// CHECK:STDOUT: !if.else:
// CHECK:STDOUT: %.loc9: i32 = int_literal 1 [template = constants.%.4]
// CHECK:STDOUT: return %.loc9
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_return_in_interface.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
Expand Down Expand Up @@ -822,3 +912,68 @@ fn F() -> T;
// CHECK:STDOUT: return
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: --- fail_use_in_impl.carbon
// CHECK:STDOUT:
// CHECK:STDOUT: constants {
// CHECK:STDOUT: %Empty.type: type = interface_type @Empty [template]
// CHECK:STDOUT: %Self: %Empty.type = bind_symbolic_name Self, 0 [symbolic]
// CHECK:STDOUT: %Int32.type: type = fn_type @Int32 [template]
// CHECK:STDOUT: %.1: type = tuple_type () [template]
// CHECK:STDOUT: %Int32: %Int32.type = struct_value () [template]
// CHECK:STDOUT: %.2: i32 = int_literal 0 [template]
// CHECK:STDOUT: %.3: <witness> = interface_witness () [template]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: imports {
// CHECK:STDOUT: %Core: <namespace> = namespace file.%Core.import, [template] {
// CHECK:STDOUT: .Int32 = %import_ref
// CHECK:STDOUT: import Core//prelude
// CHECK:STDOUT: import Core//prelude/operators
// CHECK:STDOUT: import Core//prelude/types
// CHECK:STDOUT: import Core//prelude/operators/arithmetic
// CHECK:STDOUT: import Core//prelude/operators/as
// CHECK:STDOUT: import Core//prelude/operators/bitwise
// CHECK:STDOUT: import Core//prelude/operators/comparison
// CHECK:STDOUT: import Core//prelude/types/bool
// CHECK:STDOUT: }
// CHECK:STDOUT: %import_ref: %Int32.type = import_ref Core//prelude/types, inst+4, loaded [template = constants.%Int32]
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: file {
// CHECK:STDOUT: package: <namespace> = namespace [template] {
// CHECK:STDOUT: .Core = imports.%Core
// CHECK:STDOUT: .Empty = %Empty.decl
// CHECK:STDOUT: }
// CHECK:STDOUT: %Core.import = import Core
// CHECK:STDOUT: %Empty.decl: type = interface_decl @Empty [template = constants.%Empty.type] {} {}
// CHECK:STDOUT: impl_decl @impl [template] {} {
// CHECK:STDOUT: %int.make_type_32.loc6: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc6_6.1: type = value_of_initializer %int.make_type_32.loc6 [template = i32]
// CHECK:STDOUT: %.loc6_6.2: type = converted %int.make_type_32.loc6, %.loc6_6.1 [template = i32]
// CHECK:STDOUT: %Empty.ref: type = name_ref Empty, file.%Empty.decl [template = constants.%Empty.type]
// CHECK:STDOUT: }
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: interface @Empty {
// CHECK:STDOUT: %Self: %Empty.type = bind_symbolic_name Self, 0 [symbolic = constants.%Self]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Self = %Self
// CHECK:STDOUT: witness = ()
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: impl @impl: %.loc6_6.2 as %Empty.ref {
// CHECK:STDOUT: %int.make_type_32.loc10: init type = call constants.%Int32() [template = i32]
// CHECK:STDOUT: %.loc10_14.1: type = value_of_initializer %int.make_type_32.loc10 [template = i32]
// CHECK:STDOUT: %.loc10_14.2: type = converted %int.make_type_32.loc10, %.loc10_14.1 [template = i32]
// CHECK:STDOUT: %.loc10_20: i32 = int_literal 0 [template = constants.%.2]
// CHECK:STDOUT: %Zero: i32 = bind_name Zero, %.loc10_20
// CHECK:STDOUT: %.loc6_19: <witness> = interface_witness () [template = constants.%.3]
// CHECK:STDOUT:
// CHECK:STDOUT: !members:
// CHECK:STDOUT: .Zero = %Zero
// CHECK:STDOUT: witness = %.loc6_19
// CHECK:STDOUT: }
// CHECK:STDOUT:
// CHECK:STDOUT: fn @Int32() -> type = "int.make_type_32";
// CHECK:STDOUT:

0 comments on commit 6439f90

Please sign in to comment.