Просмотр исходного кода

Implement more flows on unformed static analysis (#1923)

- Added detection of unformed usage with `return var`, control-flow statements and member access.
- Refactored the work flow: made flow facts a class and moved operations of flow facts into the class.
- Added, cleaned and renamed test cases. Some test cases for the dynamic unformed check were suppressed by the static check. They are now added back.

Co-authored-by: Jon Ross-Perkins <jperkins@google.com>
Zenong Zhang 3 лет назад
Родитель
Сommit
b28fd7ebc9
32 измененных файлов с 525 добавлено и 77 удалено
  1. 146 70
      explorer/interpreter/resolve_unformed.cpp
  2. 43 0
      explorer/interpreter/resolve_unformed.h
  3. 21 0
      explorer/testdata/unformed/control_flow_defer_to_dynamic.carbon
  4. 1 1
      explorer/testdata/unformed/dynamic/fail_global.carbon
  5. 25 0
      explorer/testdata/unformed/dynamic/fail_param.carbon
  6. 22 0
      explorer/testdata/unformed/dynamic/fail_pattern_declare.carbon
  7. 21 0
      explorer/testdata/unformed/dynamic/fail_return.carbon
  8. 21 0
      explorer/testdata/unformed/dynamic/fail_returned_var.carbon
  9. 23 0
      explorer/testdata/unformed/dynamic/fail_rhs_assign.carbon
  10. 0 0
      explorer/testdata/unformed/global_assign_before_use.carbon
  11. 0 0
      explorer/testdata/unformed/global_escape.carbon
  12. 0 0
      explorer/testdata/unformed/global_unformed_without_use.carbon
  13. 0 0
      explorer/testdata/unformed/local_assign_before_use.carbon
  14. 0 0
      explorer/testdata/unformed/local_escape.carbon
  15. 0 0
      explorer/testdata/unformed/local_unformed_without_use.carbon
  16. 0 0
      explorer/testdata/unformed/pattern_declare.carbon
  17. 0 0
      explorer/testdata/unformed/returned_var_assign_before_use.carbon
  18. 18 0
      explorer/testdata/unformed/static/fail_field_value.carbon
  19. 20 0
      explorer/testdata/unformed/static/fail_if_cond.carbon
  20. 23 0
      explorer/testdata/unformed/static/fail_if_else.carbon
  21. 22 0
      explorer/testdata/unformed/static/fail_if_then.carbon
  22. 1 1
      explorer/testdata/unformed/static/fail_local_pattern_declare.carbon
  23. 1 1
      explorer/testdata/unformed/static/fail_local_rhs_assign.carbon
  24. 29 0
      explorer/testdata/unformed/static/fail_match_clause.carbon
  25. 24 0
      explorer/testdata/unformed/static/fail_match_expression.carbon
  26. 1 1
      explorer/testdata/unformed/static/fail_param.carbon
  27. 1 1
      explorer/testdata/unformed/static/fail_return.carbon
  28. 1 1
      explorer/testdata/unformed/static/fail_returned_var.carbon
  29. 1 1
      explorer/testdata/unformed/static/fail_rhs_def.carbon
  30. 18 0
      explorer/testdata/unformed/static/fail_struct_member_access.carbon
  31. 22 0
      explorer/testdata/unformed/static/fail_while_body.carbon
  32. 20 0
      explorer/testdata/unformed/static/fail_while_cond.carbon

+ 146 - 70
explorer/interpreter/resolve_unformed.cpp

@@ -10,67 +10,85 @@
 #include "explorer/ast/ast.h"
 #include "explorer/ast/expression.h"
 #include "explorer/ast/pattern.h"
-#include "explorer/common/error_builders.h"
 #include "explorer/common/nonnull.h"
 
 using llvm::cast;
 
 namespace Carbon {
 
-// Aggregate information about a AstNode being analyzed.
-struct FlowFact {
-  bool may_be_formed;
-};
+auto FlowFacts::TakeAction(Nonnull<const AstNode*> node, ActionType action,
+                           SourceLocation source_loc, const std::string& name)
+    -> ErrorOr<Success> {
+  switch (action) {
+    case ActionType::AddInit: {
+      AddFact(node, FormedState::MustBeFormed);
+      break;
+    }
+    case ActionType::AddUninit: {
+      AddFact(node, FormedState::Unformed);
+      break;
+    }
+    case ActionType::Form: {
+      // TODO: Use CARBON_CHECK when we are able to handle global variables.
+      auto entry = facts_.find(node);
+      if (entry != facts_.end() &&
+          entry->second.formed_state == FormedState::Unformed) {
+        entry->second.formed_state = FormedState::MayBeFormed;
+      }
+      break;
+    }
+    case ActionType::Check: {
+      // TODO: @slaterlatiao add all available value nodes to flow facts and use
+      // CARBON_CHECK on the following line.
+      auto entry = facts_.find(node);
+      if (entry != facts_.end() &&
+          entry->second.formed_state == FormedState::Unformed) {
+        return CompilationError(source_loc)
+               << "use of uninitialized variable " << name;
+      }
+      break;
+    }
+    case ActionType::None:
+      break;
+  }
+  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,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts,
-    bool set_formed) -> ErrorOr<Success>;
-static auto ResolveUnformed(
-    Nonnull<const Pattern*> pattern,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts,
-    bool has_init) -> ErrorOr<Success>;
-static auto ResolveUnformed(
-    Nonnull<const Statement*> statement,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts)
+static auto ResolveUnformed(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)
+    -> ErrorOr<Success>;
+static auto ResolveUnformed(Nonnull<const Statement*> statement,
+                            FlowFacts& flow_facts, FlowFacts::ActionType action)
     -> ErrorOr<Success>;
 static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
     -> ErrorOr<Success>;
 
-static auto ResolveUnformed(
-    Nonnull<const Expression*> expression,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts,
-    const bool set_formed) -> ErrorOr<Success> {
+static auto ResolveUnformed(Nonnull<const Expression*> expression,
+                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+    -> ErrorOr<Success> {
   switch (expression->kind()) {
     case ExpressionKind::IdentifierExpression: {
       auto& identifier = cast<IdentifierExpression>(*expression);
-      auto fact = flow_facts.find(&identifier.value_node().base());
-      // TODO: @slaterlatiao add all available value nodes to flow facts and use
-      // CARBON_CHECK on the following line.
-      if (fact == flow_facts.end()) {
-        break;
-      }
-      if (set_formed) {
-        fact->second.may_be_formed = true;
-      } else if (!fact->second.may_be_formed) {
-        return CompilationError(identifier.source_loc())
-               << "use of uninitialized variable " << identifier.name();
-      }
+      CARBON_RETURN_IF_ERROR(
+          flow_facts.TakeAction(&identifier.value_node().base(), action,
+                                identifier.source_loc(), identifier.name()));
       break;
     }
     case ExpressionKind::CallExpression: {
       auto& call = cast<CallExpression>(*expression);
       CARBON_RETURN_IF_ERROR(
-          ResolveUnformed(&call.argument(), flow_facts, /*set_formed=*/false));
+          ResolveUnformed(&call.argument(), flow_facts, action));
       break;
     }
     case ExpressionKind::TupleLiteral:
       for (Nonnull<const Expression*> field :
            cast<TupleLiteral>(*expression).fields()) {
-        CARBON_RETURN_IF_ERROR(
-            ResolveUnformed(field, flow_facts, /*set_formed=*/false));
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(field, flow_facts, action));
       }
       break;
     case ExpressionKind::OperatorExpression: {
@@ -83,15 +101,26 @@ static auto ResolveUnformed(
             // runtime. A more sound analysis can be implemented when a
             // points-to analysis is available.
             ResolveUnformed(opt_exp.arguments().front(), flow_facts,
-                            /*set_formed=*/true));
+                            FlowFacts::ActionType::Form));
       } else {
         for (Nonnull<const Expression*> operand : opt_exp.arguments()) {
-          CARBON_RETURN_IF_ERROR(
-              ResolveUnformed(operand, flow_facts, /*set_formed=*/false));
+          CARBON_RETURN_IF_ERROR(ResolveUnformed(operand, flow_facts, action));
         }
       }
       break;
     }
+    case ExpressionKind::StructLiteral:
+      for (const FieldInitializer& init :
+           cast<StructLiteral>(*expression).fields()) {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&init.expression(), flow_facts,
+                                               FlowFacts::ActionType::Check));
+      }
+      break;
+    case ExpressionKind::SimpleMemberAccessExpression:
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(
+          &cast<SimpleMemberAccessExpression>(*expression).object(), flow_facts,
+          FlowFacts::ActionType::Check));
+      break;
     case ExpressionKind::DotSelfExpression:
     case ExpressionKind::IntLiteral:
     case ExpressionKind::BoolLiteral:
@@ -103,11 +132,9 @@ static auto ResolveUnformed(
     case ExpressionKind::ContinuationTypeLiteral:
     case ExpressionKind::ValueLiteral:
     case ExpressionKind::IndexExpression:
-    case ExpressionKind::SimpleMemberAccessExpression:
     case ExpressionKind::CompoundMemberAccessExpression:
     case ExpressionKind::IfExpression:
     case ExpressionKind::WhereExpression:
-    case ExpressionKind::StructLiteral:
     case ExpressionKind::StructTypeLiteral:
     case ExpressionKind::IntrinsicExpression:
     case ExpressionKind::UnimplementedExpression:
@@ -119,20 +146,20 @@ static auto ResolveUnformed(
   return Success();
 }
 
-static auto ResolveUnformed(
-    Nonnull<const Pattern*> pattern,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts,
-    const bool has_init) -> ErrorOr<Success> {
+static auto ResolveUnformed(Nonnull<const Pattern*> pattern,
+                            FlowFacts& flow_facts, FlowFacts::ActionType action)
+    -> ErrorOr<Success> {
   switch (pattern->kind()) {
-    case PatternKind::BindingPattern:
-      flow_facts.insert(
-          {Nonnull<const AstNode*>(&cast<BindingPattern>(*pattern)),
-           {has_init}});
-      break;
+    case PatternKind::BindingPattern: {
+      auto& binding_pattern = cast<BindingPattern>(*pattern);
+      CARBON_RETURN_IF_ERROR(flow_facts.TakeAction(&binding_pattern, action,
+                                                   binding_pattern.source_loc(),
+                                                   binding_pattern.name()));
+    } break;
     case PatternKind::TuplePattern:
       for (Nonnull<const Pattern*> field :
            cast<TuplePattern>(*pattern).fields()) {
-        CARBON_RETURN_IF_ERROR(ResolveUnformed(field, flow_facts, has_init));
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(field, flow_facts, action));
       }
       break;
     case PatternKind::GenericBinding:
@@ -147,52 +174,100 @@ static auto ResolveUnformed(
   return Success();
 }
 
-static auto ResolveUnformed(
-    Nonnull<const Statement*> statement,
-    std::unordered_map<Nonnull<const AstNode*>, FlowFact>& flow_facts)
+static auto ResolveUnformed(Nonnull<const Statement*> statement,
+                            FlowFacts& flow_facts, FlowFacts::ActionType action)
     -> ErrorOr<Success> {
   switch (statement->kind()) {
     case StatementKind::Block: {
       auto& block = cast<Block>(*statement);
       for (auto* block_statement : block.statements()) {
-        CARBON_RETURN_IF_ERROR(ResolveUnformed(block_statement, flow_facts));
+        CARBON_RETURN_IF_ERROR(
+            ResolveUnformed(block_statement, flow_facts, action));
       }
       break;
     }
     case StatementKind::VariableDefinition: {
       auto& def = cast<VariableDefinition>(*statement);
-      CARBON_RETURN_IF_ERROR(ResolveUnformed(&def.pattern(), flow_facts,
-                                             /*has_init=*/def.has_init()));
+      if (def.has_init()) {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&def.pattern(), flow_facts,
+                                               FlowFacts::ActionType::AddInit));
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&def.init(), flow_facts,
+                                               FlowFacts::ActionType::Check));
+      } else {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(
+            &def.pattern(), flow_facts, FlowFacts::ActionType::AddUninit));
+      }
       break;
     }
