镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

console.log should *not* be a constructor #25987

Description

@lachrist
  • Version: v11.9.0
  • Platform: Darwin Laurents-MacBook-Pro-2.local 17.7.0 Darwin Kernel Version 17.7.0: Fri Nov 2 20:43:16 PDT 2018; root:xnu-4570.71.17~1/RELEASE_X86_64 x86_64
  • Subsystem:

In node, console.log behaves like a constructor without a prototype field. Other runtimes throw a proper type error in both lines below:

$ node
> new console.log();

consoleCall {}
> Reflect.construct(Boolean, [], console.log);
[Boolean: false]

Much love,
Laurent

Activity

  1. added
    consoleIssues and PRs related to the console subsystem.
    on Feb 7, 2019
  2. TimothyGu commented on Feb 7, 2019

    @TimothyGu
    Member

    Essentially we should create the log function as a method (({ log(...args) { … } })) or as an arrow function.

  3. Hakerh400 commented on Feb 9, 2019

    @Hakerh400
    Contributor

    Console methods can't be arrow functions, since they are bound to a Console instance. Defining them as methods should work. Also, if inspector is attached, all methods are wrapped, thus something like this is also needed:

    diff --git a/src/inspector_js_api.cc b/src/inspector_js_api.cc
    index 48ebd73817..a7d72bd327 100644
    --- a/src/inspector_js_api.cc
    +++ b/src/inspector_js_api.cc
    @@ -146,6 +146,12 @@ void CallAndPauseOnStart(const FunctionCallbackInfo<v8::Value>& args) {
     }
    
     void InspectorConsoleCall(const FunctionCallbackInfo<Value>& info) {
    +  Isolate* iso = Isolate::GetCurrent();
    +  if (info.IsConstructCall()) {
    +    iso->ThrowException(v8::Exception::TypeError(String::NewFromUtf8(
    +        iso, "Console methods are not constructors")));
    +    return;
    +  }
       Environment* env = Environment::GetCurrent(info);
       Isolate* isolate = env->isolate();
       Local<Context> context = isolate->GetCurrentContext();

    I would like to open a PR if this looks good.

  4. addaleax commented on Feb 9, 2019

    @addaleax
    Member

    @Hakerh400 I think rather than us throwing an exception, we could use V8’s ability to prevent constructor behaviour altogether – you can search around for ConstructorBehavior::kThrow in our source code to see how that’s done. (That also has nice properties like not creating a .prototype property on the function.)

    More generally, there is a TODO comment that might address this in a blanket fashion for all C++-backed JS methods exposed by Node:

    node/src/env-inl.h

    Lines 750 to 756 in c2d374f

    v8::Local<v8::Function> function =
    NewFunctionTemplate(callback, v8::Local<v8::Signature>(),
    // TODO(TimothyGu): Investigate if SetMethod is ever
    // used for constructors.
    v8::ConstructorBehavior::kAllow,
    v8::SideEffectType::kHasSideEffect)
    ->GetFunction(context)

    Changing kAllow to kThrow would do the trick here. The TODO comment expresses some doubt about this always being correct, but from a quick look through the usage of SetMethod() and SetMethodNoSideEffect(), I think it is okay, because we never use these helpers to set up constructors.

  5. lachrist commented on Feb 9, 2019

    @lachrist
    Author

    Yeah, throwing an error if console.log is called as a constructor is a partial solution. It will not throw if used as a new.target e.g.: Reflect.construct(Boolean, [], console.log). Plus, it will allow the construct proxy handler to be called:

    var p = new Proxy(console.log, {
      construct: () => {
        throw new Error("This should never be called");
      }
    });
    new p();
  6. jdalton commented on Feb 9, 2019

    @jdalton
    Member

    Is this covered by the console spec?

  7. lachrist commented on Feb 10, 2019

    @lachrist
    Author

    Maybe not. But, in the ECMAScript standard, primordials have a consistent constructor / non-constructor behaviour. I've made a script that reveals all the primordials that do not have a consistent constructor behavior. It only print the functions of the console API. The only other exception is Proxy which makes sense as proxies do not have an internal [[Prototype]] field. Why not normalise the console API as the other runtimes do?

    global.console._stdout._writableState.onwrite
    global.console._stdout._writableState.corkedRequestsFree.finish
    global.console._stderr._writableState.onwrite
    global.console._stderr._writableState.corkedRequestsFree.finish
    global.console.log
    global.console.debug
    global.console.info
    global.console.dirxml
    global.console.warn
    global.console.error
    global.console.dir
    global.console.time
    global.console.timeEnd
    global.console.timeLog
    global.console.trace
    global.console.assert
    global.console.clear
    global.console.count
    global.console.countReset
    global.console.group
    global.console.groupCollapsed
    global.console.groupEnd
    global.console.table
    global.Proxy
    
    const done = new Set();
    const isconstructor = (value) => {
      try {
        Reflect.construct(Boolean, [], value);
        return true;
      } catch (error) {
        return false;
      }
    };
    const loop = (value, path) => {
      if (value !== null && (typeof value === "object" || typeof value === "function")) {
        if (!done.has(value)) {
          done.add(value);
          if (isconstructor(value) && !Reflect.getOwnPropertyDescriptor(value, "prototype"))
            console.log(path);
          Reflect.ownKeys(value).forEach((key) => {
            const descriptor = Reflect.getOwnPropertyDescriptor(value, key);
            if ("value" in descriptor) {
              loop(descriptor.value, path+"."+String(key));
            } else {
              loop(descriptor.get, path+"."+String(key)+"[get]");
              loop(descriptor.set, path+"."+String(key)+"[set]");
            }
          });
          loop(Reflect.getPrototypeOf(value, path+".[__proto__]"));
        }
      }
    };
    loop(global, "global");
  8. jdalton commented on Feb 10, 2019

    @jdalton
    Member

    @lachrist

    Maybe not. But,

    Ah okay. We should ping folks involved in that effort to see about covering any gap:
    \cc @domfarolino

  9. TimothyGu commented on Feb 13, 2019

    @TimothyGu
    Member

    The Console Standard does not cover it directly, but by virtue of the method being defined through Web IDL, Web IDL rules apply – and they are that the method should not be a constructor.

  10. Hakerh400 commented on Feb 13, 2019

    @Hakerh400
    Contributor

    @addaleax Thanks for the suggestion. With kThrow it seems to work. The patch is made in f084076. If it looks ok, I think it's ready for a PR.

  11. addaleax commented on Feb 13, 2019

    @addaleax
    Member

    @Hakerh400 The changes look okay, but I would suggest making separate PRs for the changes to the console source code and to env-inl.h, respectively; even if they have the same goal, they have pretty different characteristics in terms of what and how they are changing things.

  12. Hakerh400 commented on Mar 16, 2019

    @Hakerh400
    Contributor

    This can probably be closed as fixed.

  13. addaleax commented on Mar 16, 2019

    @addaleax
    Member

    @Hakerh400 Changing ConstructorBehavior::kAllow to ConstructorBehavior::kThrow in general is still outstanding here. I’ve opened #26700 to address that.

  14. TimothyGu commented on May 24, 2019

    @TimothyGu
    Member

    Fixed in #26700.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    consoleIssues and PRs related to the console subsystem.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions