Преглед изворни кода

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.
Richard Smith пре 2 година
родитељ
комит
81e53886a8
25 измењених фајлова са 319 додато и 89 уклоњено
  1. 2 3
      explorer/data/prelude.carbon
  2. 1 0
      explorer/interpreter/BUILD
  3. 5 0
      explorer/interpreter/heap.cpp
  4. 0 33
      explorer/interpreter/interpreter.cpp
  5. 6 6
      explorer/interpreter/resolve_names.cpp
  6. 95 29
      explorer/interpreter/resolve_unformed.cpp
  7. 5 3
      explorer/interpreter/stack_space.h
  8. 1 2
      explorer/interpreter/type_checker.cpp
  9. 1 1
      explorer/parse_and_execute/parse_and_execute.cpp
  10. 1 1
      explorer/testdata/array/fail_print_uninitalized_array_element.carbon
  11. 1 2
      explorer/testdata/array/fail_store_to_uninitialized_array.carbon
  12. 16 0
      explorer/testdata/array/fail_store_to_uninitialized_global_array.carbon
  13. 2 2
      explorer/testdata/choice/generic_choice_nested_in_template_class.carbon
  14. 23 0
      explorer/testdata/class/fail_store_to_uninitialized_class.carbon
  15. 3 7
      explorer/testdata/unformed/fail_array.carbon
  16. 16 0
      explorer/testdata/unformed/fail_array_dynamic.carbon
  17. 19 0
      explorer/testdata/unformed/fail_base_access.carbon
  18. 17 0
      explorer/testdata/unformed/fail_compound_member_access.carbon
  19. 17 0
      explorer/testdata/unformed/fail_control_flow_defer_to_dynamic.carbon
  20. 13 0
      explorer/testdata/unformed/fail_if_cond.carbon
  21. 13 0
      explorer/testdata/unformed/fail_if_else.carbon
  22. 13 0
      explorer/testdata/unformed/fail_if_then.carbon
  23. 19 0
      explorer/testdata/unformed/fail_in_class.carbon
  24. 17 0
      explorer/testdata/unformed/fail_indirect_member_access.carbon
  25. 13 0
      explorer/testdata/unformed/fail_intrinsic.carbon

+ 2 - 3
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);

+ 1 - 0
explorer/interpreter/BUILD

@@ -219,6 +219,7 @@ cc_library(
         "resolve_unformed.h",
     ],
     deps = [
+        ":stack_space",
         "//common:check",
         "//explorer/ast",
         "//explorer/ast:static_scope",

+ 5 - 0
explorer/interpreter/heap.cpp

@@ -38,6 +38,11 @@ auto Heap::Write(const Address& a, Nonnull<const Value*> v,
                  SourceLocation source_loc) -> ErrorOr<Success> {
   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_],

+ 0 - 33
explorer/interpreter/interpreter.cpp

@@ -1596,10 +1596,6 @@ auto Interpreter::StepExp() -> ErrorOr<Success> {
               *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<IntValue>(*args[1]).value());
               break;
@@ -2134,18 +2130,6 @@ auto Interpreter::StepStmt() -> ErrorOr<Success> {
         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<StaticArrayType>(dest_type);
-          CARBON_CHECK(array->has_size());
-          const auto& element_type = array->element_type();
-          const auto size = array->size();
-
-          std::vector<Nonnull<const Value*>> elements;
-          elements.reserve(size);
-          for (size_t i = 0; i < size; i++) {
-            elements.push_back(arena_->New<UninitializedValue>(&element_type));
-          }
-          v = arena_->New<TupleValueBase>(Value::Kind::TupleValue, elements);
         } else {
           v = arena_->New<UninitializedValue>(p);
         }
@@ -2290,7 +2274,6 @@ auto Interpreter::StepDeclaration() -> ErrorOr<Success> {
   switch (decl.kind()) {
     case DeclarationKind::VariableDeclaration: {
       const auto& var_decl = cast<VariableDeclaration>(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<Success> {
           todo_.Initialize(&var_decl.binding(), v);
           return todo_.FinishAction();
         }
-      } else if (var_type->kind() == Value::Kind::StaticArrayType) {
-        const auto& array = cast<StaticArrayType>(var_type);
-        CARBON_CHECK(array->has_size());
-        const auto& element_type = array->element_type();
-        const auto size = array->size();
-
-        std::vector<Nonnull<const Value*>> elements;
-        elements.reserve(size);
-        for (size_t i = 0; i < size; i++) {
-          elements.push_back(arena_->New<UninitializedValue>(&element_type));
-        }
-
-        Nonnull<const Value*> v =
-            arena_->New<TupleValueBase>(Value::Kind::TupleValue, elements);
-        todo_.Initialize(&var_decl.binding(), v);
-        return todo_.FinishAction();
       } else {
         Nonnull<const Value*> v =
             arena_->New<UninitializedValue>(&var_decl.binding().value());

+ 6 - 6
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<std::optional<ValueNodeView>> {
-  return RunWithExtraStack<ErrorOr<std::optional<ValueNodeView>>>(
+  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<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>(
+  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<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>(
+  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<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>(
+  return RunWithExtraStack(
       [&]() { return ResolveNamesImpl(statement, enclosing_scope); });
 }
 
@@ -703,7 +703,7 @@ auto NameResolver::ResolveNames(Declaration& declaration,
                                 StaticScope& enclosing_scope,
                                 ResolveFunctionBodies bodies)
     -> ErrorOr<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>(
+  return RunWithExtraStack(
       [&]() { return ResolveNamesImpl(declaration, enclosing_scope, bodies); });
 }
 
@@ -922,7 +922,7 @@ auto NameResolver::ResolveNamesImpl(Declaration& declaration,
 }
 
 auto ResolveNames(AST& ast) -> ErrorOr<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>([&]() -> ErrorOr<Success> {
+  return RunWithExtraStack([&]() -> ErrorOr<Success> {
     NameResolver resolver;
 
     StaticScope file_scope;

+ 95 - 29
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<const AstNode*> 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<const Expression*> expression,
-                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+static auto ResolveUnformedImpl(Nonnull<const Expression*> expression,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success>;
-static auto ResolveUnformed(Nonnull<const Pattern*> pattern,
-                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+static auto ResolveUnformedImpl(Nonnull<const Pattern*> pattern,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success>;
-static auto ResolveUnformed(Nonnull<const Statement*> statement,
-                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+static auto ResolveUnformedImpl(Nonnull<const Statement*> statement,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success>;
-static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
+static auto ResolveUnformedImpl(Nonnull<const Expression*> expression,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success>;
 
-static auto ResolveUnformed(Nonnull<const Expression*> 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 <typename T>
+static auto ResolveUnformed(Nonnull<const T*> expression, FlowFacts& flow_facts,
+                            FlowFacts::ActionType action) -> ErrorOr<Success> {
+  return RunWithExtraStack(
+      [&] { return ResolveUnformedImpl(expression, flow_facts, action); });
+}
+
+static auto ResolveUnformedImpl(Nonnull<const Expression*> expression,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success> {
   switch (expression->kind()) {
     case ExpressionKind::IdentifierExpression: {
@@ -85,6 +99,12 @@ static auto ResolveUnformed(Nonnull<const Expression*> expression,
           ResolveUnformed(&call.argument(), flow_facts, action));
       break;
     }
+    case ExpressionKind::IntrinsicExpression: {
+      const auto& intrin = cast<IntrinsicExpression>(*expression);
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&intrin.args(), flow_facts, action));
+      break;
+    }
     case ExpressionKind::TupleLiteral:
       for (Nonnull<const Expression*> field :
            cast<TupleLiteral>(*expression).fields()) {
@@ -100,6 +120,9 @@ static auto ResolveUnformed(Nonnull<const Expression*> 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<const Expression*> expression,
       }
       break;
     case ExpressionKind::SimpleMemberAccessExpression:
-      CARBON_RETURN_IF_ERROR(ResolveUnformed(
-          &cast<SimpleMemberAccessExpression>(*expression).object(), flow_facts,
-          FlowFacts::ActionType::Check));
+    case ExpressionKind::CompoundMemberAccessExpression:
+    case ExpressionKind::BaseAccessExpression:
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&cast<MemberAccessExpression>(*expression).object(),
+                          flow_facts, FlowFacts::ActionType::Check));
       break;
     case ExpressionKind::BuiltinConvertExpression:
       CARBON_RETURN_IF_ERROR(ResolveUnformed(
           cast<BuiltinConvertExpression>(*expression).source_expression(),
           flow_facts, FlowFacts::ActionType::Check));
       break;
+    case ExpressionKind::IndexExpression:
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&cast<IndexExpression>(*expression).object(),
+                          flow_facts, FlowFacts::ActionType::Check));
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&cast<IndexExpression>(*expression).offset(),
+                          flow_facts, FlowFacts::ActionType::Check));
+      break;
+    case ExpressionKind::IfExpression: {
+      const auto& if_exp = cast<IfExpression>(*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<const Expression*> 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<const Expression*> expression,
   return Success();
 }
 
-static auto ResolveUnformed(Nonnull<const Pattern*> pattern,
-                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+static auto ResolveUnformedImpl(Nonnull<const Pattern*> pattern,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success> {
   switch (pattern->kind()) {
     case PatternKind::BindingPattern: {
@@ -178,8 +217,9 @@ static auto ResolveUnformed(Nonnull<const Pattern*> pattern,
   return Success();
 }
 
-static auto ResolveUnformed(Nonnull<const Statement*> statement,
-                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+static auto ResolveUnformedImpl(Nonnull<const Statement*> statement,
+                                FlowFacts& flow_facts,
+                                FlowFacts::ActionType action)
     -> ErrorOr<Success> {
   switch (statement->kind()) {
     case StatementKind::Block: {
@@ -280,15 +320,36 @@ static auto ResolveUnformed(Nonnull<const Statement*> statement,
       }
       break;
     }
+    case StatementKind::For: {
+      const auto& for_stmt = cast<For>(*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<const Declaration*> declaration)
+    -> ErrorOr<Success>;
+
+static auto ResolveUnformed(
+    llvm::ArrayRef<Nonnull<const Declaration*>> declarations)
+    -> ErrorOr<Success> {
+  return RunWithExtraStack([declarations]() -> ErrorOr<Success> {
+    for (Nonnull<const Declaration*> declaration : declarations) {
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(declaration));
+    }
+    return Success();
+  });
+}
+
 static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
     -> ErrorOr<Success> {
   switch (declaration->kind()) {
@@ -306,12 +367,7 @@ static auto ResolveUnformed(Nonnull<const Declaration*> 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<const Declaration*> declaration)
     case DeclarationKind::AliasDeclaration:
       // do nothing
       break;
+    case DeclarationKind::ClassDeclaration:
+      return ResolveUnformed(cast<ClassDeclaration>(declaration)->members());
+    case DeclarationKind::MixinDeclaration:
+      return ResolveUnformed(cast<MixinDeclaration>(declaration)->members());
+    case DeclarationKind::InterfaceDeclaration:
+    case DeclarationKind::ConstraintDeclaration:
+      return ResolveUnformed(
+          cast<ConstraintTypeDeclaration>(declaration)->members());
+    case DeclarationKind::ImplDeclaration:
+      return ResolveUnformed(cast<ImplDeclaration>(declaration)->members());
   }
   return Success();
 }

+ 5 - 3
explorer/interpreter/stack_space.h

@@ -27,11 +27,13 @@ auto RunWithExtraStackHelper(llvm::function_ref<void()> fn) -> void;
 // create the current thread.
 //
 // Usage:
-//   return RunWithExtraStack<ReturnType>([&]() -> ReturnType {
+//   return RunWithExtraStack([&]() -> ReturnType {
 //         <function body>
 //       });
-template <typename ReturnType>
-auto RunWithExtraStack(llvm::function_ref<ReturnType()> fn) -> ReturnType {
+template <typename Fn>
+auto RunWithExtraStack(Fn fn) -> decltype(fn()) {
+  using ReturnType = decltype(fn());
+  static_assert(!std::is_reference_v<ReturnType>);
   if (Internal::IsStackSpaceNearlyExhausted()) {
     std::optional<ReturnType> result;
     Internal::RunWithExtraStackHelper([&] { result = fn(); });

+ 1 - 2
explorer/interpreter/type_checker.cpp

@@ -2742,8 +2742,7 @@ auto TypeChecker::CheckAddrMeAccess(
 auto TypeChecker::TypeCheckExp(Nonnull<Expression*> e,
                                const ImplScope& impl_scope)
     -> ErrorOr<Success> {
-  return RunWithExtraStack<ErrorOr<Success>>(
-      [&]() { return TypeCheckExpImpl(e, impl_scope); });
+  return RunWithExtraStack([&]() { return TypeCheckExpImpl(e, impl_scope); });
 }
 
 // NOLINTNEXTLINE(readability-function-size)

+ 1 - 1
explorer/parse_and_execute/parse_and_execute.cpp

@@ -40,7 +40,7 @@ static auto ParseAndExecuteHelper(std::function<ErrorOr<AST>(Arena*)> parse,
                                   Nonnull<TraceStream*> trace_stream,
                                   Nonnull<llvm::raw_ostream*> print_stream)
     -> ErrorOr<int> {
-  return RunWithExtraStack<ErrorOr<int>>([&]() -> ErrorOr<int> {
+  return RunWithExtraStack([&]() -> ErrorOr<int> {
     Arena arena;
     auto cursor = std::chrono::steady_clock::now();
 

+ 1 - 1
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;
 }

+ 1 - 2
explorer/testdata/array/uninitialized_local_array_access.carbon → 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;

+ 16 - 0
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<Placeholder<my_array>>
+  my_array[0] = 100;
+  Print("{0}", my_array[0]);
+  return 0;
+}

+ 2 - 2
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);

+ 23 - 0
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<Placeholder<p>>
+  p.x = 1;
+  p.y = 2;
+  return p.x;
+}

+ 3 - 7
explorer/testdata/array/uninitialized_global_array_access.carbon → 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];
 }

+ 16 - 0
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<Placeholder<v>>
+  return v[0];
+}

+ 19 - 0
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;
+}

+ 17 - 0
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);
+}

+ 17 - 0
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<Placeholder<x>>
+  return x;
+}

+ 13 - 0
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;
+}

+ 13 - 0
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;
+}

+ 13 - 0
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;
+}

+ 19 - 0
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();
+}

+ 17 - 0
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<Placeholder<pt>>
+  return *p;
+}

+ 13 - 0
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;
+}