-    case StatementKind::ReturnVar:
-      // TODO: @slaterlatiao: Implement this flow.
+    case StatementKind::ReturnVar: {
+      auto& ret_var = cast<ReturnVar>(*statement);
+      auto& binding_pattern = cast<BindingPattern>(ret_var.value_node().base());
+      CARBON_RETURN_IF_ERROR(
+          flow_facts.TakeAction(&binding_pattern, FlowFacts::ActionType::Check,
+                                ret_var.source_loc(), binding_pattern.name()));
       break;
+    }
     case StatementKind::ReturnExpression: {
       auto& ret_exp_stmt = cast<ReturnExpression>(*statement);
       CARBON_RETURN_IF_ERROR(ResolveUnformed(&ret_exp_stmt.expression(),
-                                             flow_facts, /*set_formed=*/false));
+                                             flow_facts,
+                                             FlowFacts::ActionType::Check));
       break;
     }
     case StatementKind::Assign: {
       auto& assign = cast<Assign>(*statement);
-      CARBON_RETURN_IF_ERROR(
-          ResolveUnformed(&assign.lhs(), flow_facts, /*set_formed=*/true));
-      CARBON_RETURN_IF_ERROR(
-          ResolveUnformed(&assign.rhs(), flow_facts, /*set_formed=*/false));
+      if (assign.lhs().kind() == ExpressionKind::IdentifierExpression) {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts,
+                                               FlowFacts::ActionType::Form));
+      } else {
+        // TODO: Support checking non-identifier lhs expression.
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.lhs(), flow_facts,
+                                               FlowFacts::ActionType::None));
+      }
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(&assign.rhs(), flow_facts,
+                                             FlowFacts::ActionType::Check));
       break;
     }
     case StatementKind::ExpressionStatement: {
       auto& exp_stmt = cast<ExpressionStatement>(*statement);
-      CARBON_RETURN_IF_ERROR(ResolveUnformed(&exp_stmt.expression(), flow_facts,
-                                             /*set_formed=*/false));
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&exp_stmt.expression(), flow_facts, action));
+      break;
+    }
+    case StatementKind::If: {
+      auto& if_stmt = cast<If>(*statement);
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(&if_stmt.condition(), flow_facts,
+                                             FlowFacts::ActionType::Check));
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&if_stmt.then_block(), flow_facts, action));
+      if (if_stmt.else_block().has_value()) {
+        CARBON_RETURN_IF_ERROR(
+            ResolveUnformed(*if_stmt.else_block(), flow_facts, action));
+      }
+      break;
+    }
+    case StatementKind::While: {
+      auto& while_stmt = cast<While>(*statement);
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(
+          &while_stmt.condition(), flow_facts, FlowFacts::ActionType::Check));
+      CARBON_RETURN_IF_ERROR(
+          ResolveUnformed(&while_stmt.body(), flow_facts, action));
+      break;
+    }
+    case StatementKind::Match: {
+      auto& match = cast<Match>(*statement);
+      CARBON_RETURN_IF_ERROR(ResolveUnformed(&match.expression(), flow_facts,
+                                             FlowFacts::ActionType::Check));
+      for (auto& clause : match.clauses()) {
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(&clause.pattern(), flow_facts,
+                                               FlowFacts::ActionType::Check));
+        CARBON_RETURN_IF_ERROR(
+            ResolveUnformed(&clause.statement(), flow_facts, action));
+      }
       break;
     }
     case StatementKind::Break:
     case StatementKind::Continue:
