JSModuleNamespaceObject.cpp
1 /* 2 * Copyright (C) 2015-2020 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "JSModuleNamespaceObject.h" 28 29 #include "AbstractModuleRecord.h" 30 #include "JSCInlines.h" 31 #include "JSModuleEnvironment.h" 32 33 namespace JSC { 34 35 const ClassInfo JSModuleNamespaceObject::s_info = { "ModuleNamespaceObject", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSModuleNamespaceObject) }; 36 37 JSModuleNamespaceObject::JSModuleNamespaceObject(VM& vm, Structure* structure) 38 : Base(vm, structure) 39 , m_exports() 40 { 41 } 42 43 void JSModuleNamespaceObject::finishCreation(JSGlobalObject* globalObject, AbstractModuleRecord* moduleRecord, Vector<std::pair<Identifier, AbstractModuleRecord::Resolution>>&& resolutions) 44 { 45 VM& vm = globalObject->vm(); 46 auto scope = DECLARE_THROW_SCOPE(vm); 47 Base::finishCreation(vm); 48 ASSERT(inherits(vm, info())); 49 50 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects 51 // Quoted from the spec: 52 // A List containing the String values of the exported names exposed as own properties of this object. 53 // The list is ordered as if an Array of those String values had been sorted using Array.prototype.sort using SortCompare as comparefn. 54 // 55 // Sort the exported names by the code point order. 56 std::sort(resolutions.begin(), resolutions.end(), [] (const auto& lhs, const auto& rhs) { 57 return codePointCompare(lhs.first.impl(), rhs.first.impl()) < 0; 58 }); 59 60 m_moduleRecord.set(vm, this, moduleRecord); 61 m_names.reserveCapacity(resolutions.size()); 62 { 63 auto locker = holdLock(cellLock()); 64 for (const auto& pair : resolutions) { 65 m_names.append(pair.first); 66 auto addResult = m_exports.add(pair.first.impl(), ExportEntry()); 67 addResult.iterator->value.localName = pair.second.localName; 68 addResult.iterator->value.moduleRecord.set(vm, this, pair.second.moduleRecord); 69 } 70 } 71 72 putDirect(vm, vm.propertyNames->toStringTagSymbol, jsNontrivialString(vm, "Module"_s), PropertyAttribute::DontEnum | PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly); 73 74 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-getprototypeof 75 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-setprototypeof-v 76 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-isextensible 77 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-preventextensions 78 methodTable(vm)->preventExtensions(this, globalObject); 79 scope.assertNoException(); 80 } 81 82 void JSModuleNamespaceObject::destroy(JSCell* cell) 83 { 84 JSModuleNamespaceObject* thisObject = static_cast<JSModuleNamespaceObject*>(cell); 85 thisObject->JSModuleNamespaceObject::~JSModuleNamespaceObject(); 86 } 87 88 void JSModuleNamespaceObject::visitChildren(JSCell* cell, SlotVisitor& visitor) 89 { 90 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 91 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 92 Base::visitChildren(thisObject, visitor); 93 visitor.append(thisObject->m_moduleRecord); 94 { 95 auto locker = holdLock(thisObject->cellLock()); 96 for (auto& pair : thisObject->m_exports) 97 visitor.appendHidden(pair.value.moduleRecord); 98 } 99 } 100 101 static JSValue getValue(JSModuleEnvironment* environment, PropertyName localName, ScopeOffset& scopeOffset) 102 { 103 SymbolTable* symbolTable = environment->symbolTable(); 104 { 105 ConcurrentJSLocker locker(symbolTable->m_lock); 106 auto iter = symbolTable->find(locker, localName.uid()); 107 ASSERT(iter != symbolTable->end(locker)); 108 SymbolTableEntry& entry = iter->value; 109 ASSERT(!entry.isNull()); 110 scopeOffset = entry.scopeOffset(); 111 } 112 return environment->variableAt(scopeOffset).get(); 113 } 114 115 bool JSModuleNamespaceObject::getOwnPropertySlotCommon(JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) 116 { 117 VM& vm = globalObject->vm(); 118 auto scope = DECLARE_THROW_SCOPE(vm); 119 120 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-getownproperty-p 121 122 // step 1. 123 // If the property name is a symbol, we don't look into the imported bindings. 124 // It may return the descriptor with writable: true, but namespace objects does not allow it in [[Set]] / [[DefineOwnProperty]] side. 125 if (propertyName.isSymbol()) 126 return Base::getOwnPropertySlot(this, globalObject, propertyName, slot); 127 128 slot.setIsTaintedByOpaqueObject(); 129 130 auto iterator = m_exports.find(propertyName.uid()); 131 if (iterator == m_exports.end()) 132 return false; 133 ExportEntry& exportEntry = iterator->value; 134 135 switch (slot.internalMethodType()) { 136 case PropertySlot::InternalMethodType::GetOwnProperty: 137 case PropertySlot::InternalMethodType::Get: { 138 if (exportEntry.localName == vm.propertyNames->starNamespacePrivateName) { 139 // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver 140 // 10. If binding.[[BindingName]] is "*namespace*", then 141 // a. Return ? GetModuleNamespace(targetModule). 142 // We call getModuleNamespace() to ensure materialization. And after that, looking up the value from the scope to encourage module namespace object IC. 143 exportEntry.moduleRecord->getModuleNamespace(globalObject); 144 RETURN_IF_EXCEPTION(scope, false); 145 } 146 JSModuleEnvironment* environment = exportEntry.moduleRecord->moduleEnvironment(); 147 ScopeOffset scopeOffset; 148 JSValue value = getValue(environment, exportEntry.localName, scopeOffset); 149 // If the value is filled with TDZ value, throw a reference error. 150 if (!value) { 151 throwVMError(globalObject, scope, createTDZError(globalObject)); 152 return false; 153 } 154 155 slot.setValueModuleNamespace(this, static_cast<unsigned>(PropertyAttribute::DontDelete), value, environment, scopeOffset); 156 return true; 157 } 158 159 case PropertySlot::InternalMethodType::HasProperty: { 160 // Do not perform [[Get]] for [[HasProperty]]. 161 // [[Get]] / [[GetOwnProperty]] onto namespace object could throw an error while [[HasProperty]] just returns true here. 162 // https://tc39.github.io/ecma262/#sec-module-namespace-exotic-objects-hasproperty-p 163 slot.setValue(this, static_cast<unsigned>(PropertyAttribute::DontDelete), jsUndefined()); 164 return true; 165 } 166 167 case PropertySlot::InternalMethodType::VMInquiry: 168 slot.setValue(this, static_cast<unsigned>(JSC::PropertyAttribute::None), jsUndefined()); 169 return false; 170 } 171 172 RELEASE_ASSERT_NOT_REACHED(); 173 return false; 174 } 175 176 bool JSModuleNamespaceObject::getOwnPropertySlot(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) 177 { 178 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 179 return thisObject->getOwnPropertySlotCommon(globalObject, propertyName, slot); 180 } 181 182 bool JSModuleNamespaceObject::getOwnPropertySlotByIndex(JSObject* cell, JSGlobalObject* globalObject, unsigned propertyName, PropertySlot& slot) 183 { 184 VM& vm = globalObject->vm(); 185 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 186 return thisObject->getOwnPropertySlotCommon(globalObject, Identifier::from(vm, propertyName), slot); 187 } 188 189 bool JSModuleNamespaceObject::put(JSCell*, JSGlobalObject* globalObject, PropertyName, JSValue, PutPropertySlot& slot) 190 { 191 VM& vm = globalObject->vm(); 192 auto scope = DECLARE_THROW_SCOPE(vm); 193 194 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-set-p-v-receiver 195 if (slot.isStrictMode()) 196 throwTypeError(globalObject, scope, ReadonlyPropertyWriteError); 197 return false; 198 } 199 200 bool JSModuleNamespaceObject::putByIndex(JSCell*, JSGlobalObject* globalObject, unsigned, JSValue, bool shouldThrow) 201 { 202 VM& vm = globalObject->vm(); 203 auto scope = DECLARE_THROW_SCOPE(vm); 204 205 if (shouldThrow) 206 throwTypeError(globalObject, scope, ReadonlyPropertyWriteError); 207 return false; 208 } 209 210 bool JSModuleNamespaceObject::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, DeletePropertySlot& slot) 211 { 212 // http://www.ecma-international.org/ecma-262/6.0/#sec-module-namespace-exotic-objects-delete-p 213 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 214 if (propertyName.isSymbol()) 215 return Base::deleteProperty(thisObject, globalObject, propertyName, slot); 216 217 return !thisObject->m_exports.contains(propertyName.uid()); 218 } 219 220 void JSModuleNamespaceObject::getOwnPropertyNames(JSObject* cell, JSGlobalObject* globalObject, PropertyNameArray& propertyNames, DontEnumPropertiesMode mode) 221 { 222 VM& vm = globalObject->vm(); 223 auto scope = DECLARE_THROW_SCOPE(vm); 224 225 // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-ownpropertykeys 226 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 227 for (const auto& name : thisObject->m_names) { 228 if (mode == DontEnumPropertiesMode::Exclude) { 229 // Perform [[GetOwnProperty]] to throw ReferenceError if binding is uninitialized. 230 PropertySlot slot(cell, PropertySlot::InternalMethodType::GetOwnProperty); 231 thisObject->getOwnPropertySlotCommon(globalObject, name.impl(), slot); 232 RETURN_IF_EXCEPTION(scope, void()); 233 } 234 propertyNames.add(name.impl()); 235 } 236 if (propertyNames.includeSymbolProperties()) { 237 scope.release(); 238 thisObject->getOwnNonIndexPropertyNames(globalObject, propertyNames, mode); 239 } 240 } 241 242 bool JSModuleNamespaceObject::defineOwnProperty(JSObject* cell, JSGlobalObject* globalObject, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow) 243 { 244 VM& vm = globalObject->vm(); 245 auto scope = DECLARE_THROW_SCOPE(vm); 246 247 // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-defineownproperty-p-desc 248 249 JSModuleNamespaceObject* thisObject = jsCast<JSModuleNamespaceObject*>(cell); 250 251 // 1. If Type(P) is Symbol, return OrdinaryDefineOwnProperty(O, P, Desc). 252 if (propertyName.isSymbol()) 253 RELEASE_AND_RETURN(scope, Base::defineOwnProperty(thisObject, globalObject, propertyName, descriptor, shouldThrow)); 254 255 // 2. Let current be ? O.[[GetOwnProperty]](P). 256 PropertyDescriptor current; 257 bool isCurrentDefined = thisObject->getOwnPropertyDescriptor(globalObject, propertyName, current); 258 RETURN_IF_EXCEPTION(scope, false); 259 260 // 3. If current is undefined, return false. 261 if (!isCurrentDefined) { 262 if (shouldThrow) 263 throwTypeError(globalObject, scope, NonExtensibleObjectPropertyDefineError); 264 return false; 265 } 266 267 // 4. If IsAccessorDescriptor(Desc) is true, return false. 268 if (descriptor.isAccessorDescriptor()) { 269 if (shouldThrow) 270 throwTypeError(globalObject, scope, "Cannot change module namespace object's binding to accessor"_s); 271 return false; 272 } 273 274 // 5. If Desc.[[Writable]] is present and has value false, return false. 275 if (descriptor.writablePresent() && !descriptor.writable()) { 276 if (shouldThrow) 277 throwTypeError(globalObject, scope, "Cannot change module namespace object's binding to non-writable attribute"_s); 278 return false; 279 } 280 281 // 6. If Desc.[[Enumerable]] is present and has value false, return false. 282 if (descriptor.enumerablePresent() && !descriptor.enumerable()) { 283 if (shouldThrow) 284 throwTypeError(globalObject, scope, "Cannot replace module namespace object's binding with non-enumerable attribute"_s); 285 return false; 286 } 287 288 // 7. If Desc.[[Configurable]] is present and has value true, return false. 289 if (descriptor.configurablePresent() && descriptor.configurable()) { 290 if (shouldThrow) 291 throwTypeError(globalObject, scope, "Cannot replace module namespace object's binding with configurable attribute"_s); 292 return false; 293 } 294 295 // 8. If Desc.[[Value]] is present, return SameValue(Desc.[[Value]], current.[[Value]]). 296 if (descriptor.value()) { 297 bool result = sameValue(globalObject, descriptor.value(), current.value()); 298 RETURN_IF_EXCEPTION(scope, false); 299 if (!result) { 300 if (shouldThrow) 301 throwTypeError(globalObject, scope, "Cannot replace module namespace object's binding's value"_s); 302 return false; 303 } 304 } 305 306 // 9. Return true. 307 return true; 308 } 309 310 } // namespace JSC