| /* |
| * Copyright (C) 2012-2019 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #pragma once |
| |
| #include "JSScope.h" |
| #include "PropertyDescriptor.h" |
| #include "SymbolTable.h" |
| #include "ThrowScope.h" |
| #include "VariableWriteFireDetail.h" |
| |
| namespace JSC { |
| |
| class JSSymbolTableObject : public JSScope { |
| public: |
| typedef JSScope Base; |
| static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetPropertyNames; |
| |
| SymbolTable* symbolTable() const { return m_symbolTable.get(); } |
| |
| JS_EXPORT_PRIVATE static bool deleteProperty(JSCell*, JSGlobalObject*, PropertyName); |
| JS_EXPORT_PRIVATE static void getOwnNonIndexPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, EnumerationMode); |
| |
| static ptrdiff_t offsetOfSymbolTable() { return OBJECT_OFFSETOF(JSSymbolTableObject, m_symbolTable); } |
| |
| DECLARE_EXPORT_INFO; |
| |
| protected: |
| JSSymbolTableObject(VM& vm, Structure* structure, JSScope* scope) |
| : Base(vm, structure, scope) |
| { |
| } |
| |
| JSSymbolTableObject(VM& vm, Structure* structure, JSScope* scope, SymbolTable* symbolTable) |
| : Base(vm, structure, scope) |
| { |
| ASSERT(symbolTable); |
| setSymbolTable(vm, symbolTable); |
| } |
| |
| void setSymbolTable(VM& vm, SymbolTable* symbolTable) |
| { |
| ASSERT(!m_symbolTable); |
| symbolTable->notifyCreation(vm, this, "Allocated a scope"); |
| m_symbolTable.set(vm, this, symbolTable); |
| } |
| |
| static void visitChildren(JSCell*, SlotVisitor&); |
| |
| private: |
| WriteBarrier<SymbolTable> m_symbolTable; |
| }; |
| |
| template<typename SymbolTableObjectType> |
| inline bool symbolTableGet( |
| SymbolTableObjectType* object, PropertyName propertyName, PropertySlot& slot) |
| { |
| SymbolTable& symbolTable = *object->symbolTable(); |
| ConcurrentJSLocker locker(symbolTable.m_lock); |
| SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid()); |
| if (iter == symbolTable.end(locker)) |
| return false; |
| SymbolTableEntry::Fast entry = iter->value; |
| ASSERT(!entry.isNull()); |
| |
| ScopeOffset offset = entry.scopeOffset(); |
| // Defend against the inspector asking for a var after it has been optimized out. |
| if (!object->isValidScopeOffset(offset)) |
| return false; |
| |
| slot.setValue(object, entry.getAttributes() | PropertyAttribute::DontDelete, object->variableAt(offset).get()); |
| return true; |
| } |
| |
| template<typename SymbolTableObjectType> |
| inline bool symbolTableGet( |
| SymbolTableObjectType* object, PropertyName propertyName, PropertyDescriptor& descriptor) |
| { |
| SymbolTable& symbolTable = *object->symbolTable(); |
| ConcurrentJSLocker locker(symbolTable.m_lock); |
| SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid()); |
| if (iter == symbolTable.end(locker)) |
| return false; |
| SymbolTableEntry::Fast entry = iter->value; |
| ASSERT(!entry.isNull()); |
| |
| ScopeOffset offset = entry.scopeOffset(); |
| // Defend against the inspector asking for a var after it has been optimized out. |
| if (!object->isValidScopeOffset(offset)) |
| return false; |
| |
| descriptor.setDescriptor(object->variableAt(offset).get(), entry.getAttributes() | PropertyAttribute::DontDelete); |
| return true; |
| } |
| |
| template<typename SymbolTableObjectType> |
| inline bool symbolTableGet( |
| SymbolTableObjectType* object, PropertyName propertyName, PropertySlot& slot, |
| bool& slotIsWriteable) |
| { |
| SymbolTable& symbolTable = *object->symbolTable(); |
| ConcurrentJSLocker locker(symbolTable.m_lock); |
| SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid()); |
| if (iter == symbolTable.end(locker)) |
| return false; |
| SymbolTableEntry::Fast entry = iter->value; |
| ASSERT(!entry.isNull()); |
| |
| ScopeOffset offset = entry.scopeOffset(); |
| // Defend against the inspector asking for a var after it has been optimized out. |
| if (!object->isValidScopeOffset(offset)) |
| return false; |
| |
| slot.setValue(object, entry.getAttributes() | PropertyAttribute::DontDelete, object->variableAt(offset).get()); |
| slotIsWriteable = !entry.isReadOnly(); |
| return true; |
| } |
| |
| template<typename SymbolTableObjectType> |
| ALWAYS_INLINE void symbolTablePutTouchWatchpointSet(VM& vm, SymbolTableObjectType* object, PropertyName propertyName, JSValue value, WriteBarrierBase<Unknown>* reg, WatchpointSet* set) |
| { |
| reg->set(vm, object, value); |
| if (set) |
| VariableWriteFireDetail::touch(vm, set, object, propertyName); |
| } |
| |
| template<typename SymbolTableObjectType> |
| ALWAYS_INLINE void symbolTablePutInvalidateWatchpointSet(VM& vm, SymbolTableObjectType* object, PropertyName propertyName, JSValue value, WriteBarrierBase<Unknown>* reg, WatchpointSet* set) |
| { |
| reg->set(vm, object, value); |
| if (set) |
| set->invalidate(vm, VariableWriteFireDetail(object, propertyName)); // Don't mess around - if we had found this statically, we would have invalidated it. |
| } |
| |
| enum class SymbolTablePutMode { |
| Touch, |
| Invalidate |
| }; |
| |
| template<SymbolTablePutMode symbolTablePutMode, typename SymbolTableObjectType> |
| inline bool symbolTablePut(SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult) |
| { |
| VM& vm = getVM(globalObject); |
| auto scope = DECLARE_THROW_SCOPE(vm); |
| |
| WatchpointSet* set = nullptr; |
| WriteBarrierBase<Unknown>* reg; |
| { |
| SymbolTable& symbolTable = *object->symbolTable(); |
| // FIXME: This is very suspicious. We shouldn't need a GC-safe lock here. |
| // https://bugs.webkit.org/show_bug.cgi?id=134601 |
| GCSafeConcurrentJSLocker locker(symbolTable.m_lock, vm.heap); |
| SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid()); |
| if (iter == symbolTable.end(locker)) |
| return false; |
| bool wasFat; |
| SymbolTableEntry::Fast fastEntry = iter->value.getFast(wasFat); |
| ASSERT(!fastEntry.isNull()); |
| if (fastEntry.isReadOnly() && !ignoreReadOnlyErrors) { |
| if (shouldThrowReadOnlyError) |
| throwTypeError(globalObject, scope, ReadonlyPropertyWriteError); |
| putResult = false; |
| return true; |
| } |
| |
| ScopeOffset offset = fastEntry.scopeOffset(); |
| |
| // Defend against the inspector asking for a var after it has been optimized out. |
| if (!object->isValidScopeOffset(offset)) |
| return false; |
| |
| set = iter->value.watchpointSet(); |
| reg = &object->variableAt(offset); |
| } |
| // I'd prefer we not hold lock while executing barriers, since I prefer to reserve |
| // the right for barriers to be able to trigger GC. And I don't want to hold VM |
| // locks while GC'ing. |
| if (symbolTablePutMode == SymbolTablePutMode::Invalidate) |
| symbolTablePutInvalidateWatchpointSet(vm, object, propertyName, value, reg, set); |
| else |
| symbolTablePutTouchWatchpointSet(vm, object, propertyName, value, reg, set); |
| putResult = true; |
| return true; |
| } |
| |
| template<typename SymbolTableObjectType> |
| inline bool symbolTablePutTouchWatchpointSet( |
| SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, |
| bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult) |
| { |
| ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(object)); |
| return symbolTablePut<SymbolTablePutMode::Touch>(object, globalObject, propertyName, value, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult); |
| } |
| |
| template<typename SymbolTableObjectType> |
| inline bool symbolTablePutInvalidateWatchpointSet( |
| SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, |
| bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult) |
| { |
| ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(object)); |
| return symbolTablePut<SymbolTablePutMode::Invalidate>(object, globalObject, propertyName, value, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult); |
| } |
| |
| } // namespace JSC |