-    case StatementKind::If:
-    case StatementKind::While:
-    case StatementKind::Match:
     case StatementKind::Continuation:
     case StatementKind::Run:
     case StatementKind::Await:
@@ -212,8 +287,9 @@ static auto ResolveUnformed(Nonnull<const Declaration*> declaration)
     case DeclarationKind::FunctionDeclaration: {
       auto& function = cast<FunctionDeclaration>(*declaration);
       if (function.body().has_value()) {
-        std::unordered_map<Nonnull<const AstNode*>, FlowFact> flow_facts;
-        CARBON_RETURN_IF_ERROR(ResolveUnformed(*function.body(), flow_facts));
+        FlowFacts flow_facts;
+        CARBON_RETURN_IF_ERROR(ResolveUnformed(*function.body(), flow_facts,
+                                               FlowFacts::ActionType::None));
       }
       break;
     }

+ 43 - 0
explorer/interpreter/resolve_unformed.h

@@ -10,6 +10,49 @@
 
 namespace Carbon {
 
+// Maps AST nodes to flow facts within a function.
+class FlowFacts {
+ public:
+  enum class ActionType {
+    // Adds a must-be-formed flow fact.
+    // Used at `VariableDefinition` with initialization.
+    AddInit,
+    // Adds an unformed flow fact.
+    // Used at `VariableDefinition` without initialization.
+    AddUninit,
+    // Marks an unformed flow fact as may-be-formed.
+    // Used at AST nodes that potentially initializes a variable.
+    Form,
+    // Returns compilation error if the AST node is impossible to be formed.
+    // Used at AST nodes that uses a variable.
+    Check,
+    // Used in traversing children nodes without an acion to take.
+    None,
+  };
+  // Take action on flow facts based on `ActionType`.
+  auto TakeAction(Nonnull<const AstNode*> node, ActionType action,
+                  SourceLocation source_loc, const std::string& name)
+      -> ErrorOr<Success>;
+
+ private:
+  enum class FormedState {
+    MustBeFormed,
+    MayBeFormed,
+    Unformed,
+  };
+  // Aggregate information about a AstNode being analyzed.
+  struct Fact {
+    FormedState formed_state;
+  };
+
+  void AddFact(Nonnull<const AstNode*> node, const FormedState state) {
+    CARBON_CHECK(facts_.find(node) == facts_.end());
+    facts_.insert({node, {state}});
+  }
+
+  std::unordered_map<Nonnull<const AstNode*>, Fact> facts_;
+};
+
 // An intraprocedural forward analysis that checks the may-be-formed states on
 // local variables. Returns compilation error on usage of must-be-unformed
 // variables.

+ 21 - 0
explorer/testdata/unformed/control_flow_defer_to_dynamic.carbon

@@ -0,0 +1,21 @@
+// 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
+//
+// RUN: %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+// CHECK: result: 0
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  if (0 == 0) {
+    x = 0;
+  }
+  // Static analysis thinks `x` may be formed, defer the check to run-time.
+  return x;
+}

+ 1 - 1
explorer/testdata/uninitialized/fail_global_uninitialized.carbon → explorer/testdata/unformed/dynamic/fail_global.carbon

@@ -13,6 +13,6 @@ package ExplorerTest api;
 var x: i32;
 
 fn Main() -> i32 {
-  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_global_uninitialized.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_global.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
   return x;
 }

+ 25 - 0
explorer/testdata/unformed/dynamic/fail_param.carbon

@@ -0,0 +1,25 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn AddInt(a: i32, b: i32) -> auto {
+  return a + b;
+}
+
+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: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_param.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  return AddInt(x, 2);
+}

+ 22 - 0
explorer/testdata/unformed/dynamic/fail_pattern_declare.carbon

@@ -0,0 +1,22 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var (x: i32, y: i32);
+  x = 1;
+  if (0 == 1) {
+    y = 0;
+  }
+  // Static analysis thinks `y` may be formed, defer the check to run-time.
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_pattern_declare.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<y>>
+  return y;
+}

+ 21 - 0
explorer/testdata/unformed/dynamic/fail_return.carbon

@@ -0,0 +1,21 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+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: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_return.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  return x;
+}

+ 21 - 0
explorer/testdata/unformed/dynamic/fail_returned_var.carbon

@@ -0,0 +1,21 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_returned_var.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  returned var x: i32;
+  if (0 == 1) {
+    x = 0;
+  }
+  // Static analysis thinks `x` may be formed, defer the check to run-time.
+  return var;
+}

+ 23 - 0
explorer/testdata/unformed/dynamic/fail_rhs_assign.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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  var y: i32;
+  if (0 == 1) {
+    x = 0;
+  }
+  // Static analysis thinks `x` may be formed, defer the check to run-time.
+  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/unformed/dynamic/fail_rhs_assign.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
+  y = x;
+  return y;
+}

+ 0 - 0
explorer/testdata/uninitialized/global_declare.carbon → explorer/testdata/unformed/global_assign_before_use.carbon


+ 0 - 0
explorer/testdata/uninitialized/global_uninitialized_escape.carbon → explorer/testdata/unformed/global_escape.carbon


+ 0 - 0
explorer/testdata/uninitialized/global_uninit_without_use.carbon → explorer/testdata/unformed/global_unformed_without_use.carbon


+ 0 - 0
explorer/testdata/uninitialized/local_declare.carbon → explorer/testdata/unformed/local_assign_before_use.carbon


+ 0 - 0
explorer/testdata/uninitialized/local_uninitialized_escape.carbon → explorer/testdata/unformed/local_escape.carbon


+ 0 - 0
explorer/testdata/uninitialized/local_uninit_without_use.carbon → explorer/testdata/unformed/local_unformed_without_use.carbon


+ 0 - 0
explorer/testdata/uninitialized/local_declare_pattern.carbon → explorer/testdata/unformed/pattern_declare.carbon


+ 0 - 0
explorer/testdata/uninitialized/declare_returned_var.carbon → explorer/testdata/unformed/returned_var_assign_before_use.carbon


+ 18 - 0
explorer/testdata/unformed/static/fail_field_value.carbon

@@ -0,0 +1,18 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_field_value.carbon:[[@LINE+1]]: use of uninitialized variable x
+  var p: auto = {.x = x,};
+  return 0;
+}

+ 20 - 0
explorer/testdata/unformed/static/fail_if_cond.carbon

@@ -0,0 +1,20 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x: i32;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_if_cond.carbon:[[@LINE+1]]: use of uninitialized variable x
+  if (x == 0) {
+    return 0;
+  }
+  return 1;
+}

+ 23 - 0
explorer/testdata/unformed/static/fail_if_else.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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Foo() -> i32;
+
+fn Main() -> i32 {
+  var x: i32;
+  if (Foo() == 0) {
+    return 0;
+  } else {
+    // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_if_else.carbon:[[@LINE+1]]: use of uninitialized variable x
+    return x;
+  }
+}

+ 22 - 0
explorer/testdata/unformed/static/fail_if_then.carbon

@@ -0,0 +1,22 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Foo() -> i32;
+
+fn Main() -> i32 {
+  var x: i32;
+  if (Foo() == 0) {
+    // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_if_then.carbon:[[@LINE+1]]: use of uninitialized variable x
+    return x;
+  }
+  return 1;
+}

+ 1 - 1
explorer/testdata/uninitialized/fail_local_uninitialized_pattern.carbon → explorer/testdata/unformed/static/fail_local_pattern_declare.carbon

@@ -13,6 +13,6 @@ package ExplorerTest api;
 fn Main() -> i32 {
   var (x: i32, y: i32);
   x = 1;
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/uninitialized/fail_local_uninitialized_pattern.carbon:[[@LINE+1]]: use of uninitialized variable y
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_local_pattern_declare.carbon:[[@LINE+1]]: use of uninitialized variable y
   return y;
 }

+ 1 - 1
explorer/testdata/uninitialized/fail_local_uninitialized_assign.carbon → explorer/testdata/unformed/static/fail_local_rhs_assign.carbon

@@ -13,7 +13,7 @@ package ExplorerTest api;
 fn Main() -> i32 {
   var x: i32;
   var y: i32;
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/uninitialized/fail_local_uninitialized_assign.carbon:[[@LINE+1]]: use of uninitialized variable x
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_local_rhs_assign.carbon:[[@LINE+1]]: use of uninitialized variable x
   y = x;
   return y;
 }

+ 29 - 0
explorer/testdata/unformed/static/fail_match_clause.carbon

@@ -0,0 +1,29 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Foo() -> i32;
+
+fn Main() -> i32 {
+  var x : i32;
+  match (Foo()) {
+    case 0 => {
+      return 2;
+    }
+    case 1 => {
+      // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_match_clause.carbon:[[@LINE+1]]: use of uninitialized variable x
+      return x;
+    }
+    default => {
+      return 0;
+    }
+  }
+}

+ 24 - 0
explorer/testdata/unformed/static/fail_match_expression.carbon

@@ -0,0 +1,24 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var x : i32;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_match_expression.carbon:[[@LINE+1]]: use of uninitialized variable x
+  match (x) {
+    case 0 => {
+      return 1;
+    }
+    default => {
+      return 0;
+    }
+  }
+}

+ 1 - 1
explorer/testdata/uninitialized/fail_local_uninitialized_param.carbon → explorer/testdata/unformed/static/fail_param.carbon

@@ -16,6 +16,6 @@ fn AddInt(a: i32, b: i32) -> auto {
 
 fn Main() -> i32 {
   var x: i32;
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/uninitialized/fail_local_uninitialized_param.carbon:[[@LINE+1]]: use of uninitialized variable x
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_param.carbon:[[@LINE+1]]: use of uninitialized variable x
   return AddInt(x, 2);
 }

+ 1 - 1
explorer/testdata/uninitialized/fail_local_uninitialized_return.carbon → explorer/testdata/unformed/static/fail_return.carbon

@@ -12,6 +12,6 @@ package ExplorerTest api;
 
 fn Main() -> i32 {
   var x: i32;
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/uninitialized/fail_local_uninitialized_return.carbon:[[@LINE+1]]: use of uninitialized variable x
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_return.carbon:[[@LINE+1]]: use of uninitialized variable x
   return x;
 }

+ 1 - 1
explorer/testdata/uninitialized/fail_uninitialized_returned_var.carbon → explorer/testdata/unformed/static/fail_returned_var.carbon

@@ -11,7 +11,7 @@
 package ExplorerTest api;
 
 fn Main() -> i32 {
-  // CHECK: RUNTIME ERROR: {{.*}}/explorer/testdata/uninitialized/fail_uninitialized_returned_var.carbon:[[@LINE+1]]: undefined behavior: access to uninitialized value Uninit<Placeholder<x>>
   returned var x: i32;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_returned_var.carbon:[[@LINE+1]]: use of uninitialized variable x
   return var;
 }

+ 1 - 1
explorer/testdata/uninitialized/fail_local_uninitialized_init.carbon → explorer/testdata/unformed/static/fail_rhs_def.carbon

@@ -12,7 +12,7 @@ package ExplorerTest api;
 
 fn Main() -> i32 {
   var x: i32;
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_rhs_def.carbon:[[@LINE+1]]: use of uninitialized variable x
   var y: i32 = x;
-  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/uninitialized/fail_local_uninitialized_init.carbon:[[@LINE+1]]: use of uninitialized variable x
   return x;
 }

+ 18 - 0
explorer/testdata/unformed/static/fail_struct_member_access.carbon

@@ -0,0 +1,18 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var p: {.x: i32, .y: i32};
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_struct_member_access.carbon:[[@LINE+1]]: use of uninitialized variable p
+  p.x = p.y;
+  return 0;
+}

+ 22 - 0
explorer/testdata/unformed/static/fail_while_body.carbon

@@ -0,0 +1,22 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Foo() -> i32;
+
+fn Main() -> i32 {
+  var (x: i32, y: i32);
+  while (Foo() == 0) {
+    // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_while_body.carbon:[[@LINE+1]]: use of uninitialized variable x
+    y = x;
+  }
+  return 1;
+}

+ 20 - 0
explorer/testdata/unformed/static/fail_while_cond.carbon

@@ -0,0 +1,20 @@
+// 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
+//
+// RUN: %{not} %{explorer} %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes=false %s
+// RUN: %{not} %{explorer} --parser_debug --trace_file=- %s 2>&1 | \
+// RUN:   %{FileCheck} --match-full-lines --allow-unused-prefixes %s
+// AUTOUPDATE: %{explorer} %s
+
+package ExplorerTest api;
+
+fn Main() -> i32 {
+  var (x: i32, y: i32);
+  // CHECK: COMPILATION ERROR: {{.*}}/explorer/testdata/unformed/static/fail_while_cond.carbon:[[@LINE+1]]: use of uninitialized variable x
+  while (x == 0) {
+    y = 1;
+  }
+  return 1;
+}