Skip to content

Commit 771d486

Browse files
committed
[MERGE #5698 @jackhorton] Share more code/ideas between JsBuiltIns and Intl
Merge pull request #5698 from jackhorton:jsbuiltins/cleanup Fixes #5644 Fixes #5643 Fixes an unlogged issue where all JsBuiltIns were constructable by default. This mainly merged a lot of the code between tagPublicLibraryFunction, registerFunction, and registerChakraLibraryFunction. However, there are a number of smaller updates too: 1. Stopped creating a new ScriptFunction on each JsBuiltIn registration. As far as I can tell, the only reason this existed before was to get a ScriptFunction without any framedisplay/environment, but since that can be set manually, its cheaper to just set it on the existing ScriptFunction. 1. Intl functions no longer have to duplicate their name in the tagPublicLibraryCode argument and in script. I have edited a few Intl functions to confirm it works -- not sure if its worth it to go through and make all of the functions anonymous now. I also wanted to investigate changing JavascriptLibrary::InitializeFunction to not set the name attribute for these anonymous jsbuiltin functions since the name will always be overridden, but I am not sure if its possible. 1. function length is now set using default parameters rather than manually/after the fact. Not sure if I like this better or worse than having the function macro list the length and have it set it explicitly. We could theoretically do the same optimization in InitializeFunction to avoid the extra property set. 1. JsBuiltIns now uses a shared/projected enum for function registration like Intl. 1. Ran into an error when using default parameters for functions marked explicitly as "use strict," and I found the error message misleading, so I changed it to be the same as V8's which I found clearer. 1. CheckArrayAndGetLen never hit its error case and reported a slightly worse-looking runtime/accidental error as a result when this == null Still need to run this through test262 to make sure there are no regressions.
2 parents be2b997 + 0accefd commit 771d486

24 files changed

+25847
-26357
lines changed

lib/Parser/perrors.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ LSC_ERROR_MSG( 1077, ERRDestructNotInit, "Destructuring declarations cannot have
8888
LSC_ERROR_MSG(1079, ERRInvalidNewTarget, "Invalid use of the 'new.target' keyword")
8989
LSC_ERROR_MSG(1080, ERRForInNoInitAllowed, "for-in loop head declarations cannot have an initializer")
9090
LSC_ERROR_MSG(1081, ERRForOfNoInitAllowed, "for-of loop head declarations cannot have an initializer")
91-
LSC_ERROR_MSG(1082, ERRNonSimpleParamListInStrictMode, "Cannot apply strict mode on functions with non-simple parameter list")
91+
LSC_ERROR_MSG(1082, ERRNonSimpleParamListInStrictMode, "Illegal 'use strict' directive in function with non-simple parameter list")
9292

9393
LSC_ERROR_MSG(1083, ERRBadAwait, "'await' expression not allowed in this context")
9494

lib/Runtime/Base/JnDirectFields.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,16 @@ ENTRY(toLength)
645645
ENTRY(toInteger)
646646
ENTRY(arraySpeciesCreate)
647647
ENTRY(arrayCreateDataPropertyOrThrow)
648+
ENTRY(Array_values)
649+
ENTRY(Array_keys)
650+
ENTRY(Array_entries)
651+
ENTRY(Array_indexOf)
652+
ENTRY(Array_filter)
653+
ENTRY(Array_flat)
654+
ENTRY(Array_flatMap)
655+
ENTRY(Array_forEach)
656+
ENTRY(Object_fromEntries)
657+
ENTRY(FunctionKind)
648658

649659
// EngineInterfaceObject built-ins
650660
ENTRY(builtInJavascriptArrayEntryFilter)

lib/Runtime/ByteCode/ByteCodeCacheReleaseFileVersion.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@
44
//-------------------------------------------------------------------------------------------------------
55
// NOTE: If there is a merge conflict the correct fix is to make a new GUID.
66

7-
// {A7B3E50E-9870-4A74-A155-58021B9B2202}
7+
// {BFA0BED0-7B1A-4018-83DA-B1D989D38BFD}
88
const GUID byteCodeCacheReleaseFileVersion =
9-
{ 0xA7B3E50E, 0x9870, 0x4A74, { 0xA1, 0x55, 0x58, 0x02, 0x1B, 0x9B, 0x22, 0x02 } };
9+
{ 0xBFA0BED0, 0x7B1A, 0x4018, { 0x83, 0xDA, 0xB1, 0xD9, 0x89, 0xD3, 0x8B, 0xFD } };

lib/Runtime/Library/EngineInterfaceObject.cpp

Lines changed: 104 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -267,47 +267,125 @@ namespace Js
267267
return scriptContext->GetLibrary()->GetUndefined();
268268
}
269269

270-
Var EngineInterfaceObject::Entry_TagPublicLibraryCode(RecyclableObject *function, CallInfo callInfo, ...)
270+
/* static */
271+
ScriptFunction *EngineInterfaceObject::CreateLibraryCodeScriptFunction(ScriptFunction *scriptFunction, JavascriptString *displayName, bool isConstructor, bool isJsBuiltIn, bool isPublic)
271272
{
272-
EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
273+
if (scriptFunction->GetFunctionProxy()->IsPublicLibraryCode())
274+
{
275+
// this can happen when we re-initialize Intl for a different mode -- for instance, if we have the following JS:
276+
// print((1).toLocaleString())
277+
// print(new Intl.NumberFormat().format(1))
278+
// Intl will first get initialized for Number, and then will get re-initialized for all of Intl. This will cause
279+
// Number.prototype.toLocaleString to be registered twice, which breaks some of our assertions below.
280+
return scriptFunction;
281+
}
273282

274-
AssertOrFailFast((callInfo.Count == 3 || callInfo.Count == 4) && JavascriptFunction::Is(args[1]) && JavascriptString::Is(args[2]));
283+
ScriptContext *scriptContext = scriptFunction->GetScriptContext();
275284

276-
JavascriptFunction *func = JavascriptFunction::UnsafeFromVar(args[1]);
277-
JavascriptString *methodName = JavascriptString::UnsafeFromVar(args[2]);
285+
if (!isConstructor)
286+
{
287+
// set the ErrorOnNew attribute to disallow construction. JsBuiltIn/Intl functions are usually regular ScriptFunctions
288+
// (not lambdas or class methods), so they are usually constructable by default.
289+
FunctionInfo *info = scriptFunction->GetFunctionInfo();
290+
AssertMsg((info->GetAttributes() & FunctionInfo::Attributes::ErrorOnNew) == 0, "Why are we trying to disable construction of a function that already isn't constructable?");
291+
info->SetAttributes((FunctionInfo::Attributes) (info->GetAttributes() | FunctionInfo::Attributes::ErrorOnNew));
278292

279-
func->GetFunctionProxy()->SetIsPublicLibraryCode();
293+
// Assert that the type handler is deferred to ensure that we aren't overwriting previous modifications.
294+
// Script functions start with deferred type handlers, which undefer as soon as any property is modified.
295+
// Since the function that is passed in should be an inline function expression, its type should still be deferred by the time it gets here.
296+
AssertOrFailFast(scriptFunction->GetDynamicType()->GetTypeHandler()->IsDeferredTypeHandler());
280297

281-
// use GetSz rather than GetString because we use wcsrchr below, which expects a null-terminated string
282-
const char16 *methodNameBuf = methodName->GetSz();
283-
charcount_t methodNameLength = methodName->GetLength();
284-
const char16 *shortName = wcsrchr(methodNameBuf, _u('.'));
285-
charcount_t shortNameOffset = 0;
286-
if (shortName != nullptr)
298+
// give the function a type handler with name and length but without prototype
299+
DynamicTypeHandler::SetInstanceTypeHandler(scriptFunction, scriptContext->GetLibrary()->GetDeferredFunctionWithLengthTypeHandler());
300+
}
301+
else
287302
{
288-
shortName++;
289-
shortNameOffset = static_cast<charcount_t>(shortName - methodNameBuf);
303+
AssertMsg((scriptFunction->GetFunctionInfo()->GetAttributes() & FunctionInfo::Attributes::ErrorOnNew) == 0, "Why is the function not constructable by default?");
290304
}
291305

292-
func->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodNameBuf, methodNameLength, shortNameOffset);
293-
294-
bool creatingConstructor = true;
295-
if (callInfo.Count == 4)
306+
if (isPublic)
296307
{
297-
AssertOrFailFast(JavascriptBoolean::Is(args[3]));
298-
creatingConstructor = JavascriptBoolean::UnsafeFromVar(args[3])->GetValue();
308+
// Use GetSz rather than GetString because we use wcsrchr below, which expects a null-terminated string
309+
// Callers can pass in a string like "get compare" or "Intl.Collator.prototype.resolvedOptions" -- only for the
310+
// latter do we extract a shortName.
311+
const char16 *methodNameBuf = displayName->GetSz();
312+
charcount_t methodNameLength = displayName->GetLength();
313+
const char16 *shortName = wcsrchr(methodNameBuf, _u('.'));
314+
charcount_t shortNameOffset = 0;
315+
if (shortName != nullptr)
316+
{
317+
shortName++;
318+
shortNameOffset = static_cast<charcount_t>(shortName - methodNameBuf);
319+
}
320+
321+
scriptFunction->GetFunctionProxy()->EnsureDeserialized()->SetDisplayName(methodNameBuf, methodNameLength, shortNameOffset);
322+
323+
// handle the name property AFTER handling isConstructor, because this can initialize the function's deferred type
324+
Var existingName = nullptr;
325+
if (JavascriptOperators::GetOwnProperty(scriptFunction, PropertyIds::name, &existingName, scriptContext, nullptr))
326+
{
327+
JavascriptString *existingNameString = JavascriptString::FromVar(existingName);
328+
if (existingNameString->GetLength() == 0)
329+
{
330+
// Only overwrite the name of the function object if it was anonymous coming in
331+
// If the input function was named, it is likely intentional
332+
existingName = nullptr;
333+
}
334+
}
335+
336+
if (existingName == nullptr || JavascriptOperators::IsUndefined(existingName))
337+
{
338+
// It is convenient to set the name here rather than in script, since it is often duplicated.
339+
JavascriptString *funcName = displayName;
340+
if (shortName)
341+
{
342+
funcName = JavascriptString::NewCopyBuffer(shortName, methodNameLength - shortNameOffset, scriptContext);
343+
}
344+
345+
scriptFunction->SetPropertyWithAttributes(PropertyIds::name, funcName, PropertyConfigurable, nullptr);
346+
}
347+
348+
scriptFunction->GetFunctionProxy()->SetIsPublicLibraryCode();
299349
}
300350

301-
if (!creatingConstructor)
351+
if (isJsBuiltIn)
302352
{
303-
FunctionInfo *info = func->GetFunctionInfo();
304-
info->SetAttributes((FunctionInfo::Attributes) (info->GetAttributes() | FunctionInfo::Attributes::ErrorOnNew));
353+
scriptFunction->GetFunctionProxy()->SetIsJsBuiltInCode();
354+
355+
// This makes it so that the given scriptFunction can't reference/close over any outside variables,
356+
// which is desirable for JsBuiltIns (though currently not for Intl)
357+
scriptFunction->SetEnvironment(const_cast<FrameDisplay *>(&StrictNullFrameDisplay));
358+
359+
// TODO(jahorto): investigate force-inlining Intl code
360+
scriptFunction->GetFunctionProxy()->EnsureDeserialized();
361+
AssertOrFailFast(scriptFunction->HasFunctionBody());
362+
scriptFunction->GetFunctionBody()->SetJsBuiltInForceInline();
363+
}
364+
365+
return scriptFunction;
366+
}
305367

306-
AssertOrFailFast(func->GetDynamicType()->GetTypeHandler()->IsDeferredTypeHandler());
307-
DynamicTypeHandler::SetInstanceTypeHandler(func, scriptContext->GetLibrary()->GetDeferredFunctionWithLengthTypeHandler());
368+
Var EngineInterfaceObject::Entry_TagPublicLibraryCode(RecyclableObject *function, CallInfo callInfo, ...)
369+
{
370+
#pragma warning(push)
371+
#pragma warning(disable: 4189) // 'scriptContext': local variable is initialized but not referenced
372+
EngineInterfaceObject_CommonFunctionProlog(function, callInfo);
373+
#pragma warning(pop)
374+
375+
AssertOrFailFast((args.Info.Count == 3 || args.Info.Count == 4) && ScriptFunction::Is(args.Values[1]) && JavascriptString::Is(args.Values[2]));
376+
377+
ScriptFunction *func = ScriptFunction::UnsafeFromVar(args[1]);
378+
JavascriptString *methodName = JavascriptString::UnsafeFromVar(args[2]);
379+
380+
bool isConstructor = true;
381+
if (args.Info.Count == 4)
382+
{
383+
AssertOrFailFast(JavascriptBoolean::Is(args.Values[3]));
384+
isConstructor = JavascriptBoolean::UnsafeFromVar(args.Values[3])->GetValue();
308385
}
309386

310-
return func;
387+
// isConstructor = true is the default (when no 3rd arg is provided)
388+
return CreateLibraryCodeScriptFunction(func, methodName, isConstructor, false /* isJsBuiltIn */, true /* isPublic */);
311389
}
312390

313391
/*

lib/Runtime/Library/EngineInterfaceObject.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ namespace Js
8888

8989
static bool __cdecl InitializeCommonNativeInterfaces(DynamicObject* engineInterface, DeferredTypeHandlerBase * typeHandler, DeferredInitializeMode mode);
9090

91+
static ScriptFunction *CreateLibraryCodeScriptFunction(ScriptFunction *scriptFunction, JavascriptString *displayName, bool isConstructor, bool isJsBuiltIn, bool isPublic);
92+
9193
class EntryInfo
9294
{
9395
public:

lib/Runtime/Library/EngineInterfaceObjectBuiltIns.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ GlobalBuiltInConstructor(Number)
5050
GlobalBuiltInConstructor(RegExp)
5151
GlobalBuiltInConstructor(String)
5252
GlobalBuiltInConstructor(Date)
53-
GlobalBuiltInConstructor(Error) // TODO(jahorto): consider deleting (all errors should go through builtInRaise*)
53+
GlobalBuiltInConstructor(Error) // TODO(jahorto): consider deleting (currently used by WinRT Promises)
5454
GlobalBuiltInConstructor(Map) // TODO(jahorto): consider deleting (when do we need a Map over an object?)
5555
GlobalBuiltInConstructor(Symbol)
5656

@@ -66,6 +66,8 @@ GlobalBuiltIn(JavascriptObject, GetOwnPropertyNames)
6666
GlobalBuiltIn(JavascriptObject, HasOwnProperty)
6767
GlobalBuiltIn(JavascriptObject, Keys)
6868
GlobalBuiltIn(JavascriptObject, Create)
69+
GlobalBuiltIn(JavascriptObject, GetOwnPropertyDescriptor)
70+
GlobalBuiltIn(JavascriptObject, PreventExtensions)
6971

7072
GlobalBuiltIn(JavascriptArray, Push) // TODO(jahorto): consider deleting (trivially implementable in JS)
7173
GlobalBuiltIn(JavascriptArray, Join)
@@ -90,7 +92,7 @@ GlobalBuiltIn(JavascriptString, IndexOf)
9092

9193
GlobalBuiltIn(GlobalObject, IsFinite) // TODO(jahorto): consider switching to Number.isFinite
9294
GlobalBuiltIn(GlobalObject, IsNaN) // TODO(jahorto): consider switching to Number.isNaN
93-
GlobalBuiltIn(GlobalObject, Eval) // TODO(jahorto): consider deleting (should any builtins be using eval()?)
95+
GlobalBuiltIn(GlobalObject, Eval) // TODO(jahorto): consider deleting (currently used by WinRT Promises)
9496

9597
BuiltInRaiseException(TypeError, NeedObject)
9698
BuiltInRaiseException2(TypeError, ObjectIsAlreadyInitialized)

lib/Runtime/Library/InJavascript/Intl.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,7 @@
931931

932932
const CollatorPrototype = {};
933933

934-
const Collator = tagPublicFunction("Intl.Collator", function Collator(locales = undefined, options = undefined) {
934+
const Collator = tagPublicFunction("Intl.Collator", function (locales = undefined, options = undefined) {
935935
const newTarget = new.target === undefined ? Collator : new.target;
936936
const collator = OrdinaryCreateFromConstructor(newTarget, CollatorPrototype);
937937

@@ -1026,12 +1026,6 @@
10261026

10271027
return hiddenObject.boundCompare;
10281028
});
1029-
_.defineProperty(getCompare, "name", {
1030-
value: "get compare",
1031-
writable: false,
1032-
enumerable: false,
1033-
configurable: true,
1034-
});
10351029
_.defineProperty(CollatorPrototype, "compare", {
10361030
get: getCompare,
10371031
enumerable: false,

0 commit comments

Comments
 (0)