Skip to content

CSE Machine: FnValue does not actually refer to its enclosing environment #3127

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
alsonleej opened this issue Apr 11, 2025 · 0 comments
Open

Comments

@alsonleej
Copy link
Contributor

After days of debugging I found a shocking, but terribly subtle, issue.

Image

Image

Having the reference to the current frame be k , I ensured that k has a binding to a FnValue , which has an enclosing environment of k. This is what I expected. This is tested in the console.

However, for the bottom FnValue,

Image

Image

This shows that despite the frame looking identical, there are actually duplicate Frame objects in memory, so the FnValue does not actually always reference its enclosing Frame.

Suspected Cause:

The above means that somewhere in the code, new Frame objects are needlessly re-created instead of referring to the existing one, so environment objects aren't being properly shared/reused, even though they represent the same environment (as indicated by the same ID).

A possible cause would be in deepCopyTree:

export function deepCopyTree(value: EnvTree): EnvTree {
  const clone = cloneDeep(value);
  copyOwnPropertyDescriptors(value, clone);
  return clone;
}

And in copyOwnPropertyDescriptors:

} else if (isEnvironment(source) && isEnvironment(destination)) {
  // TODO: revisit this approach of copying the raw values and descriptors from source,
  // as this defeats the purpose of deep cloning by referencing the original values again.
  // Perhaps, there should be a new deep cloning function that also clones property descriptors.

  // copy descriptors from source frame to destination frame
  Object.defineProperties(destination.head, Object.getOwnPropertyDescriptors(source.head));
  // copy heap from source frame to destination frame as well, to preserve references
  destination.heap = source.heap;
  // recurse on tail
  copyOwnPropertyDescriptors(source.tail, destination.tail);
}

There even seems to be a TODO comment left behind that points out the exact issue! The problem is:

  1. deepCopyTree uses cloneDeep from lodash to create a deep copy
  2. But then it calls copyOwnPropertyDescriptors which:
  • Copies the raw property descriptors from source to destination
  • Directly assigns the source heap to the destination (destination.heap = source.heap)
    This means we end up with a mix of cloned and referenced objects.

This explains why:
The environment IDs are the same (they're copied)
But the environment objects are different (some parts are cloned, others referenced)

To replicate:

In CseMachineLayout.tsx, have a reference to current frame by insert const k = FrameComponent.envFrameMap.get(CseMachine.getCurrentEnvId()); after // initialize control and stash Layout.initializeControlStash(chapter);

Run this in CSE Machine:

    const x = [];
    const a = d => d + 1;
    return a;
}

const result = test_func();
result(5);```




Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant