@@ -3214,20 +3214,31 @@ class Interpreter {
32143214 } break ;
32153215 case Value::OBJECT:
32163216 const auto obj = static_cast <HeapObject *>(scratch.v .h );
3217- // TODO: Do this by constructing a FRAME_INVARIANT here?
3218- runInvariants (f.location , obj);
3217+ const auto loc = f.location ;
3218+
3219+ f.kind = FRAME_OBJECT_TO_JSON;
3220+ f.first = true ;
3221+ f.val = scratch;
3222+ f.str .clear ();
32193223 std::map<UString, const Identifier *> fields;
32203224 for (const auto &field : objectFields (obj, true )) {
32213225 fields[field->name ] = field;
32223226 }
3223- if (fields.empty ()) {
3227+ std::swap (f.manifestFields , fields); // Swap instead of deep copy.
3228+
3229+ // runInvariants re-enters evaluate() so it messes with the stack.
3230+ // Hence we need to make sure that the FRAME_OBJECT_TO_JSON is set up _first_,
3231+ // even if the object is "empty" (no fields to manifest).
3232+ // TODO: Do this by constructing a FRAME_INVARIANT here?
3233+ runInvariants (loc, obj);
3234+
3235+ // fields was already cleared above, and `f` may have been invalidated
3236+ // by the stack-manipulation inside runInvariants. So we need to explicitly
3237+ // look at stack.top().manifestFields.
3238+ if (stack.top ().manifestFields .empty ()) {
32243239 scratch = makeString (U" { }" );
32253240 } else {
3226- f.kind = FRAME_OBJECT_TO_JSON;
3227- f.first = true ;
3228- f.val = scratch;
3229- f.str .clear ();
3230- std::swap (f.manifestFields , fields); // Swap instead of deep copy.
3241+ assert (stack.top ().kind == FRAME_OBJECT_TO_JSON);
32313242 goto replaceframe;
32323243 }
32333244 break ;
0 commit comments