Skip to content

Fix issue 17394 - mkdirRecurse isn't @safe#5386

Merged
dlang-bot merged 2 commits intodlang:masterfrom
wilzbach:safe-file
May 17, 2017
Merged

Fix issue 17394 - mkdirRecurse isn't @safe#5386
dlang-bot merged 2 commits intodlang:masterfrom
wilzbach:safe-file

Conversation

@wilzbach
Copy link
Contributor

There are a lot of lot hanging fruits in std.file and in general imho we should invest a lot more work in making Phobos work nicely with @safe because currently it's really an empty promise as every second standard library function needs to be @trusted ...

@dlang-bot
Copy link
Contributor

Fix Bugzilla Description
17394 std.file.mkdirRecurse isn't @safe

std/file.d Outdated
if (isConvertibleToString!R)
{
return mkdirRecurse!(StringTypeOf!R)(pathname);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

mkdir accepts auto ref, so I guessed that it was probably an oversight that mkdirRecurse doesn't?

Copy link
Member

Choose a reason for hiding this comment

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

mkdirRecurse accepted a char array only - not much point for auto ref there

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well there's the following API for mkdir:

void mkdir(R)(auto ref R pathname)
 if (isConvertibleToString!R)
 {
     return mkdir!(StringTypeOf!R)(pathname);
 }


immutable basepath = deleteme ~ "_dir";
scope(exit) rmdirRecurse(basepath);
scope(exit) () @trusted { rmdirRecurse(basepath); }();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was done to keep this PR small and go in steady baby steps

std/file.d Outdated
void mkdirRecurse(in char[] pathname)
void mkdirRecurse(R)(R pathname)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
!isConvertibleToString!R)
Copy link
Member

Choose a reason for hiding this comment

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

mkdirRecurse accepts an arbitrary range then tries to call ensureDirExists which accepts only in char[]. ensureDirExists thus either needs to be changed to accept a range or mkdirRecurse's signature change undone.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh good catch! I went with changing ensureDirExists, but I don't have a strong feeling about this.

std/file.d Outdated
version(Windows)
{
if (CreateDirectoryW(pathname.tempCStringW(), null))
if (() @trusted { return CreateDirectoryW(pathname.tempCStringW(), null); }())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

in mkdir static @trusted functions are used:

static auto trustedCreateDirectoryW(const(FSChar)* pathz) @trusted
{
      return CreateDirectoryW(pathz, null);
}

I like the temporary lambdas more, but AFAIK they do create an overhead. Does by chance anyone know whether this overhead is negligible after optimization?

Copy link
Member

Choose a reason for hiding this comment

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

Doesn't matter as long as they don't create garbage.

Copy link
Contributor

Choose a reason for hiding this comment

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

Does by chance anyone know whether this overhead is negligible after optimization?

Depends on the in-liner. Chances are DMD won't in-line this, but I wouldn't change this, as optimizing for DMD's in-liner is a deep rabbit hole we don't want to go down.

Copy link
Member

Choose a reason for hiding this comment

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

I'm a bit uneasy on this, as you are trusting the pathname's range operations when you do tempCStringW (as well as assuming the range doesn't have such a member name and you aren't calling the wrong thing). Is it possible to do this outside the trusted lambda?

Copy link
Member

Choose a reason for hiding this comment

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

mkdir does auto pathz = pathname.tempCString!FSChar();, same could be done here

std/file.d Outdated
private bool ensureDirExists(in char[] pathname)
private bool ensureDirExists(R)(R pathname)
if (isInputRange!R && !isInfinite!R && isSomeChar!(ElementEncodingType!R) &&
!isConvertibleToString!R)
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you want !isConvertibleToString!R as unlike mkdir there is no overload with if(isConvertibleToString!R).

std/file.d Outdated
import std.conv : octal;

if (core.sys.posix.sys.stat.mkdir(pathname.tempCString(), octal!777) == 0)
if (() @trusted { return core.sys.posix.sys.stat.mkdir(pathname.tempCString(), octal!777); }() == 0)
Copy link
Member

Choose a reason for hiding this comment

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

Same thing here, try and make the tempCString outside the lambda.

@wilzbach wilzbach force-pushed the safe-file branch 2 times, most recently from 7fe771e to e50531d Compare May 13, 2017 11:33
@wilzbach
Copy link
Contributor Author

I removed the API unification to avoid any controversial issues - the unification between mkdir and mkdirRecurse can be done in a separate PR.

@schveiguy
Copy link
Member

Thanks, LGTM.

std/file.d Outdated
*/

void mkdirRecurse(in char[] pathname)
void mkdirRecurse()(in char[] pathname)
Copy link
Contributor

Choose a reason for hiding this comment

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

Why did this become a template? Templatizing things increases user code compilation times and is, strictly speaking, a breaking API change, so shouldn't be done without good reason.

Copy link
Member

Choose a reason for hiding this comment

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

It will need to become a template anyway to accept arbitrary char ranges as all the other stuff in std.file.

Copy link
Contributor

Choose a reason for hiding this comment

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

YAGNI for now and change it once it's actually required? If you are happy with the change, feel free to merge, though.

Copy link
Contributor

Choose a reason for hiding this comment

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

@klickverbot This can't be merged with your request for changes still pending

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

@klickverbot the other option is to manually add the attributes, and then remove them all later when we change to a range interface. Neither path is super-appealing.

Can you explain why this is a breaking API change?

Copy link
Member

Choose a reason for hiding this comment

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

The first thing that comes to mind is that you can't take its address anymore. When it's not a template, the function name can be used on its own, but once it is, it has to be instantiated. When calling it, that's not a big deal, because IFTI takes care of the instantiation for you, but for other cases, it has to be done manually.

Of course, even changing a single attribute is an API change, so we're actually really terrible in general about maintaining API compatibility. We usually do a fairly good job with avoiding breaking the common cases, but we frequently break the corner cases.

Copy link
Contributor

Choose a reason for hiding this comment

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

Of course, even changing a single attribute is an API change

Being pedantic here, but isn't adding something like nothrow an ABI and not an API change?

Copy link
Member

Choose a reason for hiding this comment

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

I suppose that that depends on whether it affects who can call the code if it's recompiled rather than simply linked. I don't know. I tend to think of the API as being anything in the function signature, whereas the ABI has to do with the resulting binary, and changing the attributes changes the function signature.

Regardless, we routinely make changes that break compatibility in small ways, and the way that D is designed (particularly with attribute inference), you'd have to be pretty disciplined about it to never break any code with changes.

@schveiguy schveiguy dismissed dnadlinger’s stale review May 17, 2017 12:33

Removed concern

@schveiguy
Copy link
Member

@wilzbach I just removed the template for now. In the next go-around when you want to add range support, just remove the @safe tag.

@dlang-bot dlang-bot merged commit 22799ff into dlang:master May 17, 2017
@wilzbach wilzbach deleted the safe-file branch October 1, 2017 15:27
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.

7 participants