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

improved checking for liveness when reading and writing memory (#448)

* improved checking for liveness when reading and writing memory

* moving some functions to be methods of State

* finished moving functions into State

* Update executable_semantics/interpreter/interpreter.h

Co-authored-by: Geoff Romer <gromer@google.com>

* moved some comments, other minor edits

Co-authored-by: Geoff Romer <gromer@google.com>
Jeremy G. Siek 5 лет назад
Родитель
Сommit
27fd9de5bf

+ 1 - 0
executable_semantics/BUILD

@@ -75,6 +75,7 @@ EXAMPLES = [
     "experimental_continuation6",
     "experimental_continuation7",
     "experimental_continuation8",
+    "experimental_continuation9",
 ]
 
 [genrule(

+ 79 - 58
executable_semantics/interpreter/interpreter.cpp

@@ -31,32 +31,52 @@ void HandleValue();
 // Auxiliary Functions
 //
 
-auto AllocateValue(const Value* v) -> Address {
+auto State::AllocateValue(const Value* v) -> Address {
   // Putting the following two side effects together in this function
   // ensures that we don't do anything else in between, which is really bad!
   // Consider whether to include a copy of the input v in this function
   // or to leave it up to the caller.
-  Address a = state->heap.size();
-  state->heap.push_back(new Value(*v));
-  state->alive.push_back(true);
+  Address a = heap.size();
+  heap.push_back(new Value(*v));
+  this->alive.push_back(true);
   return a;
 }
 
+auto State::ReadFromMemory(Address a, int line_num) -> const Value* {
+  this->CheckAlive(a, line_num);
+  return heap[a];
+}
+
+auto State::WriteToMemory(Address a, const Value* v, int line_num) -> void {
+  this->CheckAlive(a, line_num);
+  heap[a] = v;
+}
+
+void State::CheckAlive(Address address, int line_num) {
+  if (!this->alive[address]) {
+    std::cerr << line_num << ": undefined behavior: access to dead value ";
+    PrintValue(heap[address], std::cerr);
+    std::cerr << std::endl;
+    exit(-1);
+  }
+}
+
 auto CopyVal(const Value* val, int line_num) -> const Value* {
   switch (val->tag) {
     case ValKind::TupleV: {
       auto elts = new std::vector<std::pair<std::string, Address>>();
       for (auto& i : *val->u.tuple.elts) {
-        CheckAlive(i.second, line_num);
-        const Value* elt = CopyVal(state->heap[i.second], line_num);
-        Address new_address = AllocateValue(elt);
+        const Value* elt =
+            CopyVal(state->ReadFromMemory(i.second, line_num), line_num);
+        Address new_address = state->AllocateValue(elt);
         elts->push_back(make_pair(i.first, new_address));
       }
       return MakeTupleVal(elts);
     }
     case ValKind::AltV: {
-      const Value* arg = CopyVal(state->heap[val->u.alt.argument], line_num);
-      Address argument_address = AllocateValue(arg);
+      const Value* arg = CopyVal(
+          state->ReadFromMemory(val->u.alt.argument, line_num), line_num);
+      Address argument_address = state->AllocateValue(arg);
       return MakeAltVal(*val->u.alt.alt_name, *val->u.alt.choice_name,
                         argument_address);
     }
@@ -102,20 +122,18 @@ auto CopyVal(const Value* val, int line_num) -> const Value* {
   }
 }
 
-void KillObject(Address address);
-
 // Marks all of the sub-objects of this value as dead.
 void KillSubObjects(const Value* val) {
   switch (val->tag) {
     case ValKind::AltV:
-      KillObject(val->u.alt.argument);
+      state->KillObject(val->u.alt.argument);
       break;
     case ValKind::StructV:
       KillSubObjects(val->u.struct_val.inits);
       break;
     case ValKind::TupleV:
       for (auto& elt : *val->u.tuple.elts) {
-        KillObject(elt.second);
+        state->KillObject(elt.second);
       }
       break;
     default:
@@ -123,11 +141,10 @@ void KillSubObjects(const Value* val) {
   }
 }
 
-// Marks the object at this address, and all of its sub-objects, as dead.
-void KillObject(Address address) {
-  if (state->alive[address]) {
-    state->alive[address] = false;
-    KillSubObjects(state->heap[address]);
+void State::KillObject(Address address) {
+  if (this->alive[address]) {
+    this->alive[address] = false;
+    KillSubObjects(heap[address]);
   } else {
     std::cerr << "runtime error, killing an already dead value" << std::endl;
     exit(-1);
@@ -135,9 +152,9 @@ void KillObject(Address address) {
 }
 
 void PrintEnv(Env values, std::ostream& out) {
-  for (const auto& [name, value] : values) {
+  for (const auto& [name, address] : values) {
     out << name << ": ";
-    PrintValue(state->heap[value], out);
+    state->PrintAddress(address, out);
     out << ", ";
   }
 }
@@ -163,7 +180,7 @@ void PrintStack(Stack<Frame*> ls, std::ostream& out) {
   }
 }
 
-void PrintHeap(const std::vector<const Value*>& heap, std::ostream& out) {
+void State::PrintHeap(std::ostream& out) {
   for (auto& iter : heap) {
     if (iter) {
       PrintValue(iter, out);
@@ -184,7 +201,7 @@ void PrintState(std::ostream& out) {
   out << "stack: ";
   PrintStack(state->stack, out);
   out << std::endl << "heap: ";
-  PrintHeap(state->heap, out);
+  state->PrintHeap(out);
   if (!state->stack.IsEmpty() && !state->stack.Top()->scopes.IsEmpty()) {
     out << std::endl << "values: ";
     PrintEnv(CurrentEnv(state), out);
@@ -218,7 +235,6 @@ auto ValToBool(const Value* v, int line_num) -> int {
 }
 
 auto ValToPtr(const Value* v, int line_num) -> Address {
-  CheckAlive(v->u.ptr, line_num);
   switch (v->tag) {
     case ValKind::PtrV:
       return v->u.ptr;
@@ -284,7 +300,7 @@ auto ChoiceDeclaration::InitGlobals(Env& globals) const -> void {
     alts->push_back(make_pair(kv.first, t));
   }
   auto ct = MakeChoiceTypeVal(name, alts);
-  auto a = AllocateValue(ct);
+  auto a = state->AllocateValue(ct);
   globals.Set(name, a);
 }
 
@@ -302,7 +318,7 @@ auto StructDeclaration::InitGlobals(Env& globals) const -> void {
     }
   }
   auto st = MakeStructTypeVal(*definition.name, fields, methods);
-  auto a = AllocateValue(st);
+  auto a = state->AllocateValue(st);
   globals.Set(*definition.name, a);
 }
 
@@ -310,7 +326,7 @@ auto FunctionDeclaration::InitGlobals(Env& globals) const -> void {
   Env values;
   auto pt = InterpExp(values, definition->param_pattern);
   auto f = MakeFunVal(definition->name, pt, definition->body);
-  Address a = AllocateValue(f);
+  Address a = state->AllocateValue(f);
   globals.Set(definition->name, a);
 }
 
@@ -318,7 +334,7 @@ auto FunctionDeclaration::InitGlobals(Env& globals) const -> void {
 // result of evaluating the initializer.
 auto VariableDeclaration::InitGlobals(Env& globals) const -> void {
   auto v = InterpExp(globals, initializer);
-  Address a = AllocateValue(v);
+  Address a = state->AllocateValue(v);
   globals.Set(name, a);
 }
 
@@ -355,9 +371,9 @@ void CallFunction(int line_num, std::vector<const Value*> operas,
     }
     case ValKind::AltConsV: {
       const Value* arg = CopyVal(operas[1], line_num);
-      const Value* av =
-          MakeAltVal(*operas[0]->u.alt_cons.alt_name,
-                     *operas[0]->u.alt_cons.choice_name, AllocateValue(arg));
+      const Value* av = MakeAltVal(*operas[0]->u.alt_cons.alt_name,
+                                   *operas[0]->u.alt_cons.choice_name,
+                                   state->AllocateValue(arg));
       Frame* frame = state->stack.Top();
       frame->todo.Push(MakeValAct(av));
       break;
@@ -377,7 +393,7 @@ void KillScope(int line_num, Scope* scope) {
       std::cerr << "internal error in KillScope" << std::endl;
       exit(-1);
     }
-    KillObject(*a);
+    state->KillObject(*a);
   }
 }
 
@@ -393,7 +409,7 @@ void CreateTuple(Frame* frame, Action* act, const Expression* /*exp*/) {
   auto elts = new std::vector<std::pair<std::string, Address>>();
   auto f = act->u.exp->u.tuple.fields->begin();
   for (auto i = act->results.begin(); i != act->results.end(); ++i, ++f) {
-    Address a = AllocateValue(*i);  // copy?
+    Address a = state->AllocateValue(*i);  // copy?
     elts->push_back(make_pair(f->first, a));
   }
   const Value* tv = MakeTupleVal(elts);
@@ -411,7 +427,7 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
     -> std::optional<Env> {
   switch (p->tag) {
     case ValKind::VarPatV: {
-      Address a = AllocateValue(CopyVal(v, line_num));
+      Address a = state->AllocateValue(CopyVal(v, line_num));
       vars->push_back(*p->u.var_pat.name);
       values.Set(*p->u.var_pat.name, a);
       return values;
@@ -432,9 +448,9 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
               std::cerr << std::endl;
               exit(-1);
             }
-            std::optional<Env> matches =
-                PatternMatch(state->heap[elt.second], state->heap[*a], values,
-                             vars, line_num);
+            std::optional<Env> matches = PatternMatch(
+                state->ReadFromMemory(elt.second, line_num),
+                state->ReadFromMemory(*a, line_num), values, vars, line_num);
             if (!matches) {
               return std::nullopt;
             }
@@ -456,9 +472,10 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
               *p->u.alt.alt_name != *v->u.alt.alt_name) {
             return std::nullopt;
           }
-          std::optional<Env> matches = PatternMatch(
-              state->heap[p->u.alt.argument], state->heap[v->u.alt.argument],
-              values, vars, line_num);
+          std::optional<Env> matches =
+              PatternMatch(state->ReadFromMemory(p->u.alt.argument, line_num),
+                           state->ReadFromMemory(v->u.alt.argument, line_num),
+                           values, vars, line_num);
           if (!matches) {
             return std::nullopt;
           }
@@ -498,7 +515,8 @@ auto PatternMatch(const Value* p, const Value* v, Env values,
 void PatternAssignment(const Value* pat, const Value* val, int line_num) {
   switch (pat->tag) {
     case ValKind::PtrV:
-      state->heap[ValToPtr(pat, line_num)] = CopyVal(val, line_num);
+      state->WriteToMemory(ValToPtr(pat, line_num), CopyVal(val, line_num),
+                           line_num);
       break;
     case ValKind::TupleV: {
       switch (val->tag) {
@@ -516,8 +534,8 @@ void PatternAssignment(const Value* pat, const Value* val, int line_num) {
               std::cerr << std::endl;
               exit(-1);
             }
-            PatternAssignment(state->heap[elt.second], state->heap[*a],
-                              line_num);
+            PatternAssignment(state->ReadFromMemory(elt.second, line_num),
+                              state->ReadFromMemory(*a, line_num), line_num);
           }
           break;
         }
@@ -539,8 +557,9 @@ void PatternAssignment(const Value* pat, const Value* val, int line_num) {
             std::cerr << "internal error in pattern assignment" << std::endl;
             exit(-1);
           }
-          PatternAssignment(state->heap[pat->u.alt.argument],
-                            state->heap[val->u.alt.argument], line_num);
+          PatternAssignment(
+              state->ReadFromMemory(pat->u.alt.argument, line_num),
+              state->ReadFromMemory(val->u.alt.argument, line_num), line_num);
           break;
         }
         default:
@@ -584,7 +603,6 @@ void StepLvalue() {
         exit(-1);
       }
       const Value* v = MakePtrVal(*pointer);
-      CheckAlive(*pointer, exp->line_num);
       frame->todo.Pop();
       frame->todo.Push(MakeValAct(v));
       break;
@@ -681,7 +699,7 @@ void StepExp() {
                   << *(exp->u.variable.name) << "`" << std::endl;
         exit(-1);
       }
-      const Value* pointee = state->heap[*pointer];
+      const Value* pointee = state->ReadFromMemory(*pointer, exp->line_num);
       frame->todo.Pop(1);
       frame->todo.Push(MakeValAct(pointee));
       break;
@@ -901,7 +919,7 @@ void StepStmt() {
       todo.Push(MakeStmtAct(stmt->u.continuation.body));
       Frame* continuation_frame = new Frame("__continuation", scopes, todo);
       Address continuation_address =
-          AllocateValue(MakeContinuation({continuation_frame}));
+          state->AllocateValue(MakeContinuation({continuation_frame}));
       // Store the continuation's address in the frame.
       continuation_frame->continuation = continuation_address;
       // Bind the continuation object to the continuation variable
@@ -924,13 +942,14 @@ void StepStmt() {
         paused.push_back(state->stack.Pop());
       } while (!paused.back()->IsContinuation());
       // Update the continuation with the paused stack.
-      state->heap[paused.back()->continuation] = MakeContinuation(paused);
+      state->WriteToMemory(paused.back()->continuation,
+                           MakeContinuation(paused), stmt->line_num);
       break;
   }
 }
 
-auto GetMember(Address a, const std::string& f) -> Address {
-  const Value* v = state->heap[a];
+auto GetMember(Address a, const std::string& f, int line_num) -> Address {
+  const Value* v = state->ReadFromMemory(a, line_num);
   switch (v->tag) {
     case ValKind::StructV: {
       auto a = FindTupleField(f, v->u.struct_val.inits);
@@ -960,7 +979,7 @@ auto GetMember(Address a, const std::string& f) -> Address {
         exit(-1);
       }
       auto ac = MakeAltCons(f, *v->u.choice_type.name);
-      return AllocateValue(ac);
+      return state->AllocateValue(ac);
     }
     default:
       std::cerr << "field access not allowed for value ";
@@ -1013,13 +1032,13 @@ void HandleValue() {
   }
   switch (act->tag) {
     case ActionKind::DeleteTmpAction: {
-      KillObject(act->u.delete_tmp);
+      state->KillObject(act->u.delete_tmp);
       frame->todo.Pop(2);
       frame->todo.Push(val_act);
       break;
     }
     case ActionKind::ExpToLValAction: {
-      Address a = AllocateValue(act->results[0]);
+      Address a = state->AllocateValue(act->results[0]);
       auto del = MakeDeleteAct(a);
       frame->todo.Pop(2);
       InsertDelete(del, frame->todo);
@@ -1033,8 +1052,8 @@ void HandleValue() {
           //    { v :: [].f :: C, E, F} :: S, H}
           // -> { { &v.f :: C, E, F} :: S, H }
           const Value* str = act->results[0];
-          Address a =
-              GetMember(ValToPtr(str, exp->line_num), *exp->u.get_field.field);
+          Address a = GetMember(ValToPtr(str, exp->line_num),
+                                *exp->u.get_field.field, exp->line_num);
           frame->todo.Pop(2);
           frame->todo.Push(MakeValAct(MakePtrVal(a)));
           break;
@@ -1126,7 +1145,8 @@ void HandleValue() {
                   exit(-1);
                 }
                 frame->todo.Pop(2);
-                frame->todo.Push(MakeValAct(state->heap[*a]));
+                const Value* element = state->ReadFromMemory(*a, exp->line_num);
+                frame->todo.Push(MakeValAct(element));
                 break;
               }
               default:
@@ -1143,9 +1163,10 @@ void HandleValue() {
           //    { { v :: [].f :: C, E, F} :: S, H}
           // -> { { v_f :: C, E, F} : S, H}
           auto a = GetMember(ValToPtr(act->results[0], exp->line_num),
-                             *exp->u.get_field.field);
+                             *exp->u.get_field.field, exp->line_num);
+          const Value* element = state->ReadFromMemory(a, exp->line_num);
           frame->todo.Pop(2);
-          frame->todo.Push(MakeValAct(state->heap[a]));
+          frame->todo.Push(MakeValAct(element));
           break;
         }
         case ExpressionKind::PrimitiveOp: {

+ 21 - 2
executable_semantics/interpreter/interpreter.h

@@ -59,10 +59,30 @@ struct Frame {
         continuation(UINT_MAX) {}
 };
 
+// TODO(geoffromer): Make this a class, with all members private
 struct State {
   Stack<Frame*> stack;
-  std::vector<const Value*> heap;
   std::vector<bool> alive;
+
+  // Returns the value at the given address in the heap after
+  // checking that it is alive.
+  auto ReadFromMemory(Address a, int line_num) -> const Value*;
+  // Writes the given value at the address in the heap after
+  // checking that the address is alive.
+  auto WriteToMemory(Address a, const Value* v, int line_num) -> void;
+  // Print the value at the given address to the stream `out`.
+  auto PrintAddress(Address a, std::ostream& out) -> void;
+  // Signal an error if the address is no longer alive.
+  void CheckAlive(Address address, int line_num);
+  // Put the given value on the heap and mark it as alive.
+  auto AllocateValue(const Value* v) -> Address;
+  // Marks the object at this address, and all of its sub-objects, as dead.
+  auto KillObject(Address address) -> void;
+  // Print all the values on the heap to the stream `out`.
+  auto PrintHeap(std::ostream& out) -> void;
+
+ private:
+  std::vector<const Value*> heap;
 };
 
 extern State* state;
@@ -70,7 +90,6 @@ extern State* state;
 auto PrintFrame(Frame* frame, std::ostream& out) -> void;
 void PrintStack(Stack<Frame*> ls, std::ostream& out);
 void PrintEnv(Env values);
-auto AllocateValue(const Value* v) -> Address;
 auto CopyVal(const Value* val, int line_num) -> const Value*;
 auto ToInteger(const Value* v) -> int;
 

+ 12 - 9
executable_semantics/interpreter/typecheck.cpp

@@ -58,8 +58,9 @@ auto ReifyType(const Value* t, int line_num) -> const Expression* {
     case ValKind::TupleV: {
       auto args = new std::vector<std::pair<std::string, const Expression*>>();
       for (auto& field : *t->u.tuple.elts) {
-        args->push_back(
-            {field.first, ReifyType(state->heap[field.second], line_num)});
+        args->push_back({field.first, ReifyType(state->ReadFromMemory(
+                                                    field.second, line_num),
+                                                line_num)});
       }
       return MakeTuple(0, args);
     }
@@ -137,7 +138,7 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
             std::cerr << std::endl;
             exit(-1);
           }
-          auto field_t = state->heap[*field_address];
+          auto field_t = state->ReadFromMemory(*field_address, e->line_num);
           auto new_e = MakeIndex(e->line_num, res.exp, MakeInt(e->line_num, i));
           return TCResult(new_e, field_t, res.types);
         }
@@ -164,13 +165,13 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
                       << arg->first << std::endl;
             exit(-1);
           }
-          arg_expected = state->heap[*expected_field];
+          arg_expected = state->ReadFromMemory(*expected_field, e->line_num);
         }
         auto arg_res =
             TypeCheckExp(arg->second, new_types, values, arg_expected, context);
         new_types = arg_res.types;
         new_args->push_back(std::make_pair(arg->first, arg_res.exp));
-        arg_types->push_back({arg->first, AllocateValue(arg_res.type)});
+        arg_types->push_back({arg->first, state->AllocateValue(arg_res.type)});
       }
       auto tuple_e = MakeTuple(e->line_num, new_args);
       auto tuple_t = MakeTupleVal(arg_types);
@@ -207,7 +208,9 @@ auto TypeCheckExp(const Expression* e, TypeEnv types, Env values,
             if (*e->u.get_field.field == field.first) {
               auto new_e =
                   MakeGetField(e->line_num, res.exp, *e->u.get_field.field);
-              return TCResult(new_e, state->heap[field.second], res.types);
+              return TCResult(new_e,
+                              state->ReadFromMemory(field.second, e->line_num),
+                              res.types);
             }
           }
           std::cerr << e->line_num << ": compilation error, struct "
@@ -677,11 +680,11 @@ auto FunctionDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
 
 auto StructDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
   auto st = TypeOfStructDef(&definition, tops.types, tops.values);
-  Address a = AllocateValue(st);
+  Address a = state->AllocateValue(st);
   tops.values.Set(Name(), a);  // Is this obsolete?
   auto field_types = new std::vector<std::pair<std::string, Address>>();
   for (const auto& [field_name, field_value] : *st->u.struct_type.fields) {
-    field_types->push_back({field_name, AllocateValue(field_value)});
+    field_types->push_back({field_name, state->AllocateValue(field_value)});
   }
   auto fun_ty = MakeFunTypeVal(MakeTupleVal(field_types), st);
   tops.types.Set(Name(), fun_ty);
@@ -694,7 +697,7 @@ auto ChoiceDeclaration::TopLevel(TypeCheckContext& tops) const -> void {
     alts->push_back(std::make_pair(a.first, t));
   }
   auto ct = MakeChoiceTypeVal(name, alts);
-  Address a = AllocateValue(ct);
+  Address a = state->AllocateValue(ct);
   tops.values.Set(Name(), a);  // Is this obsolete?
   tops.types.Set(Name(), ct);
 }

+ 12 - 16
executable_semantics/interpreter/value.cpp

@@ -213,7 +213,14 @@ auto MakeChoiceTypeVal(std::string name,
   return v;
 }
 
-void PrintValue(const Value* val, std::ostream& out) {
+auto State::PrintAddress(Address a, std::ostream& out) -> void {
+  if (!this->alive[a]) {
+    out << "!!";
+  }
+  PrintValue(this->heap[a], out);
+}
+
+auto PrintValue(const Value* val, std::ostream& out) -> void {
   switch (val->tag) {
     case ValKind::AltConsV: {
       out << *val->u.alt_cons.choice_name << "." << *val->u.alt_cons.alt_name;
@@ -227,7 +234,7 @@ void PrintValue(const Value* val, std::ostream& out) {
     case ValKind::AltV: {
       out << "alt " << *val->u.alt.choice_name << "." << *val->u.alt.alt_name
           << " ";
-      PrintValue(state->heap[val->u.alt.argument], out);
+      state->PrintAddress(val->u.alt.argument, out);
       break;
     }
     case ValKind::StructV: {
@@ -246,7 +253,7 @@ void PrintValue(const Value* val, std::ostream& out) {
         }
 
         out << elt.first << " = ";
-        PrintValue(state->heap[elt.second], out);
+        state->PrintAddress(elt.second, out);
         out << "@" << elt.second;
       }
       out << ")";
@@ -336,8 +343,8 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool {
         if (t2_field == std::nullopt) {
           return false;
         }
-        if (!TypeEqual(state->heap[(*t1->u.tuple.elts)[i].second],
-                       state->heap[*t2_field])) {
+        if (!TypeEqual(state->ReadFromMemory((*t1->u.tuple.elts)[i].second, 0),
+                       state->ReadFromMemory(*t2_field, 0))) {
           return false;
         }
       }
@@ -363,8 +370,6 @@ auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool {
     case ValKind::BoolV:
       return v1->u.boolean == v2->u.boolean;
     case ValKind::PtrV:
-      CheckAlive(v1->u.ptr, line_num);
-      CheckAlive(v2->u.ptr, line_num);
       return v1->u.ptr == v2->u.ptr;
     case ValKind::FunV:
       return v1->u.fun.body == v2->u.fun.body;
@@ -402,13 +407,4 @@ auto ToInteger(const Value* v) -> int {
   }
 }
 
-void CheckAlive(Address address, int line_num) {
-  if (!state->alive[address]) {
-    std::cerr << line_num << ": undefined behavior: access to dead value ";
-    PrintValue(state->heap[address], std::cerr);
-    std::cerr << std::endl;
-    exit(-1);
-  }
-}
-
 }  // namespace Carbon

+ 0 - 1
executable_semantics/interpreter/value.h

@@ -160,7 +160,6 @@ auto TypeEqual(const Value* t1, const Value* t2) -> bool;
 auto ValueEqual(const Value* v1, const Value* v2, int line_num) -> bool;
 
 auto ToInteger(const Value* v) -> int;
-void CheckAlive(Address a, int line_num);
 
 }  // namespace Carbon
 

+ 21 - 0
executable_semantics/testdata/experimental_continuation9.6c

@@ -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
+
+// Test that the read from x triggers an error because x is dead.
+// This test also demonstrates how by-reference free-variable capture
+// is dangerous and can happen inside continuations.
+
+fn capture() -> __Continuation {
+  var Int: x = 1;
+  __continuation k {
+    var Int: y = x;
+  }
+  return k;
+}
+
+fn main() -> Int {
+  var __Continuation: k = capture();
+  __run k; // error, lifetime of x is over
+  return 0;
+}

+ 2 - 0
executable_semantics/testdata/experimental_continuation9.golden

@@ -0,0 +1,2 @@
+12: undefined behavior: access to dead value 1
+EXIT CODE: 255