From 81e53886a8c85ad6e779e5a6e9ce09d44e8a461c Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 31 May 2023 16:35:21 -0700 Subject: [PATCH] Fix crash on use of uninitialized array element, and improve unformed checking (#2862) Per #257, we should be treating unformedness as all-or-nothing, rather than being a per-field or per-array-element property. Previously we initialized an array with no explicit initializer as containing a sequence of uninitialized values, but that led to crashes when attempting to access those values, as the checks for reading an uninitialized value only expected values to be uninitialized at the top level. Also, we had existing tests that attempt to store to an element of an uninitialized array. We now detect that and treat it as UB during evaluation, rather than crashing due to trying to perform field access into an uninitialized value. Finally, many of these problems can be detected statically, but the resolve_unformed pass wasn't catching them because it missed a few expression and declaration forms. Support for those cases has been added too. This causes the pass to recurse more often, and in particular our existing recursion test started hitting a stack overflow after this, so resolve_unformed now uses `RunWithExtraStack`. In passing, remove the need to explicitly tell `RunWithExtraStack` the return type, and infer it as the return type of the callable instead. --- explorer/data/prelude.carbon | 5 +- explorer/interpreter/BUILD | 1 + explorer/interpreter/heap.cpp | 5 + explorer/interpreter/interpreter.cpp | 33 ----- explorer/interpreter/resolve_names.cpp | 12 +- explorer/interpreter/resolve_unformed.cpp | 124 ++++++++++++++---- explorer/interpreter/stack_space.h | 8 +- explorer/interpreter/type_checker.cpp | 3 +- .../parse_and_execute/parse_and_execute.cpp | 2 +- ...il_print_uninitalized_array_element.carbon | 2 +- ... fail_store_to_uninitialized_array.carbon} | 3 +- ...store_to_uninitialized_global_array.carbon | 16 +++ ...ric_choice_nested_in_template_class.carbon | 4 +- .../fail_store_to_uninitialized_class.carbon | 23 ++++ .../fail_array.carbon} | 10 +- .../unformed/fail_array_dynamic.carbon | 16 +++ .../testdata/unformed/fail_base_access.carbon | 19 +++ .../fail_compound_member_access.carbon | 17 +++ .../fail_control_flow_defer_to_dynamic.carbon | 17 +++ .../testdata/unformed/fail_if_cond.carbon | 13 ++ .../testdata/unformed/fail_if_else.carbon | 13 ++ .../testdata/unformed/fail_if_then.carbon | 13 ++ .../testdata/unformed/fail_in_class.carbon | 19 +++ .../fail_indirect_member_access.carbon | 17 +++ .../testdata/unformed/fail_intrinsic.carbon | 13 ++ 25 files changed, 319 insertions(+), 89 deletions(-) rename explorer/testdata/array/{uninitialized_local_array_access.carbon => fail_store_to_uninitialized_array.carbon} (72%) create mode 100644 explorer/testdata/array/fail_store_to_uninitialized_global_array.carbon create mode 100644 explorer/testdata/class/fail_store_to_uninitialized_class.carbon rename explorer/testdata/{array/uninitialized_global_array_access.carbon => unformed/fail_array.carbon} (65%) create mode 100644 explorer/testdata/unformed/fail_array_dynamic.carbon create mode 100644 explorer/testdata/unformed/fail_base_access.carbon create mode 100644 explorer/testdata/unformed/fail_compound_member_access.carbon create mode 100644 explorer/testdata/unformed/fail_control_flow_defer_to_dynamic.carbon create mode 100644 explorer/testdata/unformed/fail_if_cond.carbon create mode 100644 explorer/testdata/unformed/fail_if_else.carbon create mode 100644 explorer/testdata/unformed/fail_if_then.carbon create mode 100644 explorer/testdata/unformed/fail_in_class.carbon create mode 100644 explorer/testdata/unformed/fail_indirect_member_access.carbon create mode 100644 explorer/testdata/unformed/fail_intrinsic.carbon diff --git a/explorer/data/prelude.carbon b/explorer/data/prelude.carbon index d957ae6df9d9..5acd6e49fadd 100644 --- a/explorer/data/prelude.carbon +++ b/explorer/data/prelude.carbon @@ -712,9 +712,8 @@ class Optional(T:! type) { } } Assert(false, "Attempted to unwrap empty Optional"); - // TODO: Drop uninitialized variable & return when we can flag unreachable paths. - var y: T; - return y; + // TODO: Drop return when we can flag unreachable paths. + return self.Get(); } var element: OptionalElement(T); diff --git a/explorer/interpreter/BUILD b/explorer/interpreter/BUILD index 6847b363c283..83e52485c586 100644 --- a/explorer/interpreter/BUILD +++ b/explorer/interpreter/BUILD @@ -219,6 +219,7 @@ cc_library( "resolve_unformed.h", ], deps = [ + ":stack_space", "//common:check", "//explorer/ast", "//explorer/ast:static_scope", diff --git a/explorer/interpreter/heap.cpp b/explorer/interpreter/heap.cpp index 196694ced619..27df83ba37fd 100644 --- a/explorer/interpreter/heap.cpp +++ b/explorer/interpreter/heap.cpp @@ -38,6 +38,11 @@ auto Heap::Write(const Address& a, Nonnull v, SourceLocation source_loc) -> ErrorOr { CARBON_RETURN_IF_ERROR(this->CheckAlive(a.allocation_, source_loc)); if (states_[a.allocation_.index_] == ValueState::Uninitialized) { + if (!a.element_path_.IsEmpty()) { + return ProgramError(source_loc) + << "undefined behavior: store to subobject of uninitialized value " + << *values_[a.allocation_.index_]; + } states_[a.allocation_.index_] = ValueState::Alive; } CARBON_ASSIGN_OR_RETURN(values_[a.allocation_.index_], diff --git a/explorer/interpreter/interpreter.cpp b/explorer/interpreter/interpreter.cpp index 69a0b2d4b12a..20d151460705 100644 --- a/explorer/interpreter/interpreter.cpp +++ b/explorer/interpreter/interpreter.cpp @@ -1596,10 +1596,6 @@ auto Interpreter::StepExp() -> ErrorOr { *print_stream_ << llvm::formatv(format_string); break; case 1: { - if ((*args[1]).kind() == Value::Kind::UninitializedValue) { - return ProgramError(exp.source_loc()) - << "Printing uninitialized value"; - } *print_stream_ << llvm::formatv(format_string, cast(*args[1]).value()); break; @@ -2134,18 +2130,6 @@ auto Interpreter::StepStmt() -> ErrorOr { if (definition.has_init()) { CARBON_ASSIGN_OR_RETURN( v, Convert(act.results()[0], dest_type, stmt.source_loc())); - } else if (dest_type->kind() == Value::Kind::StaticArrayType) { - const auto& array = cast(dest_type); - CARBON_CHECK(array->has_size()); - const auto& element_type = array->element_type(); - const auto size = array->size(); - - std::vector> elements; - elements.reserve(size); - for (size_t i = 0; i < size; i++) { - elements.push_back(arena_->New(&element_type)); - } - v = arena_->New(Value::Kind::TupleValue, elements); } else { v = arena_->New(p); } @@ -2290,7 +2274,6 @@ auto Interpreter::StepDeclaration() -> ErrorOr { switch (decl.kind()) { case DeclarationKind::VariableDeclaration: { const auto& var_decl = cast(decl); - const auto* var_type = &var_decl.binding().static_type(); if (var_decl.has_initializer()) { if (act.pos() == 0) { return todo_.Spawn( @@ -2303,22 +2286,6 @@ auto Interpreter::StepDeclaration() -> ErrorOr { todo_.Initialize(&var_decl.binding(), v); return todo_.FinishAction(); } - } else if (var_type->kind() == Value::Kind::StaticArrayType) { - const auto& array = cast(var_type); - CARBON_CHECK(array->has_size()); - const auto& element_type = array->element_type(); - const auto size = array->size(); - - std::vector> elements; - elements.reserve(size); - for (size_t i = 0; i < size; i++) { - elements.push_back(arena_->New(&element_type)); - } - - Nonnull v = - arena_->New(Value::Kind::TupleValue, elements); - todo_.Initialize(&var_decl.binding(), v); - return todo_.FinishAction(); } else { Nonnull v = arena_->New(&var_decl.binding().value()); diff --git a/explorer/interpreter/resolve_names.cpp b/explorer/interpreter/resolve_names.cpp index e6945ab1f028..c62fce1fbd8a 100644 --- a/explorer/interpreter/resolve_names.cpp +++ b/explorer/interpreter/resolve_names.cpp @@ -276,7 +276,7 @@ auto NameResolver::AddExposedNames(const Declaration& declaration, auto NameResolver::ResolveNames(Expression& expression, const StaticScope& enclosing_scope) -> ErrorOr> { - return RunWithExtraStack>>( + return RunWithExtraStack( [&]() { return ResolveNamesImpl(expression, enclosing_scope); }); } @@ -463,7 +463,7 @@ auto NameResolver::ResolveNamesImpl(Expression& expression, auto NameResolver::ResolveNames(WhereClause& clause, const StaticScope& enclosing_scope) -> ErrorOr { - return RunWithExtraStack>( + return RunWithExtraStack( [&]() { return ResolveNamesImpl(clause, enclosing_scope); }); } @@ -499,7 +499,7 @@ auto NameResolver::ResolveNamesImpl(WhereClause& clause, auto NameResolver::ResolveNames(Pattern& pattern, StaticScope& enclosing_scope) -> ErrorOr { - return RunWithExtraStack>( + return RunWithExtraStack( [&]() { return ResolveNamesImpl(pattern, enclosing_scope); }); } @@ -560,7 +560,7 @@ auto NameResolver::ResolveNamesImpl(Pattern& pattern, auto NameResolver::ResolveNames(Statement& statement, StaticScope& enclosing_scope) -> ErrorOr { - return RunWithExtraStack>( + return RunWithExtraStack( [&]() { return ResolveNamesImpl(statement, enclosing_scope); }); } @@ -703,7 +703,7 @@ auto NameResolver::ResolveNames(Declaration& declaration, StaticScope& enclosing_scope, ResolveFunctionBodies bodies) -> ErrorOr { - return RunWithExtraStack>( + return RunWithExtraStack( [&]() { return ResolveNamesImpl(declaration, enclosing_scope, bodies); }); } @@ -922,7 +922,7 @@ auto NameResolver::ResolveNamesImpl(Declaration& declaration, } auto ResolveNames(AST& ast) -> ErrorOr { - return RunWithExtraStack>([&]() -> ErrorOr { + return RunWithExtraStack([&]() -> ErrorOr { NameResolver resolver; StaticScope file_scope; diff --git a/explorer/interpreter/resolve_unformed.cpp b/explorer/interpreter/resolve_unformed.cpp index 0b048f10d742..58610fd21001 100644 --- a/explorer/interpreter/resolve_unformed.cpp +++ b/explorer/interpreter/resolve_unformed.cpp @@ -11,6 +11,7 @@ #include "explorer/ast/expression.h" #include "explorer/ast/pattern.h" #include "explorer/common/nonnull.h" +#include "explorer/interpreter/stack_space.h" using llvm::cast; @@ -54,22 +55,35 @@ auto FlowFacts::TakeAction(Nonnull node, ActionType action, return Success(); } -// Traverses the sub-AST rooted at the given node, resolving the formed/unformed -// states of local variables within it and updating the flow facts. -static auto ResolveUnformed(Nonnull expression, - FlowFacts& flow_facts, FlowFacts::ActionType action) +static auto ResolveUnformedImpl(Nonnull expression, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr; -static auto ResolveUnformed(Nonnull pattern, - FlowFacts& flow_facts, FlowFacts::ActionType action) +static auto ResolveUnformedImpl(Nonnull pattern, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr; -static auto ResolveUnformed(Nonnull statement, - FlowFacts& flow_facts, FlowFacts::ActionType action) +static auto ResolveUnformedImpl(Nonnull statement, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr; -static auto ResolveUnformed(Nonnull declaration) +static auto ResolveUnformedImpl(Nonnull expression, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr; -static auto ResolveUnformed(Nonnull expression, - FlowFacts& flow_facts, FlowFacts::ActionType action) +// Traverses the sub-AST rooted at the given node, resolving the formed/unformed +// states of local variables within it and updating the flow facts. +template +static auto ResolveUnformed(Nonnull expression, FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr { + return RunWithExtraStack( + [&] { return ResolveUnformedImpl(expression, flow_facts, action); }); +} + +static auto ResolveUnformedImpl(Nonnull expression, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr { switch (expression->kind()) { case ExpressionKind::IdentifierExpression: { @@ -85,6 +99,12 @@ static auto ResolveUnformed(Nonnull expression, ResolveUnformed(&call.argument(), flow_facts, action)); break; } + case ExpressionKind::IntrinsicExpression: { + const auto& intrin = cast(*expression); + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&intrin.args(), flow_facts, action)); + break; + } case ExpressionKind::TupleLiteral: for (Nonnull field : cast(*expression).fields()) { @@ -100,6 +120,9 @@ static auto ResolveUnformed(Nonnull expression, // When a variable is taken address of, defer the unformed check to // runtime. A more sound analysis can be implemented when a // points-to analysis is available. + // TODO: This isn't enough to permit &x.y or &x[i] when x is + // uninitialized, because x.y and x[i] both require x to be + // initialized. ResolveUnformed(opt_exp.arguments().front(), flow_facts, FlowFacts::ActionType::Form)); } else { @@ -117,15 +140,35 @@ static auto ResolveUnformed(Nonnull expression, } break; case ExpressionKind::SimpleMemberAccessExpression: - CARBON_RETURN_IF_ERROR(ResolveUnformed( - &cast(*expression).object(), flow_facts, - FlowFacts::ActionType::Check)); + case ExpressionKind::CompoundMemberAccessExpression: + case ExpressionKind::BaseAccessExpression: + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&cast(*expression).object(), + flow_facts, FlowFacts::ActionType::Check)); break; case ExpressionKind::BuiltinConvertExpression: CARBON_RETURN_IF_ERROR(ResolveUnformed( cast(*expression).source_expression(), flow_facts, FlowFacts::ActionType::Check)); break; + case ExpressionKind::IndexExpression: + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&cast(*expression).object(), + flow_facts, FlowFacts::ActionType::Check)); + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&cast(*expression).offset(), + flow_facts, FlowFacts::ActionType::Check)); + break; + case ExpressionKind::IfExpression: { + const auto& if_exp = cast(*expression); + CARBON_RETURN_IF_ERROR(ResolveUnformed(&if_exp.condition(), flow_facts, + FlowFacts::ActionType::Check)); + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&if_exp.then_expression(), flow_facts, action)); + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&if_exp.else_expression(), flow_facts, action)); + break; + } case ExpressionKind::DotSelfExpression: case ExpressionKind::IntLiteral: case ExpressionKind::BoolLiteral: @@ -135,13 +178,8 @@ static auto ResolveUnformed(Nonnull expression, case ExpressionKind::StringTypeLiteral: case ExpressionKind::TypeTypeLiteral: case ExpressionKind::ValueLiteral: - case ExpressionKind::IndexExpression: - case ExpressionKind::CompoundMemberAccessExpression: - case ExpressionKind::BaseAccessExpression: - case ExpressionKind::IfExpression: case ExpressionKind::WhereExpression: case ExpressionKind::StructTypeLiteral: - case ExpressionKind::IntrinsicExpression: case ExpressionKind::UnimplementedExpression: case ExpressionKind::FunctionTypeLiteral: case ExpressionKind::ArrayTypeLiteral: @@ -150,8 +188,9 @@ static auto ResolveUnformed(Nonnull expression, return Success(); } -static auto ResolveUnformed(Nonnull pattern, - FlowFacts& flow_facts, FlowFacts::ActionType action) +static auto ResolveUnformedImpl(Nonnull pattern, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr { switch (pattern->kind()) { case PatternKind::BindingPattern: { @@ -178,8 +217,9 @@ static auto ResolveUnformed(Nonnull pattern, return Success(); } -static auto ResolveUnformed(Nonnull statement, - FlowFacts& flow_facts, FlowFacts::ActionType action) +static auto ResolveUnformedImpl(Nonnull statement, + FlowFacts& flow_facts, + FlowFacts::ActionType action) -> ErrorOr { switch (statement->kind()) { case StatementKind::Block: { @@ -280,15 +320,36 @@ static auto ResolveUnformed(Nonnull statement, } break; } + case StatementKind::For: { + const auto& for_stmt = cast(*statement); + CARBON_RETURN_IF_ERROR(ResolveUnformed( + &for_stmt.loop_target(), flow_facts, FlowFacts::ActionType::Check)); + CARBON_RETURN_IF_ERROR( + ResolveUnformed(&for_stmt.body(), flow_facts, action)); + break; + } case StatementKind::Break: case StatementKind::Continue: - case StatementKind::For: // do nothing break; } return Success(); } +static auto ResolveUnformed(Nonnull declaration) + -> ErrorOr; + +static auto ResolveUnformed( + llvm::ArrayRef> declarations) + -> ErrorOr { + return RunWithExtraStack([declarations]() -> ErrorOr { + for (Nonnull declaration : declarations) { + CARBON_RETURN_IF_ERROR(ResolveUnformed(declaration)); + } + return Success(); + }); +} + static auto ResolveUnformed(Nonnull declaration) -> ErrorOr { switch (declaration->kind()) { @@ -306,12 +367,7 @@ static auto ResolveUnformed(Nonnull declaration) break; } case DeclarationKind::NamespaceDeclaration: - case DeclarationKind::ClassDeclaration: case DeclarationKind::MixDeclaration: - case DeclarationKind::MixinDeclaration: - case DeclarationKind::InterfaceDeclaration: - case DeclarationKind::ConstraintDeclaration: - case DeclarationKind::ImplDeclaration: case DeclarationKind::MatchFirstDeclaration: case DeclarationKind::ChoiceDeclaration: case DeclarationKind::VariableDeclaration: @@ -322,6 +378,16 @@ static auto ResolveUnformed(Nonnull declaration) case DeclarationKind::AliasDeclaration: // do nothing break; + case DeclarationKind::ClassDeclaration: + return ResolveUnformed(cast(declaration)->members()); + case DeclarationKind::MixinDeclaration: + return ResolveUnformed(cast(declaration)->members()); + case DeclarationKind::InterfaceDeclaration: + case DeclarationKind::ConstraintDeclaration: + return ResolveUnformed( + cast(declaration)->members()); + case DeclarationKind::ImplDeclaration: + return ResolveUnformed(cast(declaration)->members()); } return Success(); } diff --git a/explorer/interpreter/stack_space.h b/explorer/interpreter/stack_space.h index 0d936fd76dd0..8dd224d10a07 100644 --- a/explorer/interpreter/stack_space.h +++ b/explorer/interpreter/stack_space.h @@ -27,11 +27,13 @@ auto RunWithExtraStackHelper(llvm::function_ref fn) -> void; // create the current thread. // // Usage: -// return RunWithExtraStack([&]() -> ReturnType { +// return RunWithExtraStack([&]() -> ReturnType { // // }); -template -auto RunWithExtraStack(llvm::function_ref fn) -> ReturnType { +template +auto RunWithExtraStack(Fn fn) -> decltype(fn()) { + using ReturnType = decltype(fn()); + static_assert(!std::is_reference_v); if (Internal::IsStackSpaceNearlyExhausted()) { std::optional result; Internal::RunWithExtraStackHelper([&] { result = fn(); }); diff --git a/explorer/interpreter/type_checker.cpp b/explorer/interpreter/type_checker.cpp index f295d74a0f86..951938f9851c 100644 --- a/explorer/interpreter/type_checker.cpp +++ b/explorer/interpreter/type_checker.cpp @@ -2742,8 +2742,7 @@ auto TypeChecker::CheckAddrMeAccess( auto TypeChecker::TypeCheckExp(Nonnull e, const ImplScope& impl_scope) -> ErrorOr { - return RunWithExtraStack>( - [&]() { return TypeCheckExpImpl(e, impl_scope); }); + return RunWithExtraStack([&]() { return TypeCheckExpImpl(e, impl_scope); }); } // NOLINTNEXTLINE(readability-function-size) diff --git a/explorer/parse_and_execute/parse_and_execute.cpp b/explorer/parse_and_execute/parse_and_execute.cpp index 6ffdeba7e0d4..06f52d40b9a0 100644 --- a/explorer/parse_and_execute/parse_and_execute.cpp +++ b/explorer/parse_and_execute/parse_and_execute.cpp @@ -40,7 +40,7 @@ static auto ParseAndExecuteHelper(std::function(Arena*)> parse, Nonnull trace_stream, Nonnull print_stream) -> ErrorOr { - return RunWithExtraStack>([&]() -> ErrorOr { + return RunWithExtraStack([&]() -> ErrorOr { Arena arena; auto cursor = std::chrono::steady_clock::now(); diff --git a/explorer/testdata/array/fail_print_uninitalized_array_element.carbon b/explorer/testdata/array/fail_print_uninitalized_array_element.carbon index 1150cf488196..da61deb605ae 100644 --- a/explorer/testdata/array/fail_print_uninitalized_array_element.carbon +++ b/explorer/testdata/array/fail_print_uninitalized_array_element.carbon @@ -8,7 +8,7 @@ package ExplorerTest impl; fn Main() -> i32 { var my_array : [i32; 1]; - // CHECK:STDERR: RUNTIME ERROR: fail_print_uninitalized_array_element.carbon:[[@LINE+1]]: Printing uninitialized value + // CHECK:STDERR: COMPILATION ERROR: fail_print_uninitalized_array_element.carbon:[[@LINE+1]]: use of uninitialized variable my_array Print("{0}", my_array[0]); return 0; } diff --git a/explorer/testdata/array/uninitialized_local_array_access.carbon b/explorer/testdata/array/fail_store_to_uninitialized_array.carbon similarity index 72% rename from explorer/testdata/array/uninitialized_local_array_access.carbon rename to explorer/testdata/array/fail_store_to_uninitialized_array.carbon index ec2f93a4558f..fafcf5d37787 100644 --- a/explorer/testdata/array/uninitialized_local_array_access.carbon +++ b/explorer/testdata/array/fail_store_to_uninitialized_array.carbon @@ -3,13 +3,12 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // AUTOUPDATE -// CHECK:STDOUT: 100 -// CHECK:STDOUT: result: 0 package ExplorerTest api; fn Main() -> i32 { var my_array : [i32; 1]; + // CHECK:STDERR: COMPILATION ERROR: fail_store_to_uninitialized_array.carbon:[[@LINE+1]]: use of uninitialized variable my_array my_array[0] = 100; Print("{0}", my_array[0]); return 0; diff --git a/explorer/testdata/array/fail_store_to_uninitialized_global_array.carbon b/explorer/testdata/array/fail_store_to_uninitialized_global_array.carbon new file mode 100644 index 000000000000..a6684f71480b --- /dev/null +++ b/explorer/testdata/array/fail_store_to_uninitialized_global_array.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +var my_array : [i32; 1]; + +fn Main() -> i32 { + // CHECK:STDERR: RUNTIME ERROR: fail_store_to_uninitialized_global_array.carbon:[[@LINE+1]]: undefined behavior: store to subobject of uninitialized value Uninit> + my_array[0] = 100; + Print("{0}", my_array[0]); + return 0; +} diff --git a/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon b/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon index d52d6c054129..031a7f3573a0 100644 --- a/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon +++ b/explorer/testdata/choice/generic_choice_nested_in_template_class.carbon @@ -31,14 +31,14 @@ class MyOptional(T:! type){ } fn get[self: Self] () -> T { - var y: T; var x: MyOptionalElement(T) = self.element; match(x){ case MyOptionalElement(T).Element( var x: T ) =>{ return x; } } - return y; + // TODO: Mark this as unreachable somehow. + return get(); } var element: MyOptionalElement(T); diff --git a/explorer/testdata/class/fail_store_to_uninitialized_class.carbon b/explorer/testdata/class/fail_store_to_uninitialized_class.carbon new file mode 100644 index 000000000000..e8fee33057c2 --- /dev/null +++ b/explorer/testdata/class/fail_store_to_uninitialized_class.carbon @@ -0,0 +1,23 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +class Point { + var x: i32; + var y: i32; +} + +fn Main() -> i32 { + var p: Point; + if (1 == 0) { + p = {.x = 0, .y = 0}; + } + // CHECK:STDERR: RUNTIME ERROR: fail_store_to_uninitialized_class.carbon:[[@LINE+1]]: undefined behavior: store to subobject of uninitialized value Uninit> + p.x = 1; + p.y = 2; + return p.x; +} diff --git a/explorer/testdata/array/uninitialized_global_array_access.carbon b/explorer/testdata/unformed/fail_array.carbon similarity index 65% rename from explorer/testdata/array/uninitialized_global_array_access.carbon rename to explorer/testdata/unformed/fail_array.carbon index 9ba45db5b2ae..f0efb8b74d9d 100644 --- a/explorer/testdata/array/uninitialized_global_array_access.carbon +++ b/explorer/testdata/unformed/fail_array.carbon @@ -3,15 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // // AUTOUPDATE -// CHECK:STDOUT: 100 -// CHECK:STDOUT: result: 0 package ExplorerTest api; -var my_array : [i32; 1]; - fn Main() -> i32 { - my_array[0] = 100; - Print("{0}", my_array[0]); - return 0; + var v: [i32; 2]; + // CHECK:STDERR: COMPILATION ERROR: fail_array.carbon:[[@LINE+1]]: use of uninitialized variable v + return v[0]; } diff --git a/explorer/testdata/unformed/fail_array_dynamic.carbon b/explorer/testdata/unformed/fail_array_dynamic.carbon new file mode 100644 index 000000000000..d12bd732b88f --- /dev/null +++ b/explorer/testdata/unformed/fail_array_dynamic.carbon @@ -0,0 +1,16 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var v: [i32; 2]; + if (0 == 1) { + v = (1, 2); + } + // CHECK:STDERR: RUNTIME ERROR: fail_array_dynamic.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit> + return v[0]; +} diff --git a/explorer/testdata/unformed/fail_base_access.carbon b/explorer/testdata/unformed/fail_base_access.carbon new file mode 100644 index 000000000000..13e2ca3405f7 --- /dev/null +++ b/explorer/testdata/unformed/fail_base_access.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +base class B { + var n: i32; +} + +class D extends B {} + +fn Main() -> i32 { + var d: D; + // CHECK:STDERR: COMPILATION ERROR: fail_base_access.carbon:[[@LINE+1]]: use of uninitialized variable d + return d.n; +} diff --git a/explorer/testdata/unformed/fail_compound_member_access.carbon b/explorer/testdata/unformed/fail_compound_member_access.carbon new file mode 100644 index 000000000000..4e6cc123f2e5 --- /dev/null +++ b/explorer/testdata/unformed/fail_compound_member_access.carbon @@ -0,0 +1,17 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +class C { + var n: i32; +} + +fn Main() -> i32 { + var c: C; + // CHECK:STDERR: COMPILATION ERROR: fail_compound_member_access.carbon:[[@LINE+1]]: use of uninitialized variable c + return c.(C.n); +} diff --git a/explorer/testdata/unformed/fail_control_flow_defer_to_dynamic.carbon b/explorer/testdata/unformed/fail_control_flow_defer_to_dynamic.carbon new file mode 100644 index 000000000000..7f0491e0e00f --- /dev/null +++ b/explorer/testdata/unformed/fail_control_flow_defer_to_dynamic.carbon @@ -0,0 +1,17 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var x: i32; + if (0 == 1) { + x = 0; + } + // Static analysis thinks `x` may be formed, defer the check to run-time. + // CHECK:STDERR: RUNTIME ERROR: fail_control_flow_defer_to_dynamic.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit> + return x; +} diff --git a/explorer/testdata/unformed/fail_if_cond.carbon b/explorer/testdata/unformed/fail_if_cond.carbon new file mode 100644 index 000000000000..07d735cbeb78 --- /dev/null +++ b/explorer/testdata/unformed/fail_if_cond.carbon @@ -0,0 +1,13 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var v: bool; + // CHECK:STDERR: COMPILATION ERROR: fail_if_cond.carbon:[[@LINE+1]]: use of uninitialized variable v + return if v then 1 else 2; +} diff --git a/explorer/testdata/unformed/fail_if_else.carbon b/explorer/testdata/unformed/fail_if_else.carbon new file mode 100644 index 000000000000..b3f7e4639375 --- /dev/null +++ b/explorer/testdata/unformed/fail_if_else.carbon @@ -0,0 +1,13 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var v: i32; + // CHECK:STDERR: COMPILATION ERROR: fail_if_else.carbon:[[@LINE+1]]: use of uninitialized variable v + return if false then 1 else v; +} diff --git a/explorer/testdata/unformed/fail_if_then.carbon b/explorer/testdata/unformed/fail_if_then.carbon new file mode 100644 index 000000000000..539a2a74864f --- /dev/null +++ b/explorer/testdata/unformed/fail_if_then.carbon @@ -0,0 +1,13 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var v: i32; + // CHECK:STDERR: COMPILATION ERROR: fail_if_then.carbon:[[@LINE+1]]: use of uninitialized variable v + return if true then v else 2; +} diff --git a/explorer/testdata/unformed/fail_in_class.carbon b/explorer/testdata/unformed/fail_in_class.carbon new file mode 100644 index 000000000000..642c5f4f98b2 --- /dev/null +++ b/explorer/testdata/unformed/fail_in_class.carbon @@ -0,0 +1,19 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +class C { + fn F() -> i32 { + var n: i32; + // CHECK:STDERR: COMPILATION ERROR: fail_in_class.carbon:[[@LINE+1]]: use of uninitialized variable n + return n; + } +} + +fn Main() -> i32 { + return C.F(); +} diff --git a/explorer/testdata/unformed/fail_indirect_member_access.carbon b/explorer/testdata/unformed/fail_indirect_member_access.carbon new file mode 100644 index 000000000000..b7b57454bab6 --- /dev/null +++ b/explorer/testdata/unformed/fail_indirect_member_access.carbon @@ -0,0 +1,17 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var pt: {.x: i32, .y: i32}; + // TODO: Without this, we don't allow taking the address of `pt.x`. + // That's probably too restrictive. + if (1 == 0) { pt = {.x = 1, .y = 2}; } + var p: i32* = &pt.x; + // CHECK:STDERR: RUNTIME ERROR: fail_indirect_member_access.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit> + return *p; +} diff --git a/explorer/testdata/unformed/fail_intrinsic.carbon b/explorer/testdata/unformed/fail_intrinsic.carbon new file mode 100644 index 000000000000..52ef6cfc6b7a --- /dev/null +++ b/explorer/testdata/unformed/fail_intrinsic.carbon @@ -0,0 +1,13 @@ +// Part of the Carbon Language project, under the Apache License v2.0 with LLVM +// Exceptions. See /LICENSE for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +// AUTOUPDATE + +package ExplorerTest api; + +fn Main() -> i32 { + var v: i32; + // CHECK:STDERR: COMPILATION ERROR: fail_intrinsic.carbon:[[@LINE+1]]: use of uninitialized variable v + return if __intrinsic_int_eq(v, v) then 1 else 2; +}