Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix Issue 18554 - tupleof ignoring private shouldn't be accepted in @… #8035

Merged
merged 1 commit into from
Mar 15, 2018

Conversation

WalterBright
Copy link
Member

…safe code

@dlang-bot
Copy link
Contributor

Thanks for your pull request, @WalterBright!

Bugzilla references

Auto-close Bugzilla Severity Description
18554 normal tupleof ignoring private shouldn't be accepted in @safe code

Copy link
Member

@wilzbach wilzbach left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! Yet another hole fixed :)

@@ -112,11 +110,16 @@ enum SCOPE
compile = 0x0100, /// inside __traits(compile)
ignoresymbolvisibility = 0x0200, /// ignore symbol visibility
/// https://issues.dlang.org/show_bug.cgi?id=15907
onlysafeaccess = 0x0400, /// unsafe access is not allowed for @safe code
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Shouldn't this be named onlyunsafeaccess, onlysystemaccess or nosafeaccess as it prevents @safe access but allows unsafe access?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's going to bite me later, I fear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dlang-bot dlang-bot merged commit cad484b into dlang:master Mar 15, 2018
@rainers
Copy link
Member

rainers commented Mar 15, 2018

Is this a good idea? It broadens the meaning of @safe beyond memory safety. This might open a can of worms for other code considered unsafe.
I would also expect an update to the documentation and tests that show that usual access is still ok.

@JinShil
Copy link
Contributor

JinShil commented Mar 15, 2018

@WalterBright Please consider leaving PRs open for at least 24 hours to allow time for everyone to have their say.

@timotheecour
Copy link
Contributor

one example use case of tupleof is for serialization/deserialization routines that must rely on private member access; there's nothing inherently unsafe about these (in terms of memory safety).
this PR would prevent such use cases in safe code

@@ -3139,7 +3139,7 @@ private extern(C++) final class DotExpVisitor : Visitor

e = new TupleExp(e.loc, e0, exps);
Scope* sc2 = sc.push();
sc2.flags = sc.flags | SCOPE.noaccesscheck;
sc2.flags |= global.params.vsafe ? SCOPE.onlysafeaccess : SCOPE.noaccesscheck;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this should be tied to DIP 1000 (global.params.vsafe). DIP 1000 is about scope. But this has nothing to do with scope.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other precendents breaking changes were introduced with -dip1000 as it's effectively unused, e.g. #8036 (there were more in the last weeks, but I can't find them on the first try).
I guess eventually dip1000 might evolve in a sublanguage :/

@jacob-carlborg
Copy link
Contributor

Yet another regression without thinking of the consequences 😞. Come on people, can we please do better than this!

@wilzbach
Copy link
Member

Yet another regression without thinking of the consequences disappointed.

It's not a regression (per se), because it's only active in the transitional -dip1000 flag which no one uses at the moment. For -dip1000 breaking changes are expected and can be done.

Regarding the motivation - check Bugzilla:

Copy/Paste


But tupleof ignores private even in @safe code. So it can be used to violate the assumption, which leads to memory corruption.

