New Proposal: ES Call Stack

# Ranando King (5 years ago)

ES used to have Function.caller for traversing the call stack, but apparently this was very problematic for engine development and prevented various optimizations. So now it doesn't work in strict mode which makes it useless for code involving "use strict" or class. One of the main use cases for such functionality was being able to determine where a given function was called from.

I was thinking of a global static class that is able to uniquely identify each execution context on the call stack without giving any access to the context objects themselves. I was thinking of maybe something like this:

class CallStackEntry {
  #className;    //Name of the class or null
  #functionName; //Name of the function or null
  #fileName;     //Name of the source file or null
  #line;         //Source line number
  #offset;       //Source character offset on the line
  #id;           //Symbol
  get fileName() { return this.#filename; }
  get line() { return this.#line; }
  get offset() { return this.#offset; }
  get id() { return this.#id; }
  constructor(_fileName, _line_, _offset, _id) {
    this.#fileName = _fileName;
    this.#line = _line;
    this.#offset = _offset;
    this.#id = _id;
  }
}

class CallStack {
  static #callStack = []; //Internally managed
  static get stackLimit() {...}
  static set stackLimit(v) {...}
  static get stack() {
    //return the call stack as an array of CallStackEntry objects
  }
}

With something like this, security-type software would be able to clearly identify functions without granting any access rights to the corresponding function.

# Isiah Meadows (5 years ago)

You may be interested in this: tc39/proposal

# Isiah Meadows (5 years ago)

As for security, scope control is usually the more important thing to monitor. That's covered in realms for the most part, but also a few other things. Stack traces don't play a major part here, and whenever untrusted calls need made and tracked through the stack, it's not that hard to just save and restore global data as needed.

# Ranando King (5 years ago)

That's all fine and dandy until you're dealing with a project that has a "no globals" mandate. The problem that I'd want to be able to solve is one of gaining and losing control during a single function's execution. For example: say function A is privileged, but function B is not. Function A calls function B with some data that B should only partially be able to access. Right now, that's hardly possible. There's no way for the data being passed to know if it is ok for B to use those functions or properties. If it was possible for the data methods to peek at the stack and recognize that the current function isn't privileged, then the problem is solved.

I know class-fields will make this a little easier, but it doesn't solve the problem, especially when there's a "no constructors" mandate.

# Jordan Harband (5 years ago)

"who calls function B" has, and must have, no bearing on how function B behaves. Every single call site that passes identical (Object.is) arguments must behave the same. Am I misunderstanding what you mean?

# Isiah Meadows (5 years ago)

You could substitute a function parameter accepting an object here or a class instance for globals to dodge this. Doesn't change my point.

Here's how you deal with permissions like that: have A know what data B is allowed to access, then pass B an object with only that data. If necessary, make the exposed methods form a closure over the relevant private data.

# Ranando King (5 years ago)

We agree trying to learn about the caller should not affect how the caller works. That's why I was thinking more on the lines of an "id" carried around by every function object. As long as that "id" uniquely identified the function without providing any access to the function itself, it would satisfy my needs as well as TC39 and engine builder constraints.

# Ranando King (5 years ago)

Isiah, that works when you're in full control of all the code. However, when you're writing B, and A is 3rd party (or the reverse in DI cases) then the API is out of your control. In cases like that, it's better if the data itself knew the restrictions. That keeps dependencies low.