⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 187347 in webkit


Ignore:
Timestamp:
Jul 24, 2015, 11:23:13 AM (11 years ago)
Author:
fpizlo@apple.com
Message:

DFG::safeToExecute() is wrong for MultiGetByOffset, doesn't consider the structures of the prototypes that get loaded from
https://bugs.webkit.org/show_bug.cgi?id=147250

Reviewed by Geoffrey Garen.

This fixes a nasty - but currently benign - bug in DFG::safeToExecute(). That function
will tell you if hoisting a node to some point is safe in the sense that the node will
not crash the VM if it executes at that point. A node may be unsafe to execute if we
cannot prove that at that point, the memory it is loading is not garbage. This is a
necessarily loose notion - for example it's OK to hoist a load if we haven't proved
that the load makes semantic sense at that point, since anyway the place where the node
did get used will still be guarded by any such semantic checks. But because we may also
hoist uses of the load, we need to make sure that it doesn't produce a garbage value.
Also, we need to ensure that the load won't trap. Hence safeToExecute() returns true
anytime we can be sure that a node will not produce a garbage result (i.e. a malformed
JSValue or object pointer) and will not trap when executed at the point in question.

The bug is that this verification isn't performed for the loads from prototypes inside
MultiGetByOffset. DFG::ByteCodeParser will guard MultiGetByOffset with CheckStructure's
on the prototypes. So, hypothetically, you might end up hoisting a MultiGetByOffset
above those structure checks, which would mean that we might load a value from a memory
location without knowing that the location is valid. It might then return the value
loaded.

This never happens in practice. Those structure checks are more hoistable that the
MultiGetByOffset, since they read a strict subset of the MultiGetByOffset's abstract
heap reads. Also, we hoist in program order. So, those CheckStructure's will always be
hoisted before the MultiGetByOffset gets hoisted.

But we should fix this anyway. DFG::safeToExecute() has a clear definition of what a
"true" return means for IR transformations, and it fails in satisfying that definition
for MultiGetByOffset.

There are various approaches we can use for making this safe. I considered two:

1) Have MultiGetByOffset refer to the prototypes it is loading from in IR, so that we

can check if it's safe to load from them.


2) Turn off MultiGetByOffset hoisting when it will emit loads from prototypes, and the

prototype structure isn't being watched.


I ended up using (2), because it will be the most natural solution once I finish
https://bugs.webkit.org/show_bug.cgi?id=146929. Already now, it's somewhat more natural
than (1) since that requires more extensive IR changes. Also, (2) will give us what we
want in *most* cases: we will usually watch the prototype structure, and we will
usually constant-fold loads from prototypes. Both of these usually-true things would
have to become false for MultiGetByOffset hoisting to be disabled by this change.

This change also adds my attempt at a test, though it's not really a test of this bug.
This bug is currently benign. But, the test does at least trigger the logic to run,
which is better than nothing.

  • dfg/DFGSafeToExecute.h:

(JSC::DFG::safeToExecute):

  • tests/stress/multi-get-by-offset-hoist-around-structure-check.js: Added.

(foo):