The File example in code (https://run.dlang.io/is/1QSsUk):

void main() @safe
{
    import std.stdio: File, writeln;
    auto hosts = File("/etc/hosts");
    {
        auto hosts_copy = hosts;
        hosts_copy.tupleof[0].refs = 1; /* uh-oh */
    }
    auto self = File(__FILE__);
    writeln(hosts.rawRead(new char[1000]));
        /* Reads from __FILE__ instead of /etc/hosts. */
}

And the issue reduced to its core (https://run.dlang.io/is/reMMQt):

--- foo.d
struct S
{
    private int* p;
    this(int x, int y) @safe { p = &[x, y][0]; }
    int get2nd() @trusted { return p is null ? 0 : p[1]; }
        /* Assuming that p is only ever set by the constructor.
        So it's either null or there must be two elements. */
}

--- bar.d
import foo;
import std.stdio;
void main() @safe
{
    auto s = S(1, 2);
    s.tupleof[0] = &[10][0]; /* Should not be allowed. */
    writeln(s.get2nd()); /* garbage */
}

@jacob-carlborg
Copy link
Contributor

It's not a regression (per se), because it's only active in the transitional -dip1000 flag which no one uses at the moment.

To be honest, I missed it was only for DIP1000.

For -dip1000 breaking changes are expected and can be done.

How is this going to be handled when dip1000 is the default? Doesn’t sound likely that every change that dip1000 introduced can be turned into a deprecation.

How are the examples related to memory safety?

BTW, I don’t think that anyone would accidentally use tupleof. That other things dip1000 protests against is more likely to happen by accident.

@WalterBright
Copy link
Member Author

@safe code should not be accessing private members in other modules, and using tupleof to "work around" that restriction is a giant hole in the @safe system. Such code should be marked @trusted or @System.

@WalterBright WalterBright deleted the fix18554 branch March 16, 2018 07:30
@WalterBright
Copy link
Member Author

BTW, I don’t think that anyone would accidentally use tupleof. That other things dip1000 protests against is more likely to happen by accident.

The idea is @safe offers a guarantee, not a "guarantee unless you use tupleof". The latter introduces a hole that will be very hard for reviewers to find, and impossible to guard against.

@WalterBright
Copy link
Member Author

It broadens the meaning of @safe beyond memory safety.

A struct may present an @safe interface to users, but internally have private unsafe pointers. tupleof allows any code to have access to those private members, and can mess up the invariants relied on by the struct. Even int fields are not safe to access, as they may be used as an index to a pointer.

If you're going to access private members in another module, bypassing the safe interface provided by that module, you're in @System land.

@WalterBright
Copy link
Member Author

How is this going to be handled when dip1000 is the default?

Presumably by adding the equivalent of a -nodip1000 switch.

@rainers
Copy link
Member

rainers commented Mar 16, 2018

Accessing private data is not memory unsafe by itself, though it might corrupt data in other ways. @safe has never been about encapsulation so far, though.

Please note that this no longer compiles with -dip1000:


struct S
{
	private int x;
}

void main() @safe
{
	import std.stdio;
	S s = S(7);
	writeln(s);
}

Error:

safe.d(11): Error: @safe function D main cannot call @system function std.stdio.writeln!(S).writeln

@wilzbach
Copy link
Member

That error is about phobos still not working with -dip1000.

It's the same for 2.079: https://run.dlang.io/is/mAwL0j

@rainers
Copy link
Member

rainers commented Mar 16, 2018

Ah, ok. I just tried it against 2.078 which still accepts it.

BTW: "latest" in the list of all compilers on run.dlang.io seems to be 2.078, while "nightly" seems to be about a month old.

@rainers
Copy link
Member

rainers commented Mar 16, 2018

If you're going to access private members in another module, bypassing the safe interface provided by that module, you're in @System land.

That is an encapsulation issue that @safe has not been about so far. Maybe it is ok to add it, but it is not a small addition that is part of escape analysis. It sure needs an update to the documentation.

@wilzbach
Copy link
Member

BTW: "latest" in the list of all compilers on run.dlang.io seems to be 2.078, while "nightly" seems to be about a month old.

Hmm, it gets updated daily by a cron which still seems to work:

https://run.dlang.io/is/zx3psE
https://run.dlang.io/is/O2MtyK

How did you infer that it's outdated?

@WalterBright
Copy link
Member Author

Accessing private data is not memory unsafe by itself

Yes it is. Please refer to my previous explanation as to why. Encapsulating unsafe code and providing a safe interface is essential, and @safe code must respect the encapsulation.

@rainers
Copy link
Member

rainers commented Mar 16, 2018

How did you infer that it's outdated?

If I add --version as a command line option to dmd-nightly, I get DMD64 D Compiler v2.079.0-beta.1-master-75b460e which seems to be 21 days old according to the sha: https://github.com/dlang/dmd/search?q=hash%3A75b460e&type=Commits&utf8=%E2%9C%93.

@rainers
Copy link
Member

rainers commented Mar 16, 2018

Encapsulating unsafe code and providing a safe interface is essential, and @safe code must respect the encapsulation.

That might be true but there is nothing about encapsulation in the documentation of @safe: https://dlang.org/spec/function.html#function-safety.
You can still violate safety if the function sits in the same module as the private member. So far these concepts have been orthogonal.

@WalterBright
Copy link
Member Author

You can still violate safety if the function sits in the same module as the private member.

That's why I said "access private members in another module".

Code in the same module has always been allowed to access private members, this is quite deliberate. The idea is that it eliminates the need for the awful C++ "friend" hack, and presumably the programmer of the module knows what he's doing. Importing a module is another thing entirely.

So far these concepts have been orthogonal.

Encapsulation of system code is fundamental to D's safety system.

@rainers
Copy link
Member

rainers commented Mar 17, 2018

Encapsulation of system code is fundamental to D's safety system.

I don't doubt that. But just to repeat mself: the language specification does not mention that @safe can change encapsulation.

So far the story has been: "If you want to scrutinize the safety of your code marked @safe, just grep for @trusted functions". As the example shows it is actually "grep for @trusted functions and if it uses non-local state check the whole module". Granted, without the change in this PR, you'd need to check the whole code base.

@aG0aep6G
Copy link
Contributor

As the example shows it is actually "grep for @trusted functions and if it uses non-local state check the whole module".

Maybe this warrants a new visibility attribute that's more restrictive than private, not allowing access from the module. You'd still have to check the whole aggregate, of course.

Or let @system apply to variables, meaning that all accesses become @system. Then you couldn't even mess up in @safe methods of the same aggregate.

@John-Colvin
Copy link
Contributor

@WalterBright sounds like in this case the module is the unit of @safe code, not the function, and that therefore the guarantees on any individual @safe function depends on all code (even @system) code being unable to mess up state. Sounds more like care & convention and less like mechanical checking.

(this would be true at aggregate scope even if accessing private was limited to the same aggregate, so that's not the problem)

@John-Colvin
Copy link
Contributor

This leads to a wider question about what "valid state" is when passed to an @safe function (the this reference being the state in question in this PR)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants