//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "RuntimeLibraryPch.h" #ifndef USING_PAL_STDLIB #include #endif #include "ByteCode/ByteCodeApi.h" #include "Exceptions/EvalDisabledException.h" #include "Types/PropertyIndexRanges.h" #include "Types/SimpleDictionaryPropertyDescriptor.h" #include "Types/SimpleDictionaryTypeHandler.h" using namespace Js; GlobalObject * GlobalObject::New(ScriptContext * scriptContext) { SimpleDictionaryTypeHandler* globalTypeHandler = SimpleDictionaryTypeHandler::New( scriptContext->GetRecycler(), InitialCapacity, InlineSlotCapacity, sizeof(Js::GlobalObject)); DynamicType* globalType = DynamicType::New( scriptContext, TypeIds_GlobalObject, nullptr, nullptr, globalTypeHandler); GlobalObject* globalObject = RecyclerNewPlus(scriptContext->GetRecycler(), sizeof(Var) * InlineSlotCapacity, GlobalObject, globalType, scriptContext); #if ENABLE_FIXED_FIELDS globalTypeHandler->SetSingletonInstanceIfNeeded(scriptContext->GetRecycler()->CreateWeakReferenceHandle(globalObject)); #endif return globalObject; } GlobalObject::GlobalObject(DynamicType * type, ScriptContext* scriptContext) : RootObjectBase(type, scriptContext), directHostObject(nullptr), secureDirectHostObject(nullptr), EvalHelper(&GlobalObject::DefaultEvalHelper), reservedProperties(nullptr) { } void GlobalObject::Initialize(ScriptContext * scriptContext) { Assert(type->javascriptLibrary == nullptr); Recycler * recycler = scriptContext->GetRecycler(); JavascriptLibrary* localLibrary = RecyclerNewFinalized(recycler, JavascriptLibrary, this, recycler); scriptContext->SetLibrary(localLibrary); type->javascriptLibrary = localLibrary; scriptContext->InitializeCache(); localLibrary->Initialize(scriptContext, this); library = localLibrary; } HRESULT GlobalObject::SetDirectHostObject(RecyclableObject* hostObject, RecyclableObject* secureDirectHostObject) { HRESULT hr = S_OK; this->directHostObject = hostObject; this->secureDirectHostObject = secureDirectHostObject; BEGIN_TRANSLATE_OOM_TO_HRESULT_NESTED { // In fastDOM scenario, we should use the host object to lookup the prototype. this->SetPrototype(library->GetNull()); // Host can call to set the direct host object after the GlobalObject has been initialized but // before user script has run. (This happens even before the previous call to SetPrototype) // If that happens, we'll need to update the 'globalThis' property to point to the secure // host object so that we don't hand a reference to the bare GlobalObject out to user script. if (this->GetScriptContext()->GetConfig()->IsESGlobalThisEnabled()) { this->SetProperty(PropertyIds::globalThis, this->ToThis(), PropertyOperation_None, nullptr); } } END_TRANSLATE_OOM_TO_HRESULT(hr) return hr; } RecyclableObject* GlobalObject::GetDirectHostObject() { return this->directHostObject; } RecyclableObject* GlobalObject::GetSecureDirectHostObject() { return this->secureDirectHostObject; } // Converts the global object into the object that should be used as the 'this' parameter. // In a non-hosted environment, the global object (this) is returned. // In a hosted environment, the host object is returned. Var GlobalObject::ToThis() { Var ret; // In fast DOM, we need to give user the secure version of host object if (secureDirectHostObject) { ret = secureDirectHostObject; } else if (hostObject) { // If the global object has the host object, use that as "this" ret = hostObject->GetHostDispatchVar(); Assert(ret); } else { // Otherwise just use the global object ret = this; } return ret; } BOOL GlobalObject::ReserveGlobalProperty(PropertyId propertyId) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, nullptr /*info*/))) { return false; } if (reservedProperties == nullptr) { Recycler* recycler = this->GetScriptContext()->GetRecycler(); reservedProperties = RecyclerNew(recycler, ReservedPropertiesHashSet, recycler, 3); } reservedProperties->AddNew(propertyId); return true; } BOOL GlobalObject::IsReservedGlobalProperty(PropertyId propertyId) { return reservedProperties != nullptr && reservedProperties->Contains(propertyId); } #ifdef IR_VIEWER Var GlobalObject::EntryParseIR(RecyclableObject *function, CallInfo callInfo, ...) { // // retrieve arguments // RUNTIME_ARGUMENTS(args, callInfo); Js::Var codeVar = args[1]; // args[0] is (this) Js::JavascriptString *codeStringVar = nullptr; const char16 *source = nullptr; size_t sourceLength = 0; if (Js::VarIs(codeVar)) { codeStringVar = (Js::JavascriptString *)codeVar; source = codeStringVar->GetString(); sourceLength = codeStringVar->GetLength(); } else { AssertMsg(false, "The input to parseIR was not a string."); } // // collect arguments for eval // /* @see NativeCodeGenerator::CodeGen */ ScriptContext *scriptContext = function->GetScriptContext(); // used arbitrary defaults for these, but it seems to work ModuleID moduleID = 0; uint32 grfscr = 0; LPCOLESTR pszTitle = _u(""); BOOL registerDocument = false; BOOL strictMode = true; return IRDumpEvalHelper(scriptContext, source, sourceLength, moduleID, grfscr, pszTitle, registerDocument, FALSE, strictMode); } // TODO remove when refactor Js::PropertyId GlobalObject::CreateProperty(Js::ScriptContext *scriptContext, const char16 *propertyName) { Js::PropertyRecord const *propertyRecord; scriptContext->GetOrAddPropertyRecord(propertyName, (int) wcslen(propertyName), &propertyRecord); Js::PropertyId propertyId = propertyRecord->GetPropertyId(); return propertyId; } // TODO remove when refactor void GlobalObject::SetProperty(Js::DynamicObject *obj, const char16 *propertyName, Js::Var value) { const size_t len = wcslen(propertyName); if (!(len > 0)) { return; } Js::PropertyId id = CreateProperty(obj->GetScriptContext(), propertyName); SetProperty(obj, id, value); } // TODO remove when refactor void GlobalObject::SetProperty(Js::DynamicObject *obj, Js::PropertyId id, Js::Var value) { if (value == NULL) { return; } Js::JavascriptOperators::SetProperty(obj, obj, id, value, obj->GetScriptContext()); } Var GlobalObject::FunctionInfoObjectBuilder(ScriptContext *scriptContext, const char16 *file, const char16 *function, ULONG lineNum, ULONG colNum, uint functionId, Js::Utf8SourceInfo *utf8SrcInfo, Js::Var source) { Js::DynamicObject *fnInfoObj = scriptContext->GetLibrary()->CreateObject(); // create javascript objects for properties Js::Var filenameString = Js::JavascriptString::NewCopyBuffer(file, wcslen(file), scriptContext); Js::Var funcnameString = Js::JavascriptString::NewCopyBuffer(function, wcslen(function), scriptContext); Js::Var lineNumber = Js::JavascriptNumber::ToVar((int64) lineNum, scriptContext); Js::Var colNumber = Js::JavascriptNumber::ToVar((int64) colNum, scriptContext); Js::Var functionIdNumberVar = Js::JavascriptNumber::ToVar(functionId, scriptContext); Js::Var utf8SourceInfoVar = Js::JavascriptNumber::ToVar((int32) utf8SrcInfo, scriptContext); // assign properties to function info object SetProperty(fnInfoObj, _u("filename"), filenameString); SetProperty(fnInfoObj, _u("function"), funcnameString); SetProperty(fnInfoObj, _u("line"), lineNumber); SetProperty(fnInfoObj, _u("col"), colNumber); SetProperty(fnInfoObj, _u("funcId"), functionIdNumberVar); SetProperty(fnInfoObj, _u("utf8SrcInfoPtr"), utf8SourceInfoVar); SetProperty(fnInfoObj, _u("source"), source); return fnInfoObj; } /** * Return a list of functions visible in the current ScriptContext. */ Var GlobalObject::EntryFunctionList(RecyclableObject *function, CallInfo callInfo, ...) { // Note: the typedefs below help make the following code more readable // See: Js::ScriptContext::SourceList (declaration not visible from this file) typedef JsUtil::List*, Recycler, false, Js::FreeListedRemovePolicy> SourceList; typedef RecyclerWeakReference Utf8SourceInfoRef; ScriptContext *originalScriptContext = function->GetScriptContext(); ThreadContext *threadContext = originalScriptContext->GetThreadContext(); ScriptContext *scriptContext; int fnCount = 0; for (scriptContext = threadContext->GetScriptContextList(); scriptContext; scriptContext = scriptContext->next) { if (scriptContext->IsClosed()) continue; // // get count of functions in all files in script context // SourceList *sourceList = scriptContext->GetSourceList(); sourceList->Map([&fnCount](uint i, Utf8SourceInfoRef *sourceInfoWeakRef) { Js::Utf8SourceInfo *sourceInfo = sourceInfoWeakRef->Get(); if (sourceInfo == nullptr || sourceInfo->GetIsLibraryCode()) // library code has no source, skip { return; } fnCount += sourceInfo->GetFunctionBodyCount(); }); } #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u(">> There are %d total functions\n"), fnCount); Output::Flush(); } #endif // // create a javascript array to hold info for all of the functions // Js::JavascriptArray *functionList = originalScriptContext->GetLibrary()->CreateArray(fnCount); int count = 0; for (scriptContext = threadContext->GetScriptContextList(); scriptContext; scriptContext = scriptContext->next) { if (scriptContext->IsClosed()) continue; // if (scriptContext == originalScriptContext) continue; // uncomment to ignore the originalScriptContext SourceList *sourceList = scriptContext->GetSourceList(); sourceList->Map([&fnCount, &count, functionList, scriptContext](uint i, Utf8SourceInfoRef *sourceInfoWeakRef) { Js::Utf8SourceInfo *sourceInfo = sourceInfoWeakRef->Get(); if (sourceInfo == nullptr || sourceInfo->GetIsLibraryCode()) // library code has no source, skip { return; } const SRCINFO *srcInfo = sourceInfo->GetSrcInfo(); SourceContextInfo *srcContextInfo = srcInfo->sourceContextInfo; // // get URL of source file // char16 filenameBuffer[128]; // hold dynamically built filename char16 const *srcFileUrl = NULL; if (!srcContextInfo->IsDynamic()) { Assert(srcContextInfo->url != NULL); srcFileUrl = srcContextInfo->url; } else { StringCchPrintf(filenameBuffer, 128, _u("[dynamic(hash:%u)]"), srcContextInfo->hash); srcFileUrl = filenameBuffer; } sourceInfo->MapFunction([scriptContext, &count, functionList, srcFileUrl](Js::FunctionBody *functionBody) { char16 const *functionName = functionBody->GetExternalDisplayName(); ULONG lineNum = functionBody->GetLineNumber(); ULONG colNum = functionBody->GetColumnNumber(); uint funcId = functionBody->GetLocalFunctionId(); Js::Utf8SourceInfo *utf8SrcInfo = functionBody->GetUtf8SourceInfo(); if (utf8SrcInfo == nullptr) { return; } Assert(utf8SrcInfo->FindFunction(funcId) == functionBody); BufferStringBuilder builder(functionBody->LengthInChars(), scriptContext); utf8::DecodeOptions options = utf8SrcInfo->IsCesu8() ? utf8::doAllowThreeByteSurrogates : utf8::doDefault; utf8::DecodeInto(builder.DangerousGetWritableBuffer(), functionBody->GetSource(), functionBody->LengthInChars(), options); Var cachedSourceString = builder.ToString(); Js::Var obj = FunctionInfoObjectBuilder(scriptContext, srcFileUrl, functionName, lineNum, colNum, funcId, utf8SrcInfo, cachedSourceString); functionList->SetItem(count, obj, Js::PropertyOperationFlags::PropertyOperation_None); ++count; }); }); } #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u(">> Returning from functionList()\n")); Output::Flush(); } #endif return functionList; } /** * Return the parsed IR for the given function. */ Var GlobalObject::EntryRejitFunction(RecyclableObject *function, CallInfo callInfo, ...) { #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u(">> Entering rejitFunction()\n")); Output::Flush(); } #endif // // retrieve and coerce arguments // RUNTIME_ARGUMENTS(args, callInfo); // args[0] is (callInfo) Js::Var jsVarUtf8SourceInfo = args[1]; Js::Var jsVarFunctionId = args[2]; #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u("jsVarUtf8SourceInfo: %d (0x%08X)\n"), jsVarUtf8SourceInfo, jsVarUtf8SourceInfo); Output::Print(_u("jsVarFunctionId: %d (0x%08X)\n"), jsVarFunctionId, jsVarFunctionId); } #endif Js::JavascriptNumber *jsUtf8SourceInfoNumber = NULL; Js::JavascriptNumber *jsFunctionIdNumber = NULL; int32 utf8SourceInfoNumber = 0; // null int32 functionIdNumber = -1; // start with invalid function id // extract value of jsVarUtf8SourceInfo if (Js::TaggedInt::Is(jsVarUtf8SourceInfo)) { utf8SourceInfoNumber = (int32)TaggedInt::ToInt64(jsVarUtf8SourceInfo); // REVIEW: just truncate? } else if (Js::JavascriptNumber::Is(jsVarUtf8SourceInfo)) { jsUtf8SourceInfoNumber = (Js::JavascriptNumber *)jsVarUtf8SourceInfo; utf8SourceInfoNumber = (int32)JavascriptNumber::GetValue(jsUtf8SourceInfoNumber); // REVIEW: just truncate? } else { // should not reach here AssertMsg(false, "Failed to extract value for jsVarUtf8SourceInfo as either TaggedInt or JavascriptNumber.\n"); } // extract value of jsVarFunctionId if (Js::TaggedInt::Is(jsVarFunctionId)) { functionIdNumber = (int32)TaggedInt::ToInt64(jsVarFunctionId); // REVIEW: just truncate? } else if (Js::JavascriptNumber::Is(jsVarFunctionId)) { jsFunctionIdNumber = (Js::JavascriptNumber *)jsVarFunctionId; functionIdNumber = (int32)JavascriptNumber::GetValue(jsFunctionIdNumber); // REVIEW: just truncate? } else { // should not reach here AssertMsg(false, "Failed to extract value for jsVarFunctionId as either TaggedInt or JavascriptNumber.\n"); } #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u("utf8SourceInfoNumber (value): %d (0x%08X)\n"), utf8SourceInfoNumber, utf8SourceInfoNumber); Output::Print(_u("jsVarFunctionId (value): %d (0x%08X)\n"), functionIdNumber, functionIdNumber); Output::Print(_u(">> Executing rejitFunction(%d, %d)\n"), utf8SourceInfoNumber, functionIdNumber); Output::Flush(); } #endif // // recover functionBody // Js::Utf8SourceInfo *sourceInfo = (Js::Utf8SourceInfo *)(utf8SourceInfoNumber); if (sourceInfo == NULL) { return NULL; } Js::FunctionBody *functionBody = sourceInfo->FindFunction((Js::LocalFunctionId)functionIdNumber); // // rejit the function body // Js::ScriptContext *scriptContext = function->GetScriptContext(); NativeCodeGenerator *nativeCodeGenerator = scriptContext->GetNativeCodeGenerator(); if (!functionBody || !functionBody->GetByteCode()) { return scriptContext->GetLibrary()->GetUndefined(); } Js::Var retVal = RejitIRViewerFunction(nativeCodeGenerator, functionBody, scriptContext); // // return the parsed IR object // #ifdef ENABLE_IR_VIEWER_DBG_DUMP if (Js::Configuration::Global.flags.Verbose) { Output::Print(_u("rejitFunction - retVal: 0x%08X\n"), retVal); Output::Flush(); } #endif return retVal; } #endif /* IR_VIEWER */ Var GlobalObject::EntryEvalHelper(ScriptContext* scriptContext, RecyclableObject* function, Js::Arguments& args) { FrameDisplay* environment = (FrameDisplay*)&NullFrameDisplay; ModuleID moduleID = kmodGlobal; BOOL strictMode = FALSE; // TODO: Handle call from global scope, strict mode BOOL isIndirect = FALSE; if (args.IsDirectEvalCall()) { // This was recognized as an eval call at compile time. The last one or two args are internal to us. // Argcount will be one of the following when called from global code // - eval("...") : argcount 3 : this, evalString, frameDisplay // - eval.call("..."): argcount 2 : this(which is string) , frameDisplay if (args.Info.Count >= 1) { environment = args.GetFrameDisplay(); strictMode = environment->GetStrictMode(); } } else { // This must be an indirect "eval" call that we didn't detect at compile time. // Pass null as the environment, which will force all lookups in the eval code // to use the root for the current module. // Also pass "null" for "this", which will force the callee to use the current module root. isIndirect = !PHASE_OFF1(Js::FastIndirectEvalPhase); } return GlobalObject::VEval(function->GetLibrary(), environment, moduleID, !!strictMode, !!isIndirect, args, /* isLibraryCode = */ false, /* registerDocument */ true, /*additionalGrfscr */ 0); } Var GlobalObject::EntryEvalRestrictedMode(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); RUNTIME_ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); JavascriptLibrary* library = function->GetLibrary(); ScriptContext* scriptContext = library->GetScriptContext(); scriptContext->CheckEvalRestriction(); return EntryEvalHelper(scriptContext, function, args); } // This function is used to decipher eval function parameters and we don't want the stack arguments optimization by C++ compiler so turning off the optimization Var GlobalObject::EntryEval(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); RUNTIME_ARGUMENTS(args, callInfo); Assert(!(callInfo.Flags & CallFlags_New)); JavascriptLibrary* library = function->GetLibrary(); ScriptContext* scriptContext = library->GetScriptContext(); return EntryEvalHelper(scriptContext, function, args); } Var GlobalObject::VEval(JavascriptLibrary* library, FrameDisplay* environment, ModuleID moduleID, bool strictMode, bool isIndirect, Arguments& args, bool isLibraryCode, bool registerDocument, uint32 additionalGrfscr, ScriptContext* debugEvalScriptContext) { Assert(library); ScriptContext* scriptContext = library->GetScriptContext(); unsigned argCount = args.Info.Count; AssertMsg(argCount > 0, "Should always have implicit 'this'"); bool doRegisterDocument = registerDocument & !isLibraryCode; Var varThis = library->GetUndefined(); if (argCount < 2) { return library->GetUndefined(); } Var evalArg = args[1]; if (!VarIs(evalArg)) { // "If x is not a string value, return x." return evalArg; } #ifdef ENABLE_SCRIPT_DEBUGGING // It might happen that no script parsed on this context (scriptContext) till now, // so this Eval acts as the first source compile for scriptContext, transition to debugMode as needed scriptContext->TransitionToDebugModeIfFirstSource(/* utf8SourceInfo = */ nullptr); #endif ScriptFunction *pfuncScript = nullptr; JavascriptString *argString = VarTo(evalArg); char16 const * sourceString = argString->GetSz(); charcount_t sourceLen = argString->GetLength(); FastEvalMapString key(argString, sourceString, sourceLen, moduleID, strictMode, isLibraryCode); // PropertyString's buffer references to PropertyRecord's inline buffer, if both PropertyString and PropertyRecord are collected // we'll leave the PropertyRecord's interior buffer pointer in the EvalMap. So do not use evalmap if we are evaluating PropertyString bool useEvalMap = !VirtualTableInfo::HasVirtualTable(argString) && debugEvalScriptContext == nullptr; // Don't use the cache in case of debugEval bool found = useEvalMap && scriptContext->IsInEvalMap(key, isIndirect, &pfuncScript); if (!found || (!isIndirect && pfuncScript->GetEnvironment() != &NullFrameDisplay)) { uint32 grfscr = additionalGrfscr | fscrReturnExpression | fscrEval | fscrEvalCode | fscrGlobalCode; if (isLibraryCode) { grfscr |= fscrIsLibraryCode; } if (!(grfscr & fscrConsoleScopeEval)) { grfscr |= fscrCanDeferFncParse; } pfuncScript = library->GetGlobalObject()->EvalHelper(scriptContext, argString->GetSz(), argString->GetLength(), moduleID, grfscr, Constants::EvalCode, doRegisterDocument, isIndirect, strictMode); if (debugEvalScriptContext != nullptr && CrossSite::NeedMarshalVar(pfuncScript, debugEvalScriptContext)) { // This is console scope scenario. DebugEval script context is on the top of the stack. But we are going // to execute the user script from target script context. In order to fix the script context stack we // need to marshall the function object. pfuncScript = VarTo(CrossSite::MarshalVar(debugEvalScriptContext, pfuncScript)); } if (useEvalMap && !found) { scriptContext->AddToEvalMap(key, isIndirect, pfuncScript); } } #ifdef ENABLE_DEBUG_CONFIG_OPTIONS else { Js::Utf8SourceInfo* utf8SourceInfo = pfuncScript->GetFunctionBody()->GetUtf8SourceInfo(); if (scriptContext->IsScriptContextInDebugMode() && !utf8SourceInfo->GetIsLibraryCode() && !utf8SourceInfo->IsInDebugMode()) { // Identifying if any non library function escaped for not being in debug mode. Throw::FatalInternalError(); } } #endif #if ENABLE_TTD // //TODO: We may (probably?) want to use the debugger source rundown functionality here instead // if(!isLibraryCode && pfuncScript != nullptr && (scriptContext->IsTTDRecordModeEnabled() || scriptContext->ShouldPerformReplayAction())) { //Make sure we have the body and text information available FunctionBody* globalBody = TTD::JsSupport::ForceAndGetFunctionBody(pfuncScript->GetParseableFunctionInfo()); if(!scriptContext->TTDContextInfo->IsBodyAlreadyLoadedAtTopLevel(globalBody)) { uint32 bodyIdCtr = 0; if(scriptContext->IsTTDRecordModeEnabled()) { const TTD::NSSnapValues::TopLevelEvalFunctionBodyResolveInfo* tbfi = scriptContext->GetThreadContext()->TTDLog->AddEvalFunction(globalBody, moduleID, sourceString, sourceLen, additionalGrfscr, registerDocument, isIndirect, strictMode); //We always want to register the top-level load but we don't always need to log the event if(scriptContext->ShouldPerformRecordAction()) { scriptContext->GetThreadContext()->TTDLog->RecordTopLevelCodeAction(tbfi->TopLevelBase.TopLevelBodyCtr); } bodyIdCtr = tbfi->TopLevelBase.TopLevelBodyCtr; } if(scriptContext->ShouldPerformReplayAction()) { bodyIdCtr = scriptContext->GetThreadContext()->TTDLog->ReplayTopLevelCodeAction(); } //walk global body to (1) add functions to pin set (2) build parent map scriptContext->TTDContextInfo->ProcessFunctionBodyOnLoad(globalBody, nullptr); scriptContext->TTDContextInfo->RegisterEvalScript(globalBody, bodyIdCtr); if(scriptContext->ShouldPerformRecordOrReplayAction()) { globalBody->GetUtf8SourceInfo()->SetSourceInfoForDebugReplay_TTD(bodyIdCtr); } if(scriptContext->ShouldPerformReplayDebuggerAction()) { scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessScriptLoad(scriptContext, bodyIdCtr, globalBody, globalBody->GetUtf8SourceInfo(), nullptr); } } } #endif //We shouldn't be serializing eval functions; unless with -ForceSerialized flag if (CONFIG_FLAG(ForceSerialized)) { pfuncScript->GetFunctionProxy()->EnsureDeserialized(); } if (pfuncScript->HasSuperReference()) { // Indirect evals cannot have a super reference. if (!(args.Info.Flags & CallFlags_ExtraArg)) { JavascriptError::ThrowSyntaxError(scriptContext, ERRSuperInIndirectEval, _u("super")); } } return library->GetGlobalObject()->ExecuteEvalParsedFunction(pfuncScript, environment, varThis, scriptContext); } void GlobalObject::UpdateThisForEval(Var &varThis, ModuleID moduleID, ScriptContext *scriptContext) { varThis = JavascriptOperators::OP_GetThisNoFastPath(varThis, moduleID, scriptContext); } Var GlobalObject::ExecuteEvalParsedFunction(ScriptFunction *pfuncScript, FrameDisplay* environment, Var &varThis, ScriptContext *scriptContext) { Assert(pfuncScript != nullptr); pfuncScript->SetEnvironment(environment); //This function is supposed to be deserialized Assert(pfuncScript->GetFunctionBody()); if (pfuncScript->GetFunctionBody()->GetFuncEscapes()) { // Executing the eval causes the scope chain to escape. pfuncScript->InvalidateCachedScopeChain(); } Var varResult = nullptr; BEGIN_SAFE_REENTRANT_CALL(scriptContext->GetThreadContext()) { varResult = CALL_FUNCTION(scriptContext->GetThreadContext(), pfuncScript, CallInfo(CallFlags_Eval, 1), varThis); } END_SAFE_REENTRANT_CALL pfuncScript->SetEnvironment((FrameDisplay*)&NullFrameDisplay); return varResult; } #ifdef ENABLE_SCRIPT_PROFILING ScriptFunction* GlobalObject::ProfileModeEvalHelper(ScriptContext* scriptContext, const char16 *source, int sourceLength, ModuleID moduleID, uint32 grfscr, LPCOLESTR pszTitle, BOOL registerDocument, BOOL isIndirect, BOOL strictMode) { #if ENABLE_DEBUG_CONFIG_OPTIONS char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; #endif ScriptFunction *pEvalFunction = DefaultEvalHelper(scriptContext, source, sourceLength, moduleID, grfscr, pszTitle, registerDocument, isIndirect, strictMode); Assert(pEvalFunction); Js::FunctionProxy *proxy = pEvalFunction->GetFunctionProxy(); Assert(proxy); OUTPUT_TRACE(Js::ScriptProfilerPhase, _u("GlobalObject::ProfileModeEvalHelper FunctionNumber : %s, Entrypoint : 0x%08X IsFunctionDefer : %d\n"), proxy->GetDebugNumberSet(debugStringBuffer), pEvalFunction->GetEntryPoint(), proxy->IsDeferred()); if (proxy->IsDeferred()) { // This could happen if the top level function is marked as deferred, we need to parse this to generate the script compile information (RegisterScript depends on that) Js::JavascriptFunction::DeferredParse(&pEvalFunction); proxy = pEvalFunction->GetFunctionProxy(); } scriptContext->RegisterScript(proxy); return pEvalFunction; } #endif void GlobalObject::ValidateSyntax(ScriptContext* scriptContext, const char16 *source, int sourceLength, bool isGenerator, bool isAsync, void (Parser::*validateSyntax)()) { Assert(sourceLength >= 0); HRESULT hr = S_OK; HRESULT hrParser = E_FAIL; CompileScriptException se; BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext); BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT { ArenaAllocator tempAlloc(_u("ValidateSyntaxArena"), scriptContext->GetThreadContext()->GetPageAllocator(), Throw::OutOfMemory); size_t cchSource = sourceLength; size_t cbUtf8Buffer = UInt32Math::MulAdd<3, 1>(sourceLength); LPUTF8 utf8Source = AnewArray(&tempAlloc, utf8char_t, cbUtf8Buffer); Assert(cchSource < MAXLONG); size_t cbSource = utf8::EncodeIntoAndNullTerminate(utf8Source, cbUtf8Buffer, source, static_cast(cchSource)); utf8Source = reinterpret_cast< LPUTF8 >( tempAlloc.Realloc(utf8Source, cbUtf8Buffer, cbSource + 1) ); Parser parser(scriptContext); hrParser = parser.ValidateSyntax(utf8Source, cbSource, isGenerator, isAsync, &se, validateSyntax); } END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); END_LEAVE_SCRIPT_INTERNAL(scriptContext); if (FAILED(hr)) { if (hr == E_OUTOFMEMORY) { JavascriptError::ThrowOutOfMemoryError(scriptContext); } if (hr == VBSERR_OutOfStack) { JavascriptError::ThrowStackOverflowError(scriptContext); } } if (!SUCCEEDED(hrParser)) { hrParser = SCRIPT_E_RECORDED; EXCEPINFO ei; se.GetError(&hrParser, &ei); ErrorTypeEnum errorType; switch ((HRESULT)ei.scode) { #define RT_ERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) \ case name: \ errorType = jst; \ break; #define RT_PUBLICERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) RT_ERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) #include "rterrors.h" #undef RT_PUBLICERROR_MSG #undef RT_ERROR_MSG default: errorType = kjstSyntaxError; } JavascriptError::MapAndThrowError(scriptContext, ei.scode, errorType, &ei); } } ScriptFunction* GlobalObject::DefaultEvalHelper(ScriptContext* scriptContext, const char16 *source, int sourceLength, ModuleID moduleID, uint32 grfscr, LPCOLESTR pszTitle, BOOL registerDocument, BOOL isIndirect, BOOL strictMode) { Assert(sourceLength >= 0); AnalysisAssert(scriptContext); if (scriptContext->GetThreadContext()->EvalDisabled()) { throw Js::EvalDisabledException(); } #ifdef PROFILE_EXEC scriptContext->ProfileBegin(Js::EvalCompilePhase); #endif void * frameAddr = nullptr; GET_CURRENT_FRAME_ID(frameAddr); HRESULT hr = S_OK; HRESULT hrParser = S_OK; HRESULT hrCodeGen = S_OK; CompileScriptException se; Js::ParseableFunctionInfo * funcBody = NULL; uint sourceIndex = Constants::InvalidSourceIndex; BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext); BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT { uint cchSource = sourceLength; size_t cbUtf8Buffer = UInt32Math::MulAdd<3, 1>(cchSource); ArenaAllocator tempArena(_u("EvalHelperArena"), scriptContext->GetThreadContext()->GetPageAllocator(), Js::Throw::OutOfMemory); LPUTF8 utf8Source = AnewArray(&tempArena, utf8char_t, cbUtf8Buffer); Assert(cchSource < MAXLONG); size_t cbSource = utf8::EncodeIntoAndNullTerminate(utf8Source, cbUtf8Buffer, source, static_cast(cchSource)); Assert(cbSource + 1 <= cbUtf8Buffer); SRCINFO const * pSrcInfo = scriptContext->GetModuleSrcInfo(moduleID); // Source Info objects are kept alive by the function bodies that are referencing it // The function body is created in GenerateByteCode but the source info isn't passed in, only the index // So we need to pin it here (TODO: Change GenerateByteCode to take in the sourceInfo itself) ENTER_PINNED_SCOPE(Utf8SourceInfo, sourceInfo); sourceInfo = Utf8SourceInfo::New(scriptContext, utf8Source, cchSource, cbSource, pSrcInfo, ((grfscr & fscrIsLibraryCode) != 0)); Parser parser(scriptContext, strictMode); bool forceNoNative = false; ParseNodeProg * parseTree = nullptr; SourceContextInfo * sourceContextInfo = pSrcInfo->sourceContextInfo; ULONG deferParseThreshold = Parser::GetDeferralThreshold(sourceContextInfo->IsSourceProfileLoaded()); if ((ULONG)sourceLength > deferParseThreshold && !PHASE_OFF1(Phase::DeferParsePhase)) { // Defer function bodies declared inside large dynamic blocks. grfscr |= fscrWillDeferFncParse; } grfscr = grfscr | fscrDynamicCode; // fscrEval signifies direct eval in parser hrParser = parser.ParseCesu8Source(&parseTree, utf8Source, cbSource, isIndirect ? grfscr & ~fscrEval : grfscr, &se, &sourceContextInfo->nextLocalFunctionId, sourceContextInfo); sourceInfo->SetParseFlags(grfscr); if (SUCCEEDED(hrParser) && parseTree) { // This keeps function bodies generated by the byte code alive till we return Js::AutoDynamicCodeReference dynamicFunctionReference(scriptContext); Assert(cchSource < MAXLONG); sourceIndex = scriptContext->SaveSourceNoCopy(sourceInfo, cchSource, true); // Tell byte code gen not to attempt to interact with the caller's context if this is indirect eval. // TODO: Handle strict mode. if (isIndirect && !strictMode && !parseTree->GetStrictMode()) { grfscr &= ~fscrEval; } hrCodeGen = GenerateByteCode(parseTree, grfscr, scriptContext, &funcBody, sourceIndex, forceNoNative, &parser, &se); sourceInfo->SetByteCodeGenerationFlags(grfscr); } LEAVE_PINNED_SCOPE(); } END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); END_LEAVE_SCRIPT_INTERNAL(scriptContext); #ifdef PROFILE_EXEC scriptContext->ProfileEnd(Js::EvalCompilePhase); #endif THROW_KNOWN_HRESULT_EXCEPTIONS(hr, scriptContext); if (!SUCCEEDED(hrParser)) { JavascriptError::ThrowParserError(scriptContext, hrParser, &se); } else if (!SUCCEEDED(hrCodeGen)) { Assert(hrCodeGen == SCRIPT_E_RECORDED); hrCodeGen = se.ei.scode; /* * VBSERR_OutOfStack is of type kjstError but we throw a (more specific) StackOverflowError when a hard stack * overflow occurs. To keep the behavior consistent I'm special casing it here. */ se.Free(); if (hrCodeGen == VBSERR_OutOfStack) { JavascriptError::ThrowStackOverflowError(scriptContext); } else if (hrCodeGen == JSERR_AsmJsCompileError) { // if asm.js compilation failed, retry with asm.js disabled grfscr |= fscrNoAsmJs; if (sourceIndex != Constants::InvalidSourceIndex) { // If we registered source, we should remove it or we will register another source info scriptContext->RemoveSource(sourceIndex); } return DefaultEvalHelper(scriptContext, source, sourceLength, moduleID, grfscr, pszTitle, registerDocument, isIndirect, strictMode); } JavascriptError::MapAndThrowError(scriptContext, hrCodeGen); } else { Assert(funcBody != nullptr); funcBody->SetDisplayName(pszTitle); // Set the functionbody information to dynamic content PROFILER_SCRIPT_TYPE_DYNAMIC funcBody->SetIsTopLevel(true); // If not library code then let's find the parent, we may need to register this source if any exception happens later if ((grfscr & fscrIsLibraryCode) == 0) { // For parented eval get the caller's utf8SourceInfo JavascriptFunction* pfuncCaller = nullptr; if (JavascriptStackWalker::GetCaller(&pfuncCaller, scriptContext) && pfuncCaller && pfuncCaller->IsScriptFunction()) { FunctionBody* parentFuncBody = pfuncCaller->GetFunctionBody(); Utf8SourceInfo* parentUtf8SourceInfo = parentFuncBody->GetUtf8SourceInfo(); Utf8SourceInfo* utf8SourceInfo = funcBody->GetUtf8SourceInfo(); utf8SourceInfo->SetCallerUtf8SourceInfo(parentUtf8SourceInfo); } } if (registerDocument) { funcBody->RegisterFuncToDiag(scriptContext, pszTitle); funcBody = funcBody->GetParseableFunctionInfo(); // RegisterFunction may parse and update function body } ScriptFunction* pfuncScript = funcBody->IsCoroutine() ? scriptContext->GetLibrary()->CreateGeneratorVirtualScriptFunction(funcBody) : scriptContext->GetLibrary()->CreateScriptFunction(funcBody); return pfuncScript; } } #ifdef IR_VIEWER Var GlobalObject::IRDumpEvalHelper(ScriptContext* scriptContext, const char16 *source, int sourceLength, ModuleID moduleID, uint32 grfscr, LPCOLESTR pszTitle, BOOL registerDocument, BOOL isIndirect, BOOL strictMode) { // TODO (t-doilij) clean up this function, specifically used for IR dump (don't execute bytecode; potentially dangerous) Assert(sourceLength >= 0); if (scriptContext->GetThreadContext()->EvalDisabled()) { throw Js::EvalDisabledException(); } #ifdef PROFILE_EXEC scriptContext->ProfileBegin(Js::EvalCompilePhase); #endif void * frameAddr = nullptr; GET_CURRENT_FRAME_ID(frameAddr); HRESULT hr = S_OK; HRESULT hrParser = S_OK; HRESULT hrCodeGen = S_OK; CompileScriptException se; Js::ParseableFunctionInfo * funcBody = NULL; BEGIN_LEAVE_SCRIPT_INTERNAL(scriptContext); BEGIN_TRANSLATE_EXCEPTION_TO_HRESULT { size_t cchSource = sourceLength; size_t cbUtf8Buffer = UInt32Math::AddMul<1, 3>(sourceLength); ArenaAllocator tempArena(_u("EvalHelperArena"), scriptContext->GetThreadContext()->GetPageAllocator(), Js::Throw::OutOfMemory); LPUTF8 utf8Source = AnewArray(&tempArena, utf8char_t, cbUtf8Buffer); Assert(cchSource < MAXLONG); size_t cbSource = utf8::EncodeIntoAndNullTerminate(utf8Source, source, static_cast< charcount_t >(cchSource)); Assert(cbSource + 1 <= cbUtf8Buffer); SRCINFO const * pSrcInfo = scriptContext->GetModuleSrcInfo(moduleID); // Source Info objects are kept alive by the function bodies that are referencing it // The function body is created in GenerateByteCode but the source info isn't passed in, only the index // So we need to pin it here (TODO: Change GenerateByteCode to take in the sourceInfo itself) ENTER_PINNED_SCOPE(Utf8SourceInfo, sourceInfo); sourceInfo = Utf8SourceInfo::New(scriptContext, utf8Source, cchSource, cbSource, pSrcInfo, ((grfscr & fscrIsLibraryCode) != 0)); Parser parser(scriptContext, strictMode); bool forceNoNative = false; ParseNodePtr parseTree = nullptr; SourceContextInfo * sourceContextInfo = pSrcInfo->sourceContextInfo; grfscr = grfscr | fscrDynamicCode; hrParser = parser.ParseCesu8Source(&parseTree, utf8Source, cbSource, grfscr, &se, &sourceContextInfo->nextLocalFunctionId, sourceContextInfo); sourceInfo->SetParseFlags(grfscr); if (SUCCEEDED(hrParser) && parseTree) { // This keeps function bodies generated by the byte code alive till we return Js::AutoDynamicCodeReference dynamicFunctionReference(scriptContext); Assert(cchSource < MAXLONG); uint sourceIndex = scriptContext->SaveSourceNoCopy(sourceInfo, cchSource, true); grfscr = grfscr | fscrIrDumpEnable; hrCodeGen = GenerateByteCode(parseTree, grfscr, scriptContext, &funcBody, sourceIndex, forceNoNative, &parser, &se); sourceInfo->SetByteCodeGenerationFlags(grfscr); } LEAVE_PINNED_SCOPE(); } END_TRANSLATE_EXCEPTION_TO_HRESULT(hr); END_LEAVE_SCRIPT_INTERNAL(scriptContext); #ifdef PROFILE_EXEC scriptContext->ProfileEnd(Js::EvalCompilePhase); #endif THROW_KNOWN_HRESULT_EXCEPTIONS(hr); if (!SUCCEEDED(hrParser)) { hrParser = SCRIPT_E_RECORDED; EXCEPINFO ei; se.GetError(&hrParser, &ei); ErrorTypeEnum errorType; switch ((HRESULT)ei.scode) { #define RT_ERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) \ case name: \ errorType = jst; \ break; #define RT_PUBLICERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) RT_ERROR_MSG(name, errnum, str1, str2, jst, errorNumSource) #include "rterrors.h" #undef RT_PUBLICERROR_MSG #undef RT_ERROR_MSG default: errorType = kjstSyntaxError; } JavascriptError::MapAndThrowError(scriptContext, ei.scode, errorType, &ei); } else if (!SUCCEEDED(hrCodeGen)) { Assert(hrCodeGen == SCRIPT_E_RECORDED); hrCodeGen = se.ei.scode; /* * VBSERR_OutOfStack is of type kjstError but we throw a (more specific) StackOverflowError when a hard stack * overflow occurs. To keep the behavior consistent I'm special casing it here. */ se.Free(); if (hrCodeGen == VBSERR_OutOfStack) { JavascriptError::ThrowStackOverflowError(scriptContext); } JavascriptError::MapAndThrowError(scriptContext, hrCodeGen); } else { Assert(funcBody != nullptr); funcBody->SetDisplayName(pszTitle); // Set the functionbody information to dynamic content PROFILER_SCRIPT_TYPE_DYNAMIC funcBody->SetIsTopLevel(true); if (registerDocument) { funcBody->RegisterFuncToDiag(scriptContext, pszTitle); funcBody = funcBody->GetParseableFunctionInfo(); // RegisterFunction may parse and update function body } NativeCodeGenerator *nativeCodeGenerator = scriptContext->GetNativeCodeGenerator(); Js::FunctionBody *functionBody = funcBody->GetFunctionBody(); // funcBody is ParseableFunctionInfo if (!functionBody || !functionBody->GetByteCode()) { return scriptContext->GetLibrary()->GetUndefined(); } Js::Var retVal = RejitIRViewerFunction(nativeCodeGenerator, functionBody, scriptContext); return retVal; } } #endif /* IR_VIEWER */ Var GlobalObject::EntryParseInt(RecyclableObject* function, CallInfo callInfo, ...) { JIT_HELPER_REENTRANT_HEADER(GlobalObject_ParseInt); PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); JavascriptString *str; int radix = 0; if(args.Info.Count < 2) { // We're really converting "undefined" here, but "undefined" produces // NaN, so just return that directly. return JavascriptNumber::ToVarNoCheck(JavascriptNumber::NaN, scriptContext); } // Short cut for numbers and radix 10 if (((args.Info.Count == 2) || (TaggedInt::Is(args[2]) && TaggedInt::ToInt32(args[2]) == 10) || (JavascriptNumber::Is(args[2]) && JavascriptNumber::GetValue(args[2]) == 10))) { if (TaggedInt::Is(args[1])) { return args[1]; } if (JavascriptNumber::Is(args[1])) { double value = JavascriptNumber::GetValue(args[1]); // make sure we are in the ranges that don't have exponential notation. double absValue = Math::Abs(value); if (absValue < 1.0e21 && absValue >= 1e-5) { double result; #pragma prefast(suppress:6031, "We don't care about the fraction part") ::modf(value, &result); return JavascriptNumber::ToVarIntCheck(result, scriptContext); } } } // convert input to a string if (VarIs(args[1])) { str = VarTo(args[1]); } else { str = JavascriptConversion::ToString(args[1], scriptContext); } if(args.Info.Count > 2) { // retrieve the radix // TODO : verify for ES5 : radix is 10 when undefined or 0, except when it starts with 0x when it is 16. radix = JavascriptConversion::ToInt32(args[2], scriptContext); if(radix < 0 || radix == 1 || radix > 36) { //illegal, return NaN return JavascriptNumber::ToVarNoCheck(JavascriptNumber::NaN, scriptContext); } } Var result = str->ToInteger(radix); return result; JIT_HELPER_END(GlobalObject_ParseInt); } Var GlobalObject::EntryParseFloat(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); double result = 0; ENTER_PINNED_SCOPE(JavascriptString, str); if(args.Info.Count < 2) { // We're really converting "undefined" here, but "undefined" produces // NaN, so just return that directly. return JavascriptNumber::ToVarNoCheck(JavascriptNumber::NaN, scriptContext); } // Short cut for numbers if (TaggedInt::Is(args[1])) { return args[1]; } if(JavascriptNumber::Is_NoTaggedIntCheck(args[1])) { // If the value is negative zero, return positive zero. Since the parameter type is string, -0 would first be // converted to the string "0", then parsed to result in positive zero. return JavascriptNumber::IsNegZero(JavascriptNumber::GetValue(args[1])) ? TaggedInt::ToVarUnchecked(0) : args[1]; } // convert input to a string if (VarIs(args[1])) { str = VarTo(args[1]); } else { str = JavascriptConversion::ToString(args[1], scriptContext); } // skip preceding whitespace const char16 *pch = scriptContext->GetCharClassifier()->SkipWhiteSpace(str->GetSz()); // perform the string -> float conversion result = NumberUtilities::StrToDbl(pch, &pch, scriptContext); LEAVE_PINNED_SCOPE(); // str return JavascriptNumber::ToVarNoCheck(result, scriptContext); } Var GlobalObject::EntryIsNaN(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); if(args.Info.Count < 2) { return scriptContext->GetLibrary()->GetTrue(); } return JavascriptBoolean::ToVar( JavascriptNumber::IsNan(JavascriptConversion::ToNumber(args[1],scriptContext)), scriptContext); } Var GlobalObject::EntryIsFinite(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); if(args.Info.Count < 2) { return scriptContext->GetLibrary()->GetFalse(); } return JavascriptBoolean::ToVar( NumberUtilities::IsFinite(JavascriptConversion::ToNumber(args[1],scriptContext)), scriptContext); } Var GlobalObject::EntryDecodeURI(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); return UriHelper::DecodeCoreURI(scriptContext, args, UriHelper::URIReserved); } Var GlobalObject::EntryDecodeURIComponent(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); return UriHelper::DecodeCoreURI(scriptContext, args, UriHelper::URINone); } Var GlobalObject::EntryEncodeURI(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); return UriHelper::EncodeCoreURI(scriptContext, args, UriHelper::URIReserved | UriHelper::URIUnescaped); } Var GlobalObject::EntryEncodeURIComponent(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); AssertMsg(args.Info.Count > 0, "Should always have implicit 'this'"); return UriHelper::EncodeCoreURI(scriptContext, args, UriHelper::URIUnescaped); } // // Part of Appendix B of ES5 spec // // This is a bit field for the hex values: 00-29, 2C, 3A-3F, 5B-5E, 60, 7B-FF // These are the values escape encodes using the default mask (or mask >= 4) static const BYTE _grfbitEscape[] = { 0xFF, 0xFF, // 00 - 0F 0xFF, 0xFF, // 10 - 1F 0xFF, 0x13, // 20 - 2F 0x00, 0xFC, // 30 - 3F 0x00, 0x00, // 40 - 4F 0x00, 0x78, // 50 - 5F 0x01, 0x00, // 60 - 6F 0x00, 0xF8, // 70 - 7F 0xFF, 0xFF, // 80 - 8F 0xFF, 0xFF, // 90 - 9F 0xFF, 0xFF, // A0 - AF 0xFF, 0xFF, // B0 - BF 0xFF, 0xFF, // C0 - CF 0xFF, 0xFF, // D0 - DF 0xFF, 0xFF, // E0 - EF 0xFF, 0xFF, // F0 - FF }; Var GlobalObject::EntryEscape(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); if (args.Info.Count <= 1) { return scriptContext->GetLibrary()->GetUndefinedDisplayString(); } CompoundString * bs = nullptr; ENTER_PINNED_SCOPE(JavascriptString, src); src = JavascriptConversion::ToString(args[1], scriptContext); bs = CompoundString::NewWithCharCapacity(src->GetLength(), scriptContext->GetLibrary()); char16 chw; char16 * pchSrc; char16 * pchLim; const char _rgchHex[] = "0123456789ABCDEF"; pchSrc = const_cast(src->GetString()); pchLim = pchSrc + src->GetLength(); while (pchSrc < pchLim) { chw = *pchSrc++; if (0 != (chw & 0xFF00)) { bs->AppendChars(_u("%u")); bs->AppendChars(static_cast(_rgchHex[(chw >> 12) & 0x0F])); bs->AppendChars(static_cast(_rgchHex[(chw >> 8) & 0x0F])); bs->AppendChars(static_cast(_rgchHex[(chw >> 4) & 0x0F])); bs->AppendChars(static_cast(_rgchHex[chw & 0x0F])); } else if (_grfbitEscape[chw >> 3] & (1 << (chw & 7))) { // Escape the byte. bs->AppendChars(_u('%')); bs->AppendChars(static_cast(_rgchHex[chw >> 4])); bs->AppendChars(static_cast(_rgchHex[chw & 0x0F])); } else { bs->AppendChars(chw); } } LEAVE_PINNED_SCOPE(); // src return bs; } // // Part of Appendix B of ES5 spec // Var GlobalObject::EntryUnEscape(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); ScriptContext* scriptContext = function->GetScriptContext(); Assert(!(callInfo.Flags & CallFlags_New)); if (args.Info.Count <= 1) { return scriptContext->GetLibrary()->GetUndefinedDisplayString(); } char16 chw; char16 chT; char16 * pchSrc; char16 * pchLim; char16 * pchMin; CompoundString * bs = nullptr; ENTER_PINNED_SCOPE(JavascriptString, src); src = JavascriptConversion::ToString(args[1], scriptContext); bs = CompoundString::NewWithCharCapacity(src->GetLength(), scriptContext->GetLibrary()); pchSrc = const_cast(src->GetString()); pchLim = pchSrc + src->GetLength(); while (pchSrc < pchLim) { if ('%' != (chT = *pchSrc++)) { bs->AppendChars(chT); continue; } pchMin = pchSrc; chT = *pchSrc++; if ('u' != chT) { chw = 0; goto LTwoHexDigits; } // 4 hex digits. if ((chT = *pchSrc++ - '0') <= 9) chw = chT * 0x1000; else if ((chT -= 'A' - '0') <= 5 || (chT -= 'a' - 'A') <= 5) chw = (chT + 10) * 0x1000; else goto LHexError; if ((chT = *pchSrc++ - '0') <= 9) chw += chT * 0x0100; else if ((chT -= 'A' - '0') <= 5 || (chT -= 'a' - 'A') <= 5) chw += (chT + 10) * 0x0100; else goto LHexError; chT = *pchSrc++; LTwoHexDigits: if ((chT -= '0') <= 9) chw += chT * 0x0010; else if ((chT -= 'A' - '0') <= 5 || (chT -= 'a' - 'A') <= 5) chw += (chT + 10) * 0x0010; else goto LHexError; if ((chT = *pchSrc++ - '0') <= 9) chw += chT; else if ((chT -= 'A' - '0') <= 5 || (chT -= 'a' - 'A') <= 5) chw += chT + 10; else { LHexError: pchSrc = pchMin; chw = '%'; } bs->AppendChars(chw); } LEAVE_PINNED_SCOPE(); // src return bs; } #if DBG void DebugClearStack() { char buffer[1024]; memset(buffer, 0, sizeof(buffer)); } #endif Var GlobalObject::EntryCollectGarbage(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); #if DBG_DUMP if (Js::Configuration::Global.flags.TraceWin8Allocations) { Output::Print(_u("MemoryTrace: GlobalObject::EntryCollectGarbage Invoke\n")); Output::Flush(); } #endif #if DBG // Clear 1K of stack to avoid false positive in debug build. // Because we don't debug build don't stack pack DebugClearStack(); #endif Assert(!(callInfo.Flags & CallFlags_New)); ScriptContext* scriptContext = function->GetScriptContext(); Recycler* recycler = scriptContext->GetRecycler(); if (recycler) { #if DBG && defined(RECYCLER_DUMP_OBJECT_GRAPH) ARGUMENTS(args, callInfo); if (args.Info.Count > 1) { double value = JavascriptConversion::ToNumber(args[1], function->GetScriptContext()); if (value == 1.0) { recycler->dumpObjectOnceOnCollect = true; } } #endif recycler->CollectNow(); } #if DBG_DUMP if (Js::Configuration::Global.flags.TraceWin8Allocations) { Output::Print(_u("MemoryTrace: GlobalObject::EntryCollectGarbage Exit\n")); Output::Flush(); } #endif return scriptContext->GetLibrary()->GetUndefined(); } #ifdef ENABLE_DEBUG_CONFIG_OPTIONS Var GlobalObject::EntryChWriteTraceEvent(RecyclableObject *function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); if (args.Info.Count < 2) { return function->GetScriptContext()->GetLibrary()->GetUndefined(); } JS_ETW(EventWriteJSCRIPT_INTERNAL_GENERIC_EVENT(Js::JavascriptConversion::ToString(args[1], function->GetScriptContext())->GetSz())); return function->GetScriptContext()->GetLibrary()->GetUndefined(); } #endif #if ENABLE_TTD //Log a string in the telemetry system (and print to the console) Var GlobalObject::EntryTelemetryLog(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); TTDAssert(args.Info.Count >= 2 && Js::VarIs(args[1]), "Bad arguments!!!"); Js::JavascriptString* jsString = Js::VarTo(args[1]); bool doPrint = (args.Info.Count == 3) && Js::VarIs(args[2]) && (Js::VarTo(args[2])->GetValue()); if(function->GetScriptContext()->ShouldPerformReplayAction()) { function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayTelemetryLogEvent(jsString); } if(function->GetScriptContext()->ShouldPerformRecordAction()) { function->GetScriptContext()->GetThreadContext()->TTDLog->RecordTelemetryLogEvent(jsString, doPrint); } if(doPrint) { // //TODO: the host should give us a print callback which we can use here // Output::Print(_u("%ls\n"), jsString->GetSz()); fflush(stdout); } return function->GetScriptContext()->GetLibrary()->GetUndefined(); } //Check if diagnostic trace writing is enabled Var GlobalObject::EntryEnabledDiagnosticsTrace(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); if (function->GetScriptContext()->ShouldPerformReplayAction()) { TTD::EventLog* ttlog = function->GetScriptContext()->GetThreadContext()->TTDLog; bool isEnabled = ttlog->ReplayTTDFetchAutoTraceStatusLogEvent(); return function->GetScriptContext()->GetLibrary()->CreateBoolean(isEnabled); } else if (function->GetScriptContext()->ShouldPerformRecordAction()) { TTD::EventLog* ttlog = function->GetScriptContext()->GetThreadContext()->TTDLog; bool isEnabled = ttlog->GetAutoTraceEnabled(); ttlog->RecordTTDFetchAutoTraceStatusEvent(isEnabled); return function->GetScriptContext()->GetLibrary()->CreateBoolean(isEnabled); } else { return function->GetScriptContext()->GetLibrary()->GetFalse(); } } //Write a copy of the current TTD log to a specified location Var GlobalObject::EntryEmitTTDLog(RecyclableObject* function, CallInfo callInfo, ...) { PROBE_STACK(function->GetScriptContext(), Js::Constants::MinStackDefault); ARGUMENTS(args, callInfo); Js::JavascriptLibrary* jslib = function->GetScriptContext()->GetLibrary(); if(args.Info.Count != 2 || !Js::VarIs(args[1])) { return jslib->GetFalse(); } if(function->GetScriptContext()->ShouldPerformReplayAction()) { function->GetScriptContext()->GetThreadContext()->TTDLog->ReplayEmitLogEvent(); return jslib->GetTrue(); } if(function->GetScriptContext()->ShouldPerformRecordAction()) { Js::JavascriptString* jsString = Js::VarTo(args[1]); function->GetScriptContext()->GetThreadContext()->TTDLog->RecordEmitLogEvent(jsString); return jslib->GetTrue(); } return jslib->GetFalse(); } #endif //Pattern match is unique to RuntimeObject. Only leading and trailing * are implemented //Example: *pat*tern* actually matches all the strings having pat*tern as substring. BOOL GlobalObject::MatchPatternHelper(JavascriptString *propertyName, JavascriptString *pattern, ScriptContext *scriptContext) { if (nullptr == propertyName || nullptr == pattern) { return FALSE; } charcount_t patternLength = pattern->GetLength(); charcount_t nameLength = propertyName->GetLength(); BOOL bStart; BOOL bEnd; if (0 == patternLength) { return TRUE; // empty pattern matches all } bStart = (_u('*') == pattern->GetItem(0));// Is there a start star? bEnd = (_u('*') == pattern->GetItem(patternLength - 1));// Is there an end star? //deal with the stars if (bStart || bEnd) { JavascriptString *subPattern; int idxStart = bStart? 1: 0; int idxEnd = bEnd? (patternLength - 1): patternLength; if (idxEnd <= idxStart) { return TRUE; //scenario double star ** } //get a pattern which doesn't contain leading and trailing stars subPattern = VarTo(JavascriptString::SubstringCore(pattern, idxStart, idxEnd - idxStart, scriptContext)); uint index = JavascriptString::strstr(propertyName, subPattern, false); if (index == (uint)-1) { return FALSE; } if (FALSE == bStart) { return index == 0; //begin at the same place; } else if (FALSE == bEnd) { return (index + patternLength - 1) == (nameLength); } return TRUE; } else { //full match if (0 == JavascriptString::strcmp(propertyName, pattern)) { return TRUE; } } return FALSE; } PropertyQueryFlags GlobalObject::HasPropertyQuery(PropertyId propertyId, _Inout_opt_ PropertyValueInfo* info) { return JavascriptConversion::BooleanToPropertyQueryFlags(JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, info)) || (this->directHostObject && JavascriptOperators::HasProperty(this->directHostObject, propertyId)) || (this->hostObject && JavascriptOperators::HasProperty(this->hostObject, propertyId))); } BOOL GlobalObject::HasRootProperty(PropertyId propertyId) { return __super::HasRootProperty(propertyId) || (this->directHostObject && JavascriptOperators::HasProperty(this->directHostObject, propertyId)) || (this->hostObject && JavascriptOperators::HasProperty(this->hostObject, propertyId)); } BOOL GlobalObject::HasOwnProperty(PropertyId propertyId) { return JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, nullptr /*info*/)) || (this->directHostObject && this->directHostObject->HasProperty(propertyId)); } BOOL GlobalObject::HasOwnPropertyNoHostObject(PropertyId propertyId) { return JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, nullptr /*info*/)); } PropertyQueryFlags GlobalObject::GetPropertyQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyQuery(originalInstance, propertyId, value, info, requestContext))) { return PropertyQueryFlags::Property_Found; } return JavascriptConversion::BooleanToPropertyQueryFlags((this->directHostObject && JavascriptOperators::GetProperty(this->directHostObject, propertyId, value, requestContext, info)) || (this->hostObject && JavascriptOperators::GetProperty(this->hostObject, propertyId, value, requestContext, info))); } PropertyQueryFlags GlobalObject::GetPropertyQuery(Var originalInstance, JavascriptString* propertyNameString, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { PropertyRecord const* propertyRecord; this->GetScriptContext()->GetOrAddPropertyRecord(propertyNameString, &propertyRecord); return GlobalObject::GetPropertyQuery(originalInstance, propertyRecord->GetPropertyId(), value, info, requestContext); } BOOL GlobalObject::GetRootProperty(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { if (__super::GetRootProperty(originalInstance, propertyId, value, info, requestContext)) { return TRUE; } return (this->directHostObject && JavascriptOperators::GetProperty(this->directHostObject, propertyId, value, requestContext, info)) || (this->hostObject && JavascriptOperators::GetProperty(this->hostObject, propertyId, value, requestContext, info)); } _Check_return_ _Success_(return) BOOL GlobalObject::GetAccessors(PropertyId propertyId, _Outptr_result_maybenull_ Var* getter, _Outptr_result_maybenull_ Var* setter, ScriptContext* requestContext) { if (DynamicObject::GetAccessors(propertyId, getter, setter, requestContext)) { return TRUE; } if (this->directHostObject) { return this->directHostObject->GetAccessors(propertyId, getter, setter, requestContext); } else if (this->hostObject) { return this->hostObject->GetAccessors(propertyId, getter, setter, requestContext); } return FALSE; } PropertyQueryFlags GlobalObject::GetPropertyReferenceQuery(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetPropertyReferenceQuery(originalInstance, propertyId, value, info, requestContext))) { return PropertyQueryFlags::Property_Found; } return JavascriptConversion::BooleanToPropertyQueryFlags((this->directHostObject && JavascriptOperators::GetPropertyReference(this->directHostObject, propertyId, value, requestContext, info)) || (this->hostObject && JavascriptOperators::GetPropertyReference(this->hostObject, propertyId, value, requestContext, info))); } BOOL GlobalObject::GetRootPropertyReference(Var originalInstance, PropertyId propertyId, Var* value, PropertyValueInfo* info, ScriptContext* requestContext) { if (__super::GetRootPropertyReference(originalInstance, propertyId, value, info, requestContext)) { return true; } return (this->directHostObject && JavascriptOperators::GetPropertyReference(this->directHostObject, propertyId, value, requestContext, info)) || (this->hostObject && JavascriptOperators::GetPropertyReference(this->hostObject, propertyId, value, requestContext, info)); } BOOL GlobalObject::InitProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { // var x = 10; variables declared with "var" at global scope // These cannot be deleted // In ES5 they are enumerable. PropertyAttributes attributes = PropertyWritable | PropertyEnumerable | PropertyDeclaredGlobal; flags = static_cast(flags | PropertyOperation_ThrowIfNotExtensible); return DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, info, flags); } BOOL GlobalObject::InitPropertyInEval(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { // This is var/function declared inside the 'eval' PropertyAttributes attributes = PropertyDynamicTypeDefaults | PropertyDeclaredGlobal; flags = static_cast(flags | PropertyOperation_ThrowIfNotExtensible); return DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, info, flags); } BOOL GlobalObject::InitPropertyScoped(PropertyId propertyId, Var value) { // var x = 10; variables declared with "var" inside "eval" // These CAN be deleted // in ES5 they are enumerable. // PropertyAttributes attributes = PropertyDynamicTypeDefaults | PropertyDeclaredGlobal; return DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, NULL, PropertyOperation_ThrowIfNotExtensible); } BOOL GlobalObject::InitFuncScoped(PropertyId propertyId, Var value) { // Var binding of functions declared in eval are elided when conflicting // with global scope let/const variables, so do not actually set the // property if it exists and is a let/const variable. bool noRedecl = false; if (!GetTypeHandler()->HasRootProperty(this, propertyId, &noRedecl) || !noRedecl) { // // var x = 10; variables declared with "var" inside "eval" // These CAN be deleted // in ES5 they are enumerable. PropertyAttributes attributes = PropertyDynamicTypeDefaults; DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, NULL, PropertyOperation_ThrowIfNotExtensible); } return true; } BOOL GlobalObject::SetExistingProperty(PropertyId propertyId, Var value, PropertyValueInfo* info, BOOL *setAttempted) { BOOL hasOwnProperty = JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasPropertyQuery(propertyId, nullptr /*info*/)); BOOL hasProperty = JavascriptOperators::HasProperty(this->GetPrototype(), propertyId); *setAttempted = TRUE; if (this->directHostObject && !hasOwnProperty && !hasProperty && this->directHostObject->HasProperty(propertyId)) { // directHostObject->HasProperty returns true for things in global collections, however, they are not able to be set. // When we call SetProperty we'll return FALSE which means fall back to GlobalObject::SetProperty and shadow our collection // object rather than failing the set altogether. See bug Windows Out Of Band Releases 1144780 and linked bugs for details. if (this->directHostObject->SetProperty(propertyId, value, PropertyOperation_None, info)) { return TRUE; } } else if (this->hostObject && // Consider to revert to the commented line and ignore the prototype chain when direct host is on by default in IE9 mode !hasOwnProperty && !hasProperty && this->hostObject->HasProperty(propertyId)) { return this->hostObject->SetProperty(propertyId, value, PropertyOperation_None, NULL); } if (hasOwnProperty || hasProperty) { return DynamicObject::SetProperty(propertyId, value, PropertyOperation_None, info); } *setAttempted = FALSE; return FALSE; } BOOL GlobalObject::SetExistingRootProperty(PropertyId propertyId, Var value, PropertyValueInfo* info, BOOL *setAttempted) { BOOL hasOwnProperty = __super::HasRootProperty(propertyId); BOOL hasProperty = JavascriptOperators::HasProperty(this->GetPrototype(), propertyId); *setAttempted = TRUE; if (this->directHostObject && !hasOwnProperty && !hasProperty && this->directHostObject->HasProperty(propertyId)) { // directHostObject->HasProperty returns true for things in global collections, however, they are not able to be set. // When we call SetProperty we'll return FALSE which means fall back to GlobalObject::SetProperty and shadow our collection // object rather than failing the set altogether. See bug Windows Out Of Band Releases 1144780 and linked bugs for details. if (this->directHostObject->SetProperty(propertyId, value, PropertyOperation_None, info)) { return TRUE; } } else if (this->hostObject && // Consider to revert to the commented line and ignore the prototype chain when direct host is on by default in IE9 mode !hasOwnProperty && !hasProperty && this->hostObject->HasProperty(propertyId)) { return this->hostObject->SetProperty(propertyId, value, PropertyOperation_None, NULL); } if (hasOwnProperty || hasProperty) { return __super::SetRootProperty(propertyId, value, PropertyOperation_None, info); } *setAttempted = FALSE; return FALSE; } BOOL GlobalObject::SetProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { Assert(!(flags & PropertyOperation_Root)); BOOL setAttempted = TRUE; if (this->SetExistingProperty(propertyId, value, info, &setAttempted)) { return TRUE; } // // Set was attempted. But the set operation returned false. // This happens, when the property is read only. // In those scenarios, we should be setting the property with default attributes // if (setAttempted) { return FALSE; } // Windows 8 430092: When we set a new property on globalObject there may be stale inline caches holding onto directHostObject->prototype // properties of the same name. So we need to clear proto caches in this scenario. But check for same property in directHostObject->prototype // chain is expensive (call to DOM) compared to just invalidating the cache. // If this blind invalidation is expensive in any scenario then we need to revisit this. // Another solution proposed was not to cache any of the properties of window->directHostObject->prototype. // if ((this->directHostObject && JavascriptOperators::HasProperty(this->directHostObject->GetPrototype(), propertyId)) || // (this->hostObject && JavascriptOperators::HasProperty(this->hostObject->GetPrototype(), propertyId))) this->GetScriptContext()->InvalidateProtoCaches(propertyId); // // x = 10; implicit/phantom variable at global scope // These CAN be deleted // In ES5 they are enumerable. PropertyAttributes attributes = PropertyDynamicTypeDefaults; return DynamicObject::SetPropertyWithAttributes(propertyId, value, attributes, info); } BOOL GlobalObject::SetProperty(JavascriptString* propertyNameString, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { PropertyRecord const * propertyRecord; this->GetScriptContext()->GetOrAddPropertyRecord(propertyNameString, &propertyRecord); return GlobalObject::SetProperty(propertyRecord->GetPropertyId(), value, flags, info); } BOOL GlobalObject::SetRootProperty(PropertyId propertyId, Var value, PropertyOperationFlags flags, PropertyValueInfo* info) { Assert(flags & PropertyOperation_Root); BOOL setAttempted = TRUE; if (this->SetExistingRootProperty(propertyId, value, info, &setAttempted)) { return TRUE; } // // Set was attempted. But the set operation returned false. // This happens, when the property is read only. // In those scenarios, we should be setting the property with default attributes // if (setAttempted) { return FALSE; } if (flags & PropertyOperation_StrictMode) { if (!GetScriptContext()->GetThreadContext()->RecordImplicitException()) { return FALSE; } JavascriptError::ThrowReferenceError(GetScriptContext(), JSERR_RefErrorUndefVariable); } // Windows 8 430092: When we set a new property on globalObject there may be stale inline caches holding onto directHostObject->prototype // properties of the same name. So we need to clear proto caches in this scenario. But check for same property in directHostObject->prototype // chain is expensive (call to DOM) compared to just invalidating the cache. // If this blind invalidation is expensive in any scenario then we need to revisit this. // Another solution proposed was not to cache any of the properties of window->directHostObject->prototype. // if ((this->directHostObject && JavascriptOperators::HasProperty(this->directHostObject->GetPrototype(), propertyId)) || // (this->hostObject && JavascriptOperators::HasProperty(this->hostObject->GetPrototype(), propertyId))) this->GetScriptContext()->InvalidateProtoCaches(propertyId); return __super::SetRootProperty(propertyId, value, flags, info); } BOOL GlobalObject::SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags) { if (DynamicObject::SetAccessors(propertyId, getter, setter, flags)) { return TRUE; } if (this->directHostObject) { return this->directHostObject->SetAccessors(propertyId, getter, setter, flags); } if (this->hostObject) { return this->hostObject->SetAccessors(propertyId, getter, setter, flags); } return TRUE; } DescriptorFlags GlobalObject::GetSetter(PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) { DescriptorFlags flags = DynamicObject::GetSetter(propertyId, setterValue, info, requestContext); if (flags == None) { if (this->directHostObject) { // We need to look up the prototype chain here. JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(this->directHostObject, propertyId, setterValue, &flags, info, requestContext); } else if (this->hostObject) { JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(this->hostObject, propertyId, setterValue, &flags, info, requestContext); } } return flags; } DescriptorFlags GlobalObject::GetSetter(JavascriptString* propertyNameString, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) { PropertyRecord const* propertyRecord; this->GetScriptContext()->GetOrAddPropertyRecord(propertyNameString, &propertyRecord); return GlobalObject::GetSetter(propertyRecord->GetPropertyId(), setterValue, info, requestContext); } DescriptorFlags GlobalObject::GetRootSetter(PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) { DescriptorFlags flags = __super::GetRootSetter(propertyId, setterValue, info, requestContext); if (flags == None) { if (this->directHostObject) { // We need to look up the prototype chain here. JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(this->directHostObject, propertyId, setterValue, &flags, info, requestContext); } else if (this->hostObject) { JavascriptOperators::CheckPrototypesForAccessorOrNonWritableProperty(this->hostObject, propertyId, setterValue, &flags, info, requestContext); } } return flags; } DescriptorFlags GlobalObject::GetItemSetter(uint32 index, Var* setterValue, ScriptContext* requestContext) { DescriptorFlags flags = DynamicObject::GetItemSetter(index, setterValue, requestContext); if (flags == None) { if (this->directHostObject) { // We need to look up the prototype chain here. JavascriptOperators::CheckPrototypesForAccessorOrNonWritableItem(this->directHostObject, index, setterValue, &flags, requestContext); } else if (this->hostObject) { JavascriptOperators::CheckPrototypesForAccessorOrNonWritableItem(this->hostObject, index, setterValue, &flags, requestContext); } } return flags; } BOOL GlobalObject::DeleteProperty(PropertyId propertyId, PropertyOperationFlags flags) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(__super::HasPropertyQuery(propertyId, nullptr /*info*/))) { return __super::DeleteProperty(propertyId, flags); } else if (this->directHostObject && this->directHostObject->HasProperty(propertyId)) { return this->directHostObject->DeleteProperty(propertyId, flags); } else if (this->hostObject && this->hostObject->HasProperty(propertyId)) { return this->hostObject->DeleteProperty(propertyId, flags); } // Non-existent property return TRUE; } BOOL GlobalObject::DeleteProperty(JavascriptString *propertyNameString, PropertyOperationFlags flags) { PropertyRecord const *propertyRecord = nullptr; if (JavascriptOperators::ShouldTryDeleteProperty(this, propertyNameString, &propertyRecord)) { Assert(propertyRecord); return DeleteProperty(propertyRecord->GetPropertyId(), flags); } return TRUE; } BOOL GlobalObject::DeleteRootProperty(PropertyId propertyId, PropertyOperationFlags flags) { if (__super::HasRootProperty(propertyId)) { return __super::DeleteRootProperty(propertyId, flags); } else if (this->directHostObject && this->directHostObject->HasProperty(propertyId)) { return this->directHostObject->DeleteProperty(propertyId, flags); } else if (this->hostObject && this->hostObject->HasProperty(propertyId)) { return this->hostObject->DeleteProperty(propertyId, flags); } // Non-existent property return TRUE; } PropertyQueryFlags GlobalObject::HasItemQuery(uint32 index) { return JavascriptConversion::BooleanToPropertyQueryFlags(JavascriptConversion::PropertyQueryFlagsToBoolean((DynamicObject::HasItemQuery(index))) || (this->directHostObject && JavascriptOperators::HasItem(this->directHostObject, index)) || (this->hostObject && JavascriptOperators::HasItem(this->hostObject, index))); } BOOL GlobalObject::HasOwnItem(uint32 index) { return JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::HasItemQuery(index)) || (this->directHostObject && this->directHostObject->HasItem(index)); } PropertyQueryFlags GlobalObject::GetItemReferenceQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetItemReferenceQuery(originalInstance, index, value, requestContext))) { return PropertyQueryFlags::Property_Found; } return JavascriptConversion::BooleanToPropertyQueryFlags( (this->directHostObject && JavascriptConversion::PropertyQueryFlagsToBoolean(this->directHostObject->GetItemReferenceQuery(originalInstance, index, value, requestContext))) || (this->hostObject && JavascriptConversion::PropertyQueryFlagsToBoolean(this->hostObject->GetItemReferenceQuery(originalInstance, index, value, requestContext)))); } BOOL GlobalObject::SetItem(uint32 index, Var value, PropertyOperationFlags flags) { BOOL result = DynamicObject::SetItem(index, value, flags); return result; } PropertyQueryFlags GlobalObject::GetItemQuery(Var originalInstance, uint32 index, Var* value, ScriptContext* requestContext) { if (JavascriptConversion::PropertyQueryFlagsToBoolean(DynamicObject::GetItemQuery(originalInstance, index, value, requestContext))) { return PropertyQueryFlags::Property_Found; } return JavascriptConversion::BooleanToPropertyQueryFlags((this->directHostObject && this->directHostObject->GetItem(originalInstance, index, value, requestContext)) || (this->hostObject && this->hostObject->GetItem(originalInstance, index, value, requestContext))); } BOOL GlobalObject::DeleteItem(uint32 index, PropertyOperationFlags flags) { if (DynamicObject::DeleteItem(index, flags)) { return TRUE; } if (this->directHostObject) { return this->directHostObject->DeleteItem(index, flags); } if (this->hostObject) { return this->hostObject->DeleteItem(index, flags); } return FALSE; } BOOL GlobalObject::GetDiagValueString(StringBuilder* stringBuilder, ScriptContext* requestContext) { stringBuilder->AppendCppLiteral(_u("{...}")); return TRUE; } BOOL GlobalObject::GetDiagTypeString(StringBuilder* stringBuilder, ScriptContext* requestContext) { stringBuilder->AppendCppLiteral(_u("Object, (Global)")); return TRUE; } BOOL GlobalObject::StrictEquals(__in Js::Var other, __out BOOL* value, ScriptContext * requestContext) { if (this == other) { *value = TRUE; return TRUE; } else if (this->directHostObject) { return this->directHostObject->StrictEquals(other, value, requestContext); } else if (this->hostObject) { return this->hostObject->StrictEquals(other, value, requestContext); } *value = FALSE; return FALSE; } BOOL GlobalObject::Equals(__in Js::Var other, __out BOOL* value, ScriptContext * requestContext) { if (this == other) { *value = TRUE; return TRUE; } else if (this->directHostObject) { return this->directHostObject->Equals(other, value, requestContext); } else if (this->hostObject) { return this->hostObject->Equals(other, value, requestContext); } *value = FALSE; return TRUE; } #if ENABLE_TTD TTD::NSSnapObjects::SnapObjectType GlobalObject::GetSnapTag_TTD() const { return TTD::NSSnapObjects::SnapObjectType::SnapWellKnownObject; } void GlobalObject::ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) { // //TODO: we assume that the global object is always set right (e.g., ignore the directHostObject and secureDirectHostObject values) we need to verify that this is ok and/or what to do about it // TTD::NSSnapObjects::StdExtractSetKindSpecificInfo(objData, nullptr); } #endif