Location:
trunk/Source/JavaScriptCore
Files:
1 added
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r187283 r187347  
     12015-07-23  Filip Pizlo  <fpizlo@apple.com>
     2
     3        DFG::safeToExecute() is wrong for MultiGetByOffset, doesn't consider the structures of the prototypes that get loaded from
     4        https://bugs.webkit.org/show_bug.cgi?id=147250
     5
     6        Reviewed by Geoffrey Garen.
     7       
     8        This fixes a nasty - but currently benign - bug in DFG::safeToExecute(). That function
     9        will tell you if hoisting a node to some point is safe in the sense that the node will
     10        not crash the VM if it executes at that point. A node may be unsafe to execute if we
     11        cannot prove that at that point, the memory it is loading is not garbage. This is a
     12        necessarily loose notion - for example it's OK to hoist a load if we haven't proved
     13        that the load makes semantic sense at that point, since anyway the place where the node
     14        did get used will still be guarded by any such semantic checks. But because we may also
     15        hoist uses of the load, we need to make sure that it doesn't produce a garbage value.
     16        Also, we need to ensure that the load won't trap. Hence safeToExecute() returns true
     17        anytime we can be sure that a node will not produce a garbage result (i.e. a malformed
     18        JSValue or object pointer) and will not trap when executed at the point in question.
     19       
     20        The bug is that this verification isn't performed for the loads from prototypes inside
     21        MultiGetByOffset. DFG::ByteCodeParser will guard MultiGetByOffset with CheckStructure's
     22        on the prototypes. So, hypothetically, you might end up hoisting a MultiGetByOffset
     23        above those structure checks, which would mean that we might load a value from a memory
     24        location without knowing that the location is valid. It might then return the value
     25        loaded.
     26       
     27        This never happens in practice. Those structure checks are more hoistable that the
     28        MultiGetByOffset, since they read a strict subset of the MultiGetByOffset's abstract
     29        heap reads. Also, we hoist in program order. So, those CheckStructure's will always be
     30        hoisted before the MultiGetByOffset gets hoisted.
     31       
     32        But we should fix this anyway. DFG::safeToExecute() has a clear definition of what a
     33        "true" return means for IR transformations, and it fails in satisfying that definition
     34        for MultiGetByOffset.
     35       
     36        There are various approaches we can use for making this safe. I considered two:
     37       
     38        1) Have MultiGetByOffset refer to the prototypes it is loading from in IR, so that we
     39           can check if it's safe to load from them.
     40       
     41        2) Turn off MultiGetByOffset hoisting when it will emit loads from prototypes, and the
     42           prototype structure isn't being watched.
     43       
     44        I ended up using (2), because it will be the most natural solution once I finish
     45        https://bugs.webkit.org/show_bug.cgi?id=146929. Already now, it's somewhat more natural
     46        than (1) since that requires more extensive IR changes. Also, (2) will give us what we
     47        want in *most* cases: we will usually watch the prototype structure, and we will
     48        usually constant-fold loads from prototypes. Both of these usually-true things would
     49        have to become false for MultiGetByOffset hoisting to be disabled by this change.
     50       
     51        This change also adds my attempt at a test, though it's not really a test of this bug.
     52        This bug is currently benign. But, the test does at least trigger the logic to run,
     53        which is better than nothing.
     54
     55        * dfg/DFGSafeToExecute.h:
     56        (JSC::DFG::safeToExecute):
     57        * tests/stress/multi-get-by-offset-hoist-around-structure-check.js: Added.
     58        (foo):
     59
    1602015-07-23  Sukolsak Sakshuwong  <sukolsak@gmail.com>
    261
  • trunk/Source/JavaScriptCore/dfg/DFGSafeToExecute.h

    r185239 r187347  
    100100// Determines if it's safe to execute a node within the given abstract state. This may
    101101// return false conservatively. If it returns true, then you can hoist the given node
    102 // up to the given point and expect that it will not crash. This doesn't guarantee that
    103 // the node will produce the result you wanted other than not crashing.
     102// up to the given point and expect that it will not crash. It also guarantees that the
     103// node will not produce a malformed JSValue or object pointer when executed in the
     104// given state. But this doesn't guarantee that the node will produce the result you
     105// wanted. For example, you may have a GetByOffset from a prototype that only makes
     106// semantic sense if you've also checked that some nearer prototype doesn't also have
     107// a property of the same name. This could still return true even if that check hadn't
     108// been performed in the given abstract state. That's fine though: the load can still
     109// safely execute before that check, so long as that check continues to guard any
     110// user-observable things done to the loaded value.
    104111template<typename AbstractStateType>
    105112bool safeToExecute(AbstractStateType& state, Graph& graph, Node* node)
     
    256263    case ConstantStoragePointer:
    257264    case Check:
    258     case MultiGetByOffset:
    259265    case MultiPutByOffset:
    260266    case ValueRep:
     
    334340    }
    335341       
     342    case MultiGetByOffset: {
     343        // We can't always guarantee that the MultiGetByOffset is safe to execute if it
     344        // contains loads from prototypes. We know that it won't load from those prototypes if
     345        // we watch the mutability of the properties being loaded. So, here we try to
     346        // constant-fold prototype loads, and if that fails, we claim that we cannot hoist. We
     347        // also know that a load is safe to execute if we are watching the prototype's
     348        // structure.
     349        for (const GetByIdVariant& variant : node->multiGetByOffsetData().variants) {
     350            if (!variant.alternateBase()) {
     351                // It's not a prototype load.
     352                continue;
     353            }
     354           
     355            JSValue base = variant.alternateBase();
     356            FrozenValue* frozen = graph.freeze(base);
     357            if (state.structureClobberState() == StructuresAreWatched
     358                && frozen->structure()->dfgShouldWatch()
     359                && frozen->structure()->isValidOffset(variant.offset())) {
     360                // We're already watching that it's safe to load from this.
     361                continue;
     362            }
     363           
     364            JSValue constantResult = graph.tryGetConstantProperty(
     365                variant.alternateBase(), variant.baseStructure(), variant.offset());
     366            if (!constantResult) {
     367                // Couldn't constant-fold a prototype load. Therefore, we shouldn't hoist
     368                // because the safety of the load depends on structure checks on the prototype,
     369                // and we're too cheap to verify that here.
     370                return false;
     371            }
     372           
     373            // Otherwise, we will either:
     374            // - Not load from the prototype because constant folding will succeed in the
     375            //   backend, or
     376            // - Emit invalid code that fails watchpoint checks. This could happen if the
     377            //   property becomes mutable after this point, since we already set the watchpoint
     378            //   above. This case is OK since the code will fail watchpoint checks and never
     379            //   get installed.
     380        }
     381        return true;
     382    }
     383
    336384    case LastNodeType:
    337385        RELEASE_ASSERT_NOT_REACHED();
Note: See TracChangeset for help on using the changeset viewer.