/* * Copyright (C) 2004-2022 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. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 INC. OR * 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. */ #import "config.h" #import "objc_runtime.h" #import "JSDOMBinding.h" #import "ObjCRuntimeObject.h" #import "WebScriptObject.h" #import "WebScriptObjectProtocol.h" #import "objc_instance.h" #import "runtime_array.h" #import "runtime_object.h" #import #import #import #import #import #import using namespace WebCore; namespace JSC { namespace Bindings { static JSC_DECLARE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive); ClassStructPtr webScriptObjectClass() { static ClassStructPtr webScriptObjectClass = NSClassFromString(@"WebScriptObject"); return webScriptObjectClass; } ClassStructPtr webUndefinedClass() { static ClassStructPtr webUndefinedClass = NSClassFromString(@"WebUndefined"); return webUndefinedClass; } // ---------------------- ObjcMethod ---------------------- ObjcMethod::ObjcMethod(ClassStructPtr aClass, SEL selector) : _objcClass(aClass) , _selector(selector) { } int ObjcMethod::numParameters() const { return [getMethodSignature() numberOfArguments] - 2; } NSMethodSignature* ObjcMethod::getMethodSignature() const { return [_objcClass instanceMethodSignatureForSelector:_selector]; } bool ObjcMethod::isFallbackMethod() const { return _selector == @selector(invokeUndefinedMethodFromWebScript:withArguments:); } // ---------------------- ObjcField ---------------------- ObjcField::ObjcField(Ivar ivar) : _ivar(ivar) , _name(adoptCF(CFStringCreateWithCString(0, ivar_getName(_ivar), kCFStringEncodingASCII))) { } ObjcField::ObjcField(CFStringRef name) : _ivar(0) , _name(name) { } JSValue ObjcField::valueFromInstance(JSGlobalObject* lexicalGlobalObject, const Instance* instance) const { VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSValue result = jsUndefined(); id targetObject = (static_cast(instance))->getObject(); JSLock::DropAllLocks dropAllLocks(lexicalGlobalObject); // Can't put this inside the @try scope because it unwinds incorrectly. @try { if (id objcValue = [targetObject valueForKey:(__bridge NSString *)_name.get()]) result = convertObjcValueToValue(lexicalGlobalObject, &objcValue, ObjcObjectType, instance->rootObject()); { JSLockHolder lock(lexicalGlobalObject); ObjcInstance::moveGlobalExceptionToExecState(lexicalGlobalObject); } } @catch(NSException* localException) { JSLockHolder lock(lexicalGlobalObject); throwError(lexicalGlobalObject, scope, [localException reason]); } // Work around problem in some versions of GCC where result gets marked volatile and // it can't handle copying from a volatile to non-volatile. return const_cast(result); } static id convertValueToObjcObject(JSGlobalObject* lexicalGlobalObject, JSValue value) { VM& vm = lexicalGlobalObject->vm(); RefPtr rootObject = findRootObject(vm.deprecatedVMEntryGlobalObject(lexicalGlobalObject)); if (!rootObject) return nil; return [webScriptObjectClass() _convertValueToObjcValue:value originRootObject:rootObject.get() rootObject:rootObject.get()]; } bool ObjcField::setValueToInstance(JSGlobalObject* lexicalGlobalObject, const Instance* instance, JSValue aValue) const { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); id targetObject = (static_cast(instance))->getObject(); id value = convertValueToObjcObject(lexicalGlobalObject, aValue); JSLock::DropAllLocks dropAllLocks(lexicalGlobalObject); // Can't put this inside the @try scope because it unwinds incorrectly. @try { [targetObject setValue:value forKey:(__bridge NSString *)_name.get()]; { JSLockHolder lock(lexicalGlobalObject); ObjcInstance::moveGlobalExceptionToExecState(lexicalGlobalObject); } return true; } @catch(NSException* localException) { JSLockHolder lock(lexicalGlobalObject); throwError(lexicalGlobalObject, scope, [localException reason]); return false; } } // ---------------------- ObjcArray ---------------------- ObjcArray::ObjcArray(ObjectStructPtr a, RefPtr&& rootObject) : Array(WTFMove(rootObject)) , _array(a) { } bool ObjcArray::setValueAt(JSGlobalObject* lexicalGlobalObject, unsigned int index, JSValue aValue) const { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (![_array respondsToSelector:@selector(insertObject:atIndex:)]) { throwTypeError(lexicalGlobalObject, scope, "Array is not mutable."_s); return false; } if (index > [_array count]) { throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Index exceeds array size."_s)); return false; } // Always try to convert the value to an ObjC object, so it can be placed in the // array. ObjcValue oValue = convertValueToObjcValue (lexicalGlobalObject, aValue, ObjcObjectType); @try { [_array insertObject:(__bridge id)oValue.objectValue atIndex:index]; return true; } @catch(NSException* localException) { throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Objective-C exception."_s)); return false; } } JSValue ObjcArray::valueAt(JSGlobalObject* lexicalGlobalObject, unsigned int index) const { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (index > [_array count]) return throwException(lexicalGlobalObject, scope, createRangeError(lexicalGlobalObject, "Index exceeds array size."_s)); @try { id obj = [_array objectAtIndex:index]; if (obj) return convertObjcValueToValue (lexicalGlobalObject, &obj, ObjcObjectType, m_rootObject.get()); } @catch(NSException* localException) { return throwException(lexicalGlobalObject, scope, createError(lexicalGlobalObject, "Objective-C exception."_s)); } return jsUndefined(); } unsigned int ObjcArray::getLength() const { return [_array count]; } const ClassInfo ObjcFallbackObjectImp::s_info = { "ObjcFallbackObject"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(ObjcFallbackObjectImp) }; ObjcFallbackObjectImp::ObjcFallbackObjectImp(JSGlobalObject* globalObject, Structure* structure, ObjcInstance* i, const String& propertyName) : JSDestructibleObject(globalObject->vm(), structure) , _instance(i) , m_item(propertyName) { } void ObjcFallbackObjectImp::destroy(JSCell* cell) { static_cast(cell)->ObjcFallbackObjectImp::~ObjcFallbackObjectImp(); } void ObjcFallbackObjectImp::finishCreation(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); Base::finishCreation(vm); ASSERT(inherits(info())); putDirect(vm, vm.propertyNames->toPrimitiveSymbol, JSFunction::create(vm, globalObject, 0, "[Symbol.toPrimitive]"_s, convertObjCFallbackObjectToPrimitive), static_cast(PropertyAttribute::DontEnum)); } bool ObjcFallbackObjectImp::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot) { VM& vm = globalObject->vm(); if (propertyName.uid() == vm.propertyNames->toPrimitiveSymbol.impl()) return JSObject::getOwnPropertySlot(object, globalObject, propertyName, slot); // keep the prototype from getting called instead of just returning false slot.setUndefined(); return true; } bool ObjcFallbackObjectImp::put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&) { return false; } static JSC_DECLARE_HOST_FUNCTION(callObjCFallbackObject); JSC_DEFINE_HOST_FUNCTION(callObjCFallbackObject, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) { JSC::VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); JSValue thisValue = callFrame->thisValue(); if (!thisValue.inherits()) return throwVMTypeError(lexicalGlobalObject, scope); JSValue result = jsUndefined(); ObjCRuntimeObject* runtimeObject = static_cast(asObject(thisValue)); ObjcInstance* objcInstance = runtimeObject->getInternalObjCInstance(); if (!objcInstance) return JSValue::encode(throwRuntimeObjectInvalidAccessError(lexicalGlobalObject, scope)); objcInstance->begin(); id targetObject = objcInstance->getObject(); if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]){ ObjcClass* objcClass = static_cast(objcInstance->getClass()); std::unique_ptr fallbackMethod(makeUnique(objcClass->isa(), @selector(invokeUndefinedMethodFromWebScript:withArguments:))); const String& nameIdentifier = static_cast(callFrame->jsCallee())->propertyName(); fallbackMethod->setJavaScriptName(nameIdentifier.createCFString().get()); result = objcInstance->invokeObjcMethod(lexicalGlobalObject, callFrame, fallbackMethod.get()); } objcInstance->end(); return JSValue::encode(result); } CallData ObjcFallbackObjectImp::getCallData(JSCell* cell) { CallData callData; ObjcFallbackObjectImp* thisObject = jsCast(cell); id targetObject = thisObject->_instance->getObject(); if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) { callData.type = CallData::Type::Native; callData.native.function = callObjCFallbackObject; } return callData; } bool ObjcFallbackObjectImp::deleteProperty(JSCell*, JSGlobalObject*, PropertyName, DeletePropertySlot&) { return false; } JSC_DEFINE_HOST_FUNCTION(convertObjCFallbackObjectToPrimitive, (JSGlobalObject* lexicalGlobalObject, CallFrame* callFrame)) { VM& vm = lexicalGlobalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); auto* thisObject = jsDynamicCast(callFrame->thisValue()); if (!thisObject) return throwVMTypeError(lexicalGlobalObject, scope, "ObjcFallbackObject[Symbol.toPrimitive] method called on incompatible |this| value."_s); scope.release(); return JSValue::encode(thisObject->getInternalObjCInstance()->getValueOfUndefinedField(lexicalGlobalObject, Identifier::fromString(vm, thisObject->propertyName()))); } bool ObjcFallbackObjectImp::toBoolean(JSGlobalObject*) const { id targetObject = _instance->getObject(); if ([targetObject respondsToSelector:@selector(invokeUndefinedMethodFromWebScript:withArguments:)]) return true; return false; } JSC::GCClient::IsoSubspace* ObjcFallbackObjectImp::subspaceForImpl(JSC::VM& vm) { static NeverDestroyed perVM([] (JSC::Heap& heap) { return ISO_SUBSPACE_PARAMETERS(heap.destructibleObjectHeapCellType, ObjcFallbackObjectImp); }); return &perVM.get().clientIsoSubspaceforVM(vm); } } // namespace Bindings } // namespace JSC