Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Jint.Tests/Runtime/FunctionNameForMessageTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using Jint.Native.Function;

namespace Jint.Tests.Runtime;

/// <summary>
/// <see cref="Function.GetOwnFunctionNameForMessage"/> exists to render a name inside an error message
/// without running script — so it must never throw itself. A script function's own <c>name</c> starts as
/// the shared pending-lazy sentinel, whose value accessors throw by design to make a leak loud; reading
/// the descriptor's <c>Value</c> before anything materialized the property is exactly such a leak.
/// </summary>
public class FunctionNameForMessageTests
{
[Fact]
public void APendingLazyNameFallsBackToTheClrTypeNameInsteadOfThrowing()
{
using var engine = new Engine();

// The function's own "name" property exists from birth, but its descriptor is the pending
// sentinel until something reads the property — which nothing here does.
var function = (Function) engine.Evaluate("(function foo() {})");

var name = function.GetOwnFunctionNameForMessage();

Assert.Equal(function.GetType().Name, name);
}

[Fact]
public void AMaterializedNameIsQuoted()
{
using var engine = new Engine();

var function = (Function) engine.Evaluate("const f = function foo() {}; void f.name; f");

Assert.Equal("foo", function.GetOwnFunctionNameForMessage());
}
}
15 changes: 12 additions & 3 deletions Jint/Native/Function/Function.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,13 +663,22 @@ internal string GetOwnFunctionName()
/// <c>toString</c> throws. <see cref="GetOwnFunctionName"/> coerces through <c>TypeConverter.ToString</c>
/// and would let that object hijack the error being built — an extra observable call, and a thrown
/// value replacing the <c>TypeError</c> the caller meant to raise. So only an actual string is quoted
/// here; anything else falls back to the CLR type's name, which no script can influence. Reading
/// <see cref="PropertyDescriptor.Value"/> touches the raw field and never invokes an accessor.
/// here; anything else falls back to the CLR type's name, which no script can influence. A descriptor
/// carrying <see cref="PropertyFlag.CustomJsValue"/> is skipped for the same reason: its
/// <see cref="PropertyDescriptor.Value"/> routes through the <c>CustomValue</c> accessor rather than the
/// raw field, and one of those accessors — the shared pending-lazy sentinel a script function's name
/// starts with — throws by design. An error-message path must never introduce a throw of its own.
/// </para>
/// </summary>
internal string GetOwnFunctionNameForMessage()
{
return _nameDescriptor?.Value is JsString name ? name.ToString() : GetType().Name;
var descriptor = _nameDescriptor;
if (descriptor is null || (descriptor.Flags & PropertyFlag.CustomJsValue) != PropertyFlag.None)
{
return GetType().Name;
}

return descriptor.Value is JsString name ? name.ToString() : GetType().Name;
}

/// <summary>
Expand Down