Gopherjs: Invalid code generated on combination of closures, method values, and pointer receivers.

Created on 14 Mar 2017  路  6Comments  路  Source: gopherjs/gopherjs

After updating go (to 1.8) and gopherjs (to head) my application stopped functioning with the error

Uncaught ReferenceError: timedOut$24ptr is not defined.

I have already tried clearing gopherjs' compile cache, does not make a difference.

After googling i found this gist https://gist.github.com/jancona/441352b86fcd8553fd445adffb2f0a5f
by Jim Ancona, so apparently this is also affecting others.

this is the interesting part of the stack trace:

setRequestCancel    @   main_cms.js:84919
send    @   client.go:247
$packages.net/http.Client.ptr.send  @   client.go:173
$packages.net/http.Client.ptr.Do    @   client.go:595

this is the function that is broken:

    setRequestCancel = function(req, rt, deadline) {
        var $ptr, _tmp, _tmp$1, _tmp$2, _tmp$3, cancel, deadline, didTimeout, doCancel, initialReqCancel, once, req, rt, stopTimer, stopTimerCh, timedOut, timer;
        stopTimer = $throwNilPointerError;
        didTimeout = $throwNilPointerError;
        if ($clone(deadline, time.Time).IsZero()) {
            _tmp = nop;
            _tmp$1 = alwaysFalse;
            stopTimer = _tmp;
            didTimeout = _tmp$1;
            return [stopTimer, didTimeout];
        }
        initialReqCancel = req.Cancel;
        cancel = new $Chan(structType, 0);
        req.Cancel = cancel;
        doCancel = (function $b() {
            var $ptr, _ref, v, v$1, $s, $r;
            /* */ $s = 0; var $f, $c = false; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $ptr = $f.$ptr; _ref = $f._ref; v = $f.v; v$1 = $f.v$1; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:
            $close(cancel);
            _ref = rt;
            /* */ if ($assertType(_ref, ptrType$21, true)[1] || $assertType(_ref, ptrType$14, true)[1]) { $s = 1; continue; }
            /* */ if ($assertType(_ref, canceler, true)[1]) { $s = 2; continue; }
            /* */ $s = 3; continue;
            /* if ($assertType(_ref, ptrType$21, true)[1] || $assertType(_ref, ptrType$14, true)[1]) { */ case 1:
                v = _ref;
                $s = 3; continue;
            /* } else if ($assertType(_ref, canceler, true)[1]) { */ case 2:
                v$1 = _ref;
                $r = v$1.CancelRequest(req); /* */ $s = 4; case 4: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
            /* } */ case 3:
            $s = -1; return;
            /* */ } return; } if ($f === undefined) { $f = { $blk: $b }; } $f.$ptr = $ptr; $f._ref = _ref; $f.v = v; $f.v$1 = v$1; $f.$s = $s; $f.$r = $r; return $f;
        });
        stopTimerCh = new $Chan(structType, 0);
        once = new sync.Once.ptr(new sync.Mutex.ptr(0, 0), 0);
        stopTimer = (function $b() {
            var $ptr, $s, $r;
            /* */ $s = 0; var $f, $c = false; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $ptr = $f.$ptr; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:
            $r = once.Do((function() {
                var $ptr;
                $close(stopTimerCh);
            })); /* */ $s = 1; case 1: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
            $s = -1; return;
            /* */ } return; } if ($f === undefined) { $f = { $blk: $b }; } $f.$ptr = $ptr; $f.$s = $s; $f.$r = $r; return $f;
        });
        timer = time.NewTimer(time.Until($clone(deadline, time.Time)));
        timedOut = 0;
        $go((function $b() {
            var $ptr, _r$1, _selection, timedOut$24ptr, $s, $r;
            /* */ $s = 0; var $f, $c = false; if (this !== undefined && this.$blk !== undefined) { $f = this; $c = true; $ptr = $f.$ptr; _r$1 = $f._r$1; _selection = $f._selection; timedOut$24ptr = $f.timedOut$24ptr; $s = $f.$s; $r = $f.$r; } s: while (true) { switch ($s) { case 0:
            _r$1 = $select([[initialReqCancel], [timer.C], [stopTimerCh]]); /* */ $s = 1; case 1: if($c) { $c = false; _r$1 = _r$1.$blk(); } if (_r$1 && _r$1.$blk !== undefined) { break s; }
            _selection = _r$1;
            /* */ if (_selection[0] === 0) { $s = 2; continue; }
            /* */ if (_selection[0] === 1) { $s = 3; continue; }
            /* */ if (_selection[0] === 2) { $s = 4; continue; }
            /* */ $s = 5; continue;
            /* if (_selection[0] === 0) { */ case 2:
                $r = doCancel(); /* */ $s = 6; case 6: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
                timer.Stop();
                $s = 5; continue;
            /* } else if (_selection[0] === 1) { */ case 3:
                (timedOut$24ptr || (timedOut$24ptr = new ptrType$22(function() { return timedOut; }, function($v) { timedOut = $v; }))).setTrue();
                $r = doCancel(); /* */ $s = 7; case 7: if($c) { $c = false; $r = $r.$blk(); } if ($r && $r.$blk !== undefined) { break s; }
                $s = 5; continue;
            /* } else if (_selection[0] === 2) { */ case 4:
                timer.Stop();
            /* } */ case 5:
            $s = -1; return;
            /* */ } return; } if ($f === undefined) { $f = { $blk: $b }; } $f.$ptr = $ptr; $f._r$1 = _r$1; $f._selection = _selection; $f.timedOut$24ptr = timedOut$24ptr; $f.$s = $s; $f.$r = $r; return $f;
        }), []);
        _tmp$2 = stopTimer;
        _tmp$3 = $methodVal((timedOut$24ptr || (timedOut$24ptr = new ptrType$22(function() { return timedOut; }, function($v) { timedOut = $v; }))), "isSet");
        stopTimer = _tmp$2;
        didTimeout = _tmp$3;
        return [stopTimer, didTimeout];
    };

It looks like timedOut$24ptr is out of scope. It is declared in the $go((function $b() { just above.

Could the issue be related to the deprecation of Cancel in favour of Context in net/http/Request, as in this commit?: https://github.com/gopherjs/gopherjs/commit/0917fa2c47066674e43b89869c341aa9dae28e03

Thanks in advance and cheers, Lex.

bug

Most helpful comment

I was able to reduce it to an even smaller case:

package main

func main() {
    setRequestCancel()
}

func setRequestCancel() (didTimeout func()) {
    var timedOut atomicBool

    _ = func() {
        _ = timedOut.method
    }

    return timedOut.method
}

type atomicBool int32

func (b *atomicBool) method() {} // Pointer receiver.

Running gopherjs run main.go causes:

$ gopherjs run main.go

/Users/Dmitri/Dropbox/Work/2013/GoLand/src/github.com/shurcooL/play/31/main.go.182659065:2336
        didTimeout = $methodVal((timedOut$24ptr || (timedOut$24ptr = new ptrType(function() { return timedOut; }, function($v) { timedOut = $v; }))), "method");
                           ^
ReferenceError: timedOut$24ptr is not defined
    at setRequestCancel (/Users/Dmitri/Dropbox/Work/2013/GoLand/src/github.com/shurcooL/play/31/main.go.182659065:2336:28)
    at main (/github.com/shurcooL/play/31/main.go:4:3)
...

I couldn't make it smaller than that. timedOut.method must be captured in a closure (passing timedOut either by value or pointer no longer reproduces), and timedOut.method must be returned, or else there's no reproduce. Also, if atomicBool.method has a value receiver instead of pointer receiver, it doesn't reproduce.

@neelance Can you take a look? It looks like a compiler bug related to combination of closures, method values, and pointer receivers.

All 6 comments

my application stopped functioning

Is it possible to extract a snippet that can reproduce the issue?

Hi Dmitri, thanx for responding.

I managed to boil down the issue to this:

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {

    apireq, _ := http.NewRequest("GET", "http://httpbin.org/get", nil)

    client := &http.Client{Timeout: time.Second * 60}
    //client := &http.Client{}

    resp, _ := client.Do(apireq)

    fmt.Println(resp.StatusCode) // 200 OK is what we want
}

The problem occurs when passing a Timeout to the client constructor literal.

Hope that helps.

I can reproduce with that, thanks. Very helpful. We need to investigate the cause and fix this.

I was able to reduce it to an even smaller case:

package main

func main() {
    setRequestCancel()
}

func setRequestCancel() (didTimeout func()) {
    var timedOut atomicBool

    _ = func() {
        _ = timedOut.method
    }

    return timedOut.method
}

type atomicBool int32

func (b *atomicBool) method() {} // Pointer receiver.

Running gopherjs run main.go causes:

$ gopherjs run main.go

/Users/Dmitri/Dropbox/Work/2013/GoLand/src/github.com/shurcooL/play/31/main.go.182659065:2336
        didTimeout = $methodVal((timedOut$24ptr || (timedOut$24ptr = new ptrType(function() { return timedOut; }, function($v) { timedOut = $v; }))), "method");
                           ^
ReferenceError: timedOut$24ptr is not defined
    at setRequestCancel (/Users/Dmitri/Dropbox/Work/2013/GoLand/src/github.com/shurcooL/play/31/main.go.182659065:2336:28)
    at main (/github.com/shurcooL/play/31/main.go:4:3)
...

I couldn't make it smaller than that. timedOut.method must be captured in a closure (passing timedOut either by value or pointer no longer reproduces), and timedOut.method must be returned, or else there's no reproduce. Also, if atomicBool.method has a value receiver instead of pointer receiver, it doesn't reproduce.

@neelance Can you take a look? It looks like a compiler bug related to combination of closures, method values, and pointer receivers.

Hi, is this issue fixed?

Hi. I'm using the colly with gopherjs (Go 1.12)

Here's my code -- I have the same issue:

package main

import (
    "github.com/gocolly/colly"
    "github.com/gopherjs/gopherjs/js"
)

func main() {
    c := colly.NewCollector()
    c.OnHTML(`title`, func(element *colly.HTMLElement) {
        title := element.Text
        //println(title)
        js.Global.Get("document").Call("write", title)
    })
    c.Visit("https://github.com/gopherjs/gopherjs")
}
Was this page helpful?
0 / 5 - 0 ratings

Related issues

flimzy picture flimzy  路  3Comments

myitcv picture myitcv  路  6Comments

dmitshur picture dmitshur  路  4Comments

marwan-at-work picture marwan-at-work  路  4Comments

nytehauq picture nytehauq  路  7Comments