Don't assume getters are effect-free in eager evaluation
Categories
(DevTools :: Console, task)
Tracking
(firefox111 fixed)
Tracking | Status | |
---|---|---|
firefox111 | --- | fixed |
People
(Reporter: nchevobbe, Assigned: arai)
References
(Blocks 1 open bug)
Details
Attachments
(9 files)
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review |
At the moment, we assume that getters are always effect-free in eager evaluation:
dbg.onNativeCall = (callee, reason) => {
...
// Getters are never considered effectful, and setters are always effectful.
...
reason == "get" ||
It is simply wrong, and we shouldn't do that.
As for native functions, let's try to generate the list of safe getters from WebIdls (using the Pure
tag), and consume that list from eager evaluation.
Assignee | ||
Updated•2 years ago
|
Assignee | ||
Comment 1•2 years ago
|
||
Looking at Window
interface, only a few attributes have Pure
tag, and almost all doesn't.
for example, Window.name
is not tagged Pure
.
So, if we allow only attributes with Pure
, window.name
will be disallowed in eager evaluation.
is it acceptable?
/*sealed*/ interface Window : EventTarget {
...
[Throws] attribute DOMString name;
btw, according to the following comment, Pure
sounds too strict.
- isPure is only True for idempotent functions, so it is not valid for
things like keys, values, etc. that return a new object every time.
we don't have to require the getter to be idempotent.
returning new object is fine in eager evaluation, as long as it doesn't affect existing objects.
Reporter | ||
Comment 2•2 years ago
|
||
(In reply to Tooru Fujisawa [:arai] from comment #1)
Looking at
Window
interface, only a few attributes havePure
tag, and almost all doesn't.for example,
Window.name
is not taggedPure
.
So, if we allow only attributes withPure
,window.name
will be disallowed in eager evaluation.
is it acceptable?
I don't think this is too much of a big deal if some things don't get eager evaluated anymore. I'm a bit more concerned about what it means for Bug 1806596 , where I was hoping to get rid of our special _findSafeGetters
implementation to switch to eager evaluation (e.g. , at the moment, logging window
in the console and expanding it, and its <default properties>
node, name
does show up evaluated).
btw, according to the following comment,
Pure
sounds too strict.- isPure is only True for idempotent functions, so it is not valid for things like keys, values, etc. that return a new object every time.
we don't have to require the getter to be idempotent.
returning new object is fine in eager evaluation, as long as it doesn't affect existing objects.
Would there be a suitable tag for this? Or if not, is this something we could detect when doing the eager evaluation somehow ?
Assignee | ||
Comment 3•2 years ago
|
||
I don't find any tag that directly means "has side effect".
All attributes without any special tag are all marked "affects == Everything", but I don't think that's really true for getters.
Perhaps all DOM-related attribute getters are considered side-effect free?
If that's enforced for all specs, we can just enumerate all getters from all DOM-related interfaces and add them to allowlist, so that almost all cases keeps working in the same way,
while we can prevent other random native functions (such like lazy module importer) getting called via getter in eager evaluation.
Assignee | ||
Comment 4•2 years ago
|
||
looks like we can assume all (or almost all) DOM-related getters are side-effect free, in term of eager evaluation.
getters can be lazy and the first invocation can perform some initialization and cache handling, but that's not observable.
so, I'll try adding all of them to allowlist.
we can revisit the decision whenever we found an unexpected side effect.
Assignee | ||
Comment 5•2 years ago
|
||
[Affects]
extended attribute seems to be what we want.
Both Firefox and Chromium use it to annotate whether the attribute getter has JS observable side effect.
https://firefox-source-docs.mozilla.org/dom/webIdlBindings/index.html#affects
Used for a method or attribute getter to indicate what sorts of state can be affected when the function is called.
[Affects=Nothing] indicates that a function must not produce JS-observable side effects, while [Affects=Everything] indicates that a function may produce JS-observable side effects.
Then there's difference about the default value between Firefox and Chromium.
Firefox uses "Affects=Everything" as default value for attribute getter, but Chromium uses "Affects=Nothing" as default value for attribute getter.
class IDLAttribute(IDLInterfaceMember):
def __init__(
...
self.affects = "Everything"
When neither [Affects=Nothing] nor [Affects=Everything] is specified, the default for operations is [Affects=Everything], while for attributes it's [Affects=Nothing].
I think we'd better looking into the default value here, given that affects JIT performance.
If the default value for attribute getter can be Affects=Nothing
, we can use it here as well.
Assignee | ||
Comment 6•2 years ago
|
||
Another question about the inferfaces.
Currently only limited interfaces are scanned by GenerateDataFromWebIdls.py
, which does not include Window
.
So, all getters on Window
will be treated as "has side effect".
should we expand the target interfaces list?
# This is an explicit list of interfaces to load [Pure] and [Constant]
# annotation for. There are a bunch of things that are pure in other interfaces
# that we don't care about in the context of the devtools.
PURE_INTERFACE_ALLOWLIST = set(
[
"Document",
"Node",
"DOMTokenList",
"Element",
"Performance",
"URLSearchParams",
"FormData",
"Headers",
]
)
Assignee | ||
Comment 7•2 years ago
|
||
it turns out that if we don't have Window
on the list, document.getElementsByTagName("a")
also doesn't work on eager evaluation because of document
getter on Window
, which is considered effectful.
I'll try adding Window
's attributes
Comment 8•2 years ago
|
||
(In reply to Tooru Fujisawa [:arai] from comment #5)
When neither [Affects=Nothing] nor [Affects=Everything] is specified, the default for operations is [Affects=Everything], while for attributes it's [Affects=Nothing].
I think we'd better looking into the default value here, given that affects JIT performance.
If the default value for attribute getter can beAffects=Nothing
, we can use it here as well.
Given that Affects=Nothing
can change the actual behaviour of code that's calling the getter, and Affects=Everything
just has an effect on performance, I do not think we'd want to switch the default to Nothing
. We want a safe default value, otherwise we surely will run into subtle and rare bugs where someone forgot to annotate to switch away from the default. It'd be nice to add more annotations, but that does require looking into the getters before adding it.
Comment 9•2 years ago
|
||
(In reply to Tooru Fujisawa [:arai] from comment #7)
document
getter onWindow
, which is considered effectful.
This is a good example of why the default value that we currently have makes sense. The document
getter is quite complicated, you'd have to make sure it doesn't have side-effects.
Assignee | ||
Comment 10•2 years ago
|
||
Okay, let's keep the current Affects
values as is.
Then, there's another issue with Window
interface.
attributes on Window
interface is defined as getter/setter on window
object, not on prototype or anywhere (am I correct?)
Object.getOwnPropertyDescriptor(window, "document")
returns the descriptor but Object.getOwnPropertyDescriptor(Window.prototype, "document")
doesn't.
This mean we need window
object to get the function reference, and to my understanding that's not available on sandbox we use below:
const sandbox = Cu.Sandbox(
Components.Constructor("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
{
invisibleToDebugger: true,
wantGlobalProperties: Object.keys(mergedFunctions),
}
);
I'll look into a way to retrieve an unmodified window
object from eval-with-debugger.js
's context or somewhere.
Comment 11•2 years ago
|
||
(In reply to Tooru Fujisawa [:arai] from comment #10)
attributes on
Window
interface is defined as getter/setter onwindow
object, not on prototype or anywhere (am I correct?)
Yes, that's how [Global]
s work in WebIDL, see https://webidl.spec.whatwg.org/#Global.
Reporter | ||
Comment 12•2 years ago
|
||
(the discussion moved forward and I think that we do want, and need, Window
on the list)
(In reply to Tooru Fujisawa [:arai] from comment #10)
I'll look into a way to retrieve an unmodified
window
object fromeval-with-debugger.js
's context or somewhere.
yes, we have access to it there (e.g. https://searchfox.org/mozilla-central/rev/abf6758ed833c203f84703aa2e3e3d317571b1e9/devtools/server/actors/webconsole/eval-with-debugger.js#529-536)
Assignee | ||
Comment 13•2 years ago
•
|
||
(In reply to Nicolas Chevobbe [:nchevobbe] from comment #12)
yes, we have access to it there (e.g. https://searchfox.org/mozilla-central/rev/abf6758ed833c203f84703aa2e3e3d317571b1e9/devtools/server/actors/webconsole/eval-with-debugger.js#529-536)
Thanks.
so far, I can get the window
reference, but it's the debuggee's window, and at the point of creating the debugger, the window's properties can already be modified.
(document
property is not configurable, but for example performance
property is configurable)
So, we cannot add the those getters to allowlist.
I'll see if I can get unmodified getter values via Xray or something from the window
.
if that doesn't work, the other options I can think of are:
- use some other window, such as browser window itself
- question: is there any case that devtools debugger gets initialized without any browser window?
- add special function to return
window
's all getters - add special function that checks if given function is window getters
- or maybe just check if given function is safe?
Assignee | ||
Comment 14•2 years ago
|
||
the current mechanism (compare the native function) doesn't work for DOM getters, because getters share common implementation GenericGetter
.
template <typename ThisPolicy, typename ExceptionPolicy>
bool GenericGetter(JSContext* cx, unsigned argc, JS::Value* vp) {
So, for DOM getters, we cannot use the native function pointers as identity, but need to extract JSJitInfo
and compare the referred functions.
bool DebuggerObject::isSameNative(JSContext* cx, Handle<DebuggerObject*> object,
Reporter | ||
Comment 15•2 years ago
|
||
(In reply to Tooru Fujisawa [:arai] from comment #13)
if that doesn't work, the other options I can think of are:
- use some other window, such as browser window itself
- question: is there any case that devtools debugger gets initialized without any browser window?
Not sure if I understand this properly, but, looking around, it looks like we always call addAllGlobalsAsDebuggees
(or addDebuggee(global)
) after instantiating a Debugger
object.
Assignee | ||
Comment 16•2 years ago
|
||
thanks :)
actually, it looks like I can get the expected functions via Xray.
I'm preparing a patch stack now.
Assignee | ||
Comment 17•2 years ago
|
||
I bumped issues that window.location
isn't marked as "Pure", and also "Location" interface's attributes are not also marked as "Pure".
Some testcase expects window.location.href
to be eager-evaluate-able.
I guess adding some extra special cases for them would be okay?
Assignee | ||
Comment 18•2 years ago
|
||
Given DOM accessors share single native function, existing isSameNative cannot
distinguish 2 accessors.
Each accessor have its own JSJitInfo with native function pointer to the
actual getter implementation.
isSameNativeWithJitInfo checks if the JSJitInfo also matches.
Depends on D168334
Assignee | ||
Comment 19•2 years ago
|
||
In order to add getters to allowlist, debugger should use isSameNativeWithJitInfo
instead of isSameNative, to distinguish 2 getters with shared native function.
Depends on D168335
Assignee | ||
Comment 20•2 years ago
|
||
Depends on D168336
Assignee | ||
Comment 21•2 years ago
|
||
Also refactor the allowlist structure to represent static vs prototype and
methods vs getters clearly.
And also moved the pre-defined pure methods list into GenerateDataFromWebIdls.py
for simplicity.
Depends on D168337
Assignee | ||
Comment 22•2 years ago
|
||
Add support for [Global] extended attribute and methods/getters on instances.
This retrieves the Xray getter function of the given eval global, so that even
if some configurable properties are modified by the web content, debugger
can see the original getters, and pass them to isSameNativeWithJitInfo.
Depends on D168338
Assignee | ||
Comment 23•2 years ago
|
||
Location related accessors are not marked Pure, but some testcase expects
them to be eagerly evaluateable.
Depends on D168339
Assignee | ||
Comment 24•2 years ago
|
||
Instead of treating all getters non-effectful, apply the filter also on
getter calls.
Depends on D168340
Assignee | ||
Comment 25•2 years ago
|
||
Depends on D168341
Comment 26•2 years ago
|
||
Comment 27•2 years ago
|
||
Backed out for causing dt failures in devtools/client/webconsole/test/browser/browser_console_evaluation_context_selector.js
Backout link: https://hg.mozilla.org/integration/autoland/rev/c4af9a62fcca732c7d39ea8bc8cdd36c37c4b046
Assignee | ||
Comment 28•2 years ago
|
||
Overlooked browser_console_evaluation_context_selector.js
testcase, which tests eager-evaluation in worker.
setInputValue(hud, "globalThis.location.href");
This requires adding WorkerGlobalScope
and WorkerLocation
into allowlist,
interface WorkerGlobalScope : EventTarget {
interface WorkerLocation {
but this is problematic because Sandbox
cannot be created on worker thread, and we cannot create allowlist by retrieving objects from Sandbox
.
possible workaround is to use Xray functions also for prototype/static methods/getters in worker.
Assignee | ||
Comment 29•2 years ago
|
||
Xray functions cannot be retrieved here because we cannot get prototype object from the worker global via Xray.
I'll go with modifying the testcase not to rely on allowlist in worker, given the allowlist isn't the main focus of the testcase.
Assignee | ||
Comment 30•2 years ago
|
||
Assignee | ||
Comment 31•2 years ago
|
||
Then, if we want to support side-effect free getters in worker, the solution I can think of is to stop using sandbox, and create ChromeUtils
method that takes function and checks if given function is safe or not.
So that the check doesn't require any preparation, and accessible on all threads.
Comment 32•2 years ago
|
||
Comment 33•2 years ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/3f596908d106
https://hg.mozilla.org/mozilla-central/rev/d849405ab330
https://hg.mozilla.org/mozilla-central/rev/0540b3a181dd
https://hg.mozilla.org/mozilla-central/rev/6b09818fea2d
https://hg.mozilla.org/mozilla-central/rev/f5b8e273c0c7
https://hg.mozilla.org/mozilla-central/rev/ffb56082a8b8
https://hg.mozilla.org/mozilla-central/rev/4f9bb52e32d2
https://hg.mozilla.org/mozilla-central/rev/c34ed738ff6a
https://hg.mozilla.org/mozilla-central/rev/1c021f85cd52
Description
•