Skip to content
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

ES6 constructor returns class instead of object #1496

Closed
eebbing opened this issue Aug 26, 2016 · 10 comments
Closed

ES6 constructor returns class instead of object #1496

eebbing opened this issue Aug 26, 2016 · 10 comments
Labels

Comments

@eebbing
Copy link

eebbing commented Aug 26, 2016

The code example below should log the numbers from 0 up to 500. Instead, the code breaks after about 100 iterations. The issue is that after about 100 iterations the constructor of class B returns the class itself instead of an instance of B.

The fact that this issue only occurs after multiple iterations suggests to me that the bug is somewhere in the JIT engine.

By the way, the error disappears if the method body of A.toB() is replaced by return new B(this);.

I tested the following code in Microsoft Edge 38.14393.0.0 (Microsoft EdgeHTML 14.14393)

var Test = {};

class A {
    constructor(foo) { this.foo = foo; }
    toB() { return new Test.B(this); }
}

class B {
    constructor(bar) { this.bar = bar; }
}

Test.B = B;

for (let i=0; i<500; i++)
{
    const a = new A(i);
    const b = a.toB();

    try
    {
        console.log(b.bar.foo);
    }
    catch (e)
    {
        console.log(e); // e.description -> "Unable to get property 'foo' of undefined or null reference"
        console.log(b); // b -> class B { ... }
        break;
    }
}
@eebbing
Copy link
Author

eebbing commented Aug 27, 2016

For what it's worth, I decided to look a little bit further into it, and was able to reproduce the error with the latest source code. I ran the script below via ch.exe (x64 debug build) with the following arguments:

ch.exe -forceNative -MaxinterpretCount:10 -MaxSimpleJITRunCount:10 E:\path-to\test.js

The script is:

var Test = {};

class A {
    constructor(foo) { this.foo = foo; }
    toB() { return new Test.B(this); }
}

class B {
    constructor(bar) { this.bar = bar; }
}

Test.B = B;

for (let i=0; i<20; i++)
{
    const a = new A(i);
    const b = a.toB();

    try
    {
    WScript.Echo(b.bar.foo);        
    }
    catch (e)
    {
        WScript.Echo(e);
        WScript.Echo(b);
        break;
    }
}

The output of the script is:

0
1
2
3
4
5
6
7
8
9
TypeError: Unable to get property 'foo' of undefined or null reference
class B {
    constructor(bar) { this.bar = bar; }
}

@ianwjhalliday
Copy link
Collaborator

Awesome! Thanks @eebbing, having a simple repro that we can run with ch.exe makes it a lot easier to debug. We'll look into this soon.

@Krovatkin
Copy link
Collaborator

@ianwjhalliday @suwc

not sure if this helps (hopefully, it might), but this testcase can be further simplified by running ../../Build/VcBuild/bin/x86_debug/ch.exe -trace:backend -off:backend:1,3 -dump:irbuilder -dump:lowerer -off:inline -off:simplejit -mic:3 tt.js

to only compile Function A.prototype.toB ( (#1.2), #3)

    s9.var          =  StartCall      2 (0x2).i32                             #000f
    arg2(s10)<4>.var = ArgOut_A       s2[LikelyCanBeTaggedValue_Object].var!, s9.var! #0012
    s11<s15>[UninitializedObject].var = NewScObjectNoCtor  0xXXXXXXXX (FunctionObject [B (#1.3), #4]).var #0015  Bailout: #0015 (BailOutFailedCtorGuardCheck)
    arg1(s12)<0>.var = ArgOut_A       s11<s15>[UninitializedObject].var, arg2(s10)<4>.var! #0015
    s0<s16>[UninitializedObject].var = CallIFixed  0xXXXXXXXX (FunctionObject [B (#1.3), #4]).var, arg1(s12)<0>.var! #0015

NewScObjectNoCtor (which relies on JavascriptOperators::NewScObjectCommon) might return the c-tor function back in the case below (this might be why func B is observed instead of an object)

       if (functionInfo->IsClassConstructor() && !isBaseClassConstructorNewScObject)
        {

Both Inliner and Lower use returnNewScObj flag to decide which object needs to be returned: the one returned by NewScObjectNoCtor or s0<s16>[UninitializedObject].var = CallIFixed 0xXXXXXXXX.

In this case, s0<s16>[UninitializedObject].var = CallIFixed 0xXXXXXXXX seems to construct the correct object (via NewScObjectNoCtorFull) but we still return s11?

IR after GlobOpt

-----------------------------------------------------------------------------
************   IR after GlobOpt (FullJit)  ************
-----------------------------------------------------------------------------
Function A.prototype.toB ( (#1.2), #3)            Instr Count:18

                       FunctionEntry                                          #

BLOCK 0: Out(1)

$L3:                                                                          #
    s1[Object].var  =  Ld_A           0xXXXXXXXX (GlobalObject)[Object].var   #
    s2.var          =  ArgIn_A        prm1<16>.var!                           #0000
    s2[LikelyCanBeTaggedValue_Object].var = StrictLdThis  s2.var!             #0002


  Line   5: return new Test.B(this); }
  Col   13: ^
                       StatementBoundary  #0                                  #0005
    s3<s13>[LikelyCanBeTaggedValue_Object].var = LdRootFld  s6(s1[Object]->Test)<1,m,~-,s?,s?>[LikelyCanBeTaggedValue_Object].var! #0005
                       CheckFixedFld  s7(s3<s13>[LikelyCanBeTaggedValue_Object]->B)<0,m=,++,s13!,s14+,{B(0)=}>.var! #000b  Bailout: #000b (BailOutFailedFixedFieldTypeCheck)
    s9.var          =  StartCall      2 (0x2).i32                             #000f
    arg2(s10)<4>.var = ArgOut_A       s2[LikelyCanBeTaggedValue_Object].var!, s9.var! #0012
    s11<s15>[UninitializedObject].var = NewScObjectNoCtor  0xXXXXXXXX (FunctionObject [B (#1.3), #4]).var #0015  Bailout: #0015 (BailOutFailedCtorGuardCheck)
    arg1(s12)<0>.var = ArgOut_A       s11<s15>[UninitializedObject].var, arg2(s10)<4>.var! #0015
    s0<s16>[UninitializedObject].var = CallIFixed  0xXXXXXXXX (FunctionObject [B (#1.3), #4]).var, arg1(s12)<0>.var! #0015


  Line   5: }
  Col   38: ^
                       StatementBoundary  #1                                  #0024
                       StatementBoundary  #-1                                 #0024
                       Ret            s11<s15>[UninitializedObject].var!      #0024

BLOCK 1: In(0)

@suwc
Copy link

suwc commented Sep 13, 2016

@eebbing @ianwjhalliday @Krovatkin thanks for participating - could you help review the PR pls?

suwc pushed a commit to suwc/ChakraCore that referenced this issue Sep 15, 2016
… hit.

Github issue chakra-core#1496
ES6 constructor returns class instead of object upon constructorCache hit.
constructorCache->ctorHasNoExplicitReturnValue set to 'true' for ES6 class
constructors, therefore opcode 'GetNewScObject' is optimized away in
inliner. Fix by de-asserting Flags_HasNoExplicitReturnValue for ES6 class
constructors.
suwc pushed a commit to suwc/ChakraCore that referenced this issue Sep 15, 2016
… hit.

Github issue chakra-core#1496
ES6 constructor returns class instead of object upon constructorCache hit.
constructorCache->ctorHasNoExplicitReturnValue set to 'true' for ES6 class
constructors, therefore opcode 'GetNewScObject' is optimized away in
inliner. Fix by de-asserting Flags_HasNoExplicitReturnValue for ES6 class
constructors.
suwc pushed a commit to suwc/ChakraCore that referenced this issue Sep 16, 2016
… hit.

Github issue chakra-core#1496
ES6 constructor returns class instead of object upon constructorCache hit.
constructorCache->ctorHasNoExplicitReturnValue set to 'true' for ES6 class
constructors, therefore opcode 'GetNewScObject' is optimized away in
inliner. Fix by de-asserting Flags_HasNoExplicitReturnValue for ES6 class
constructors.
@suwc suwc closed this as completed Sep 19, 2016
@suwc
Copy link

suwc commented Sep 19, 2016

Fix is merged.

@yGuy
Copy link

yGuy commented Feb 2, 2017

As of today with the current Fast Ring version of Edge (40.15025.1000), I am still seeing this issue, though the behavior changed slightly: In my test-case the problem reproduces as soon as I open the developer tools. It does not happen anymore when the tools stay closed. Once they are open, the constructor does not return the instance but the class. Is this expected? Is there a different version of Chakra running once the dev tools are open?

@suwc
Copy link

suwc commented Feb 7, 2017

Tried both (long and short) test cases above as JS code in HTML files, opened through Node http-server in Edge 40.15026.1000.0 with F12 opened. Couldn't repro. Do you have a repro you can share? Thanks!

@michabaur
Copy link

I'm a colleague of @yGuy . We have prepared a repro that works against our public URLs: https://gist.github.com/michabaur/2a627a3e7a5703dbd682b6ef3274e5dc. Just extract the two files, open test.html, and then open the dev tools. For us, this breaks after a few seconds in Edge 40.15025.1000

@suwc
Copy link

suwc commented Feb 10, 2017

@michabaur @yGuy thanks for the test case.
Fix is merged in release/1.4

@joeltine
Copy link

What Edge version/release will contain this fix?

aomarks added a commit to Polymer/polyserve that referenced this issue Apr 11, 2017
Fixes #161. Versions before 40 have a JIT bug where a constructor can
return the class instead of an instance.

See chakra-core/ChakraCore#1496 and
chakra-core/ChakraCore#2532.

Tested with http://jsfiddle.net/0k59gbL8/5/. Fails on 38, passes on 40.
aomarks added a commit to Polymer/polyserve that referenced this issue Apr 11, 2017
Fixes #161. Versions before 40 have a JIT bug where a constructor can
return the class instead of an instance.

See chakra-core/ChakraCore#1496 and
chakra-core/ChakraCore#2532.

Tested with http://jsfiddle.net/0k59gbL8/5/. Fails on 38, passes on 40.
@iZoid iZoid unassigned suwc Sep 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

8 participants
@ianwjhalliday @yGuy @Krovatkin @suwc @michabaur @joeltine @eebbing and others