Hashing of Window objects is broken in WeakMap/Map
Reported by
hdd...@gmail.com,
Aug 3 2017
|
|||||||||
Issue descriptionUserAgent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0 Steps to reproduce the problem: 1. Allow popups 2. Open https://jsfiddle.net/769a0qmu 3. Open the Developer Tools Console 4. Run the demo (may take a few attempts) The code in the demo: var map = new WeakMap(); var popup = window.open("https://google.com"); map.set(popup, "data"); window.setTimeout(function() { console.log(map.get(popup)); map.set(popup, "modified"); console.log(map.get(popup)); }, 3000); What is the expected behavior? A new tab will open with Google Search, and the console will display: data modified What went wrong? A new tab opens with Google Search as expected, but the console displays: undefined modified In Chrome 62.0.3175.2 (canary) the output is different, but also wrong: data data In Firefox the output is correct. Did this work before? N/A Chrome version: 60.0.3112.78 Channel: stable OS Version: 10 Flash Version: 26.0.0.137 This bug was originally discovered by a user on Stack Overflow (https://stackoverflow.com/questions/45395639/key-equality-of-es6-map-behaves-oddly-in-chrome-content-script/). The original bug discovered was with Map#has and Map#get. Demo: https://jsfiddle.net/hvqab05n/ Code: var map = new Map(); var popup = window.open("https://google.com"); map.set(popup, "data"); window.setTimeout(function() { console.log(map.has(popup)); console.log(map.get(popup)); }, 3000); Expected output: true data Output on Chrome 60.0.3112.78: false undefined In Chrome 62.0.3175.2 (canary) the output is correct. The method Map#has was unwittingly fixed by the following commit in V8: https://chromium.googlesource.com/v8/v8/+/19f7cc497abf309fe52017d7363798aa0a74f0f8 [Collections] Port Map.p.has to CSA/C++ The change was added to Chromium in https://chromium.googlesource.com/chromium/src/+/249d58471e61e05bec81b1b1a757052d7a5d702a Update V8 to version 6.1.58. This points to the JavaScript function GetHash() as the culprit. The function generates a random hash and saves it as a property. From MDN (https://developer.mozilla.org/en-US/docs/Web/API/Window/open#Description), the Window object returned by open() initially contains about:blank, and the real URL is only loaded after the current script finishes executing. When the real URL is loaded, the same-origin policy restricts access to the Window object. My guess is that the saved hash becomes inaccessible and so a different hash is produced. The C++ version does not have this problem, but there are still somes uses of GetHash() in Chrome Canary (e.g. WeakMap#set).
,
Aug 4 2017
Able to reproduce the issue on Windows 10, Ubuntu 14.04 and Mac 10.12.6 using chrome reported version #60.0.3112.78 and latest canary #62.0.3176.0. Bisect Information: ===================== Good build: 59.0.3049.0 Revision(425838) Bad Build : 59.0.3050.0 Revision(426105) Change Log URL: https://chromium.googlesource.com/chromium/src/+log/f70d58032706670f094ed7ae73c2bb13259fc0b6..a94ee9eb38e2003712667dfe217a5cb1a1763dd0 From the above change log suspecting below change Review URL: https://codereview.chromium.org/2760793002 dcheng@ - Could you please check whether this is caused with respect to your change, if not please help us in assigning it to the right owner. Note: In Chrome 62.0.3175.2 (canary) the output is different, but also wrong: (as per reporter) data data Thanks...!!
,
Aug 4 2017
,
Aug 16 2017
Sathya, could you take a look at this issue?
,
Aug 16 2017
I've verified that this is fixed on ToT. FWIW, Comment#2 seems to contradict the reporter though (who reported it to be working on Chrome 62.0.3175.2).
,
Aug 17 2017
The repro case (written in very first place of the original report) is still reproducible. The repro case is as follows:
var map = new WeakMap();
var popup = window.open("https://google.com");
map.set(popup, "data");
window.setTimeout(function() {
console.log(map.get(popup));
map.set(popup, "modified");
console.log(map.get(popup));
}, 3000);
The other case (map.has / map.get) seems working as expected, though. The other case is as follows:
var map = new Map();
var popup = window.open("https://google.com");
map.set(popup, "data");
window.setTimeout(function() {
console.log(map.has(popup));
console.log(map.get(popup));
}, 3000);
Sathya, could you take another look?
,
Aug 18 2017
FYI, I'm seeing the attached screenshot. I've synched to git commit = e192006f532ff3a9a765098cbe0cbc2de008d514 chrome://chrome says "Version 62.0.3190.0 (Developer Build) (64-bit)" Platform is GNU/Linux (Goobuntu) -------- args.gn -------- is_debug = false is_component_build = true enable_nacl = false use_goma = true goma_dir = "(snip)" -------------------------
,
Aug 18 2017
Ah, I was testing content_shell. content_shell seems to exhibit the correct behavior but not chrome. Do you know if there is any difference between chrome and content_shell in handling of the window object?
,
Aug 18 2017
,
Aug 18 2017
The issue in V8 seems to be that globals created for a remote context return an empty string for %_ClassOf(). This causes us to go down the wrong path when looking for hash codes for such objects. In content_shell, %_ClassOf(popup) returns "global", as expected (I'm not yet clear on why chrome and content_shell differ here, hopefully dcheng can answer that)
,
Aug 18 2017
From a chat with dcheng, if I pass "--site-per-process" to content_shell I get the chrome behavior. So it looks like this is always broken when NewRemoteContext is used.
,
Aug 18 2017
Here's what's going on inside V8. First, note from the original stack overflow report that this used to be broken in Map, but isn't anymore. Map and WeakMap used to use exactly the same logic to get the hash code for an object. That code looks like this (it's written in internal v8 JS):
```
function GetExistingHash(key) {
if (IS_RECEIVER(key) && !IS_PROXY(key) && !IS_GLOBAL(key)) {
var hash = GET_PRIVATE(key, hashCodeSymbol);
return hash;
}
return %GenericHash(key);
}
```
Note the "IS_GLOBAL(key)". That's a macro which expands to "%_ClassOf(key) == 'global'". Since the RemoteContext-created global object returns empty string, it fails this check and so we incorrectly try to use the normal object mechanism for dealing with hashcodes, rather than the Global-specific logic (which is handled in the C++ runtime function %GenericHash().
Over the last couple of months, v8 has moved its implementation of the Map methods out of JS, which means they're no longer sharing the above logic with WeakMap. In particular, the decision about how to find the hash code always goes directly to %GenericHash, which uses a different mechanism (an instance type check) on the key to see how to handle the hash code. This method gets the right answer for the RemoteContext case, and thus uses the special "hash" field on JSGlobalProxy (https://cs.chromium.org/chromium/src/v8/src/objects.h?rcl=07259a9ceafa078c9bb7f9ee1bb6f2d67256cc80&l=5351), as intended.
All that said, there's the question of what to do to fix the WeakMap case. There's ongoing work inside v8 that will eventually make this go away (analogous to how Map got fixed), but that's probably on the order of weeks until it's fixed. If we'd like a fix sooner, we could try to hack things to make %_ClassOf return "global", but it's not yet clear to me how much work that'd be.
Given that this has been broken for two stable releases already, I'm inclined to just wait for it to be fixed on the v8 side. I've tentatively lowered priority and marked this as blocked on the v8 bug, but I'm open to doing something sooner if it seems worthwhile.
,
Aug 18 2017
Discussed offline with adamk@. I think this is just an oversight of the remote context initialization path.
,
Aug 24 2017
Actually this was fixed in 01c82f9cab010b390f9ad15de9edf1fee44b972e; local testing indicates that we now properly deal with the hash. Still might be nice for someone to write a web platform test for it, but the actual fix is done.
,
Aug 24 2017
|
|||||||||
►
Sign in to add a comment |
|||||||||
Comment 1 Deleted