D issues are now tracked on GitHub. This Bugzilla instance remains as a read-only archive.
Issue 14162 - Erratic inference of @safe for lambdas
Summary: Erratic inference of @safe for lambdas
Status: RESOLVED FIXED
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P1 normal
Assignee: No Owner
URL:
Keywords: pull, safe
Depends on:
Blocks:
 
Reported: 2015-02-10 07:17 UTC by Walter Bright
Modified: 2016-10-01 11:47 UTC (History)
0 users

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Walter Bright 2015-02-10 07:17:38 UTC
Consider the code:
---------
@trusted auto trusted(alias fun)() { return fun(); }

@safe void func()
{
    char[3] s = "abc";
    string t = trusted!(() => cast(string)(s[]));
//test.d(6): Error: cast from char[] to string not allowed in safe code
    assert(t == "abc");
}

@safe void test() { func(); }
----------
The error is correct because the lambda is evaluated in the context of @safe func(), not in the context of @trusted trusted(). But turn func() into a template function:
---------
@trusted auto trusted(alias fun)() { return fun(); }

@safe void func()() // only change is add () to make it a template
{
    char[3] s = "abc";
    string t = trusted!(() => cast(string)(s[]));
    assert(t == "abc");
}

@safe void test() { func(); }
----------
And it now incorrectly compiles without error.
Comment 1 Kenji Hara 2015-02-10 12:03:19 UTC
I also noticed this difference recently. But I think the safe violation error in the first case is incorrect.

(In reply to Walter Bright from comment #0)
> Consider the code:
> ---------
> @trusted auto trusted(alias fun)() { return fun(); }
> 
> @safe void func()
> {
>     char[3] s = "abc";
>     string t = trusted!(() => cast(string)(s[]));
> //test.d(6): Error: cast from char[] to string not allowed in safe code
>     assert(t == "abc");
> }
> 
> @safe void test() { func(); }
> ----------
> The error is correct because the lambda is evaluated in the context of @safe
> func(), not in the context of @trusted trusted(). But turn func() into a
> template function:

Normally a nested function inherits @safe attribute from the enclosing function. But lambda function attribute should inferred from the body statement. Currently the inherited @safe is preferred, but as I'll explain later, it's not good behavior.

> ---------
> @trusted auto trusted(alias fun)() { return fun(); }
> 
> @safe void func()() // only change is add () to make it a template
> {
>     char[3] s = "abc";
>     string t = trusted!(() => cast(string)(s[]));
>     assert(t == "abc");
> }
> 
> @safe void test() { func(); }
> ----------
> And it now incorrectly compiles without error.

Inside template function, the attribute inference result is priority than the inherited @safe on the lambda. Then the lambda is marked as @system.

========

The latter case is at least intended behavior. See FuncDeclaration::semantic() in func.c:

    if (sc->func)
    {
        /* If the parent is @safe, then this function defaults to safe too.
         */
        if (tf->trust == TRUSTdefault)
        {
            FuncDeclaration *fd = sc->func;

            /* If the parent's @safe-ty is inferred, then this function's @safe-ty needs
             * to be inferred first.
             * If this function's @safe-ty is inferred, then it needs to be infeerd first.
             * (local template function inside @safe function can be inferred to @system).
             */
            if (fd->isSafeBypassingInference() && !isInstantiated())
                tf->trust = TRUSTsafe;              // default to @safe
        }

The behavior is necessary to support a lambda idiom. For example:

struct S { this(this) {} }
import std.traits: isSafe;
void foo(T)() @safe
{
    static if (isSafe!((ref T t){ T t2 = t; }))
    {
        pragma(msg, true);
    }
    else
    {
        pragma(msg, false);
    }
}
void main() { foo!S(); }

Lambda is used to check whether the T's copy operation is really safe. If the lambda inherits @safe attribute from the enclosing foo, unsafe T copy will cause safe violation error so the check won't work.

And more, D allows to declare @system function inside @safe function.

void foo() @safe
{
    static void bar() @system
    {
    }
}

From the fact, even if a lambda inside @safe function is deduced to @system, it won't cause safety violation. Only when the lambda is actually called in the @safe function, it should be an error.

void foo() @safe
{
    auto dg = { return systemCall() };   // should be OK
    auto ret = { return systemCall() }();  // system cannot call in safe function
}

As a conclusion, I think the former case should be fixed, and the behavior should be same with the latter.
Comment 2 Walter Bright 2016-06-23 22:20:19 UTC
Inference of safety for lambdas should always occur if not explicitly set, because the source is always available.
Comment 3 Walter Bright 2016-06-23 22:20:43 UTC
https://github.com/dlang/dmd/pull/5881
Comment 4 github-bugzilla 2016-06-28 12:54:28 UTC
Commits pushed to master at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/366378a5b606ee2093eb6625101e88573a7b2960
fix Issue 14162 - Erratic inference of @safe for lambdas

https://github.com/dlang/dmd/commit/11165915a80835d0098f719fe0b4629b4837e583
Merge pull request #5881 from WalterBright/fix14162

fix Issue 14162 - Erratic inference of @safe for lambdas
Comment 5 github-bugzilla 2016-10-01 11:47:58 UTC
Commits pushed to stable at https://github.com/dlang/dmd

https://github.com/dlang/dmd/commit/366378a5b606ee2093eb6625101e88573a7b2960
fix Issue 14162 - Erratic inference of @safe for lambdas

https://github.com/dlang/dmd/commit/11165915a80835d0098f719fe0b4629b4837e583
Merge pull request #5881 from WalterBright/fix14162