/ runtime / JSModuleNamespaceObject.cpp
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