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

Make std.array.insertInPlace @safe for some cases #1430

Merged
merged 1 commit into from Dec 19, 2013
Merged

Make std.array.insertInPlace @safe for some cases #1430

merged 1 commit into from Dec 19, 2013

Conversation

tom-tan
Copy link
Contributor

@tom-tan tom-tan commented Jul 22, 2013

This pull request makes insertInPlace safe for some cases like:

  • array for int[]

I leave the case for strings because the unittest for it uses std.conv.to which is currently not safe for strings.

@ghost
Copy link

ghost commented Jul 23, 2013

I'm getting a little weary of these safety tricks that Kenji introduced but are now becoming more prevalent. Are we going to keep spamming lambda functions everywhere just to simulate safety? It really feels like the wrong approach.

Using this lambda trick also introduced a regression, so it has some issues with scoping. (although maybe that's just a compiler bug).

@andralex: Any opinions about these lambdas recently? Or am I worrying too much?

@Infiltrator
Copy link
Contributor

I agree. What's the point of even having @safe if you're going to work around it anyway? The nothrow pure @safe fixes have to start at the core and work their way up to phobos.

@monarchdodra
Copy link
Collaborator

I'm getting a little weary of these safety tricks that Kenji introduced but are now becoming more prevalent. Are we going to keep spamming lambda functions everywhere just to simulate safety? It really feels like the wrong approach.

Are you complaining about the fact that we have functions that are safe that weren't before? Or the "hackish" approach of doing it via lambdas ?

I suggested a little while ago, doing the same thing with a more idiomatic "blocks with attributes".
http://forum.dlang.org/thread/pnxzjenvyzynsagwklxr@forum.dlang.org

I agree. What's the point of even having @safe if you're going to work around it anyway?

Isn't that what @trusted is all about? That said, I think that any function marked trusted should be reviewed much more closely, or it does undermine the entire thing.

@tom-tan
Copy link
Contributor Author

tom-tan commented Aug 25, 2013

Currently there is a performance issue to use trusted lambda call and 9rnsr proposed a pull request to solve this issue (See Issue 2483 in dmd: dlang/dmd#2483).

Can I wait for issue 2483 merged or use nested functions instead of lambda call?

@monarchdodra
Copy link
Collaborator

I think the implementation is way too lacking to be able to promise safety. If one of the calls to emplace throws, then the passed in slice will be modified to contain elements that were duplicated, but without a call to postblit, which can later lead to very nasty some very memory corruption issues. You could fix this by making a temp slice, and only assign back to the original slice at the end of the operation. Such a fix would be necessary.

Also, as I mentioned in std.array.array, emplace, as is, simply can't be used in safe code. Lucky for us, the ".ptr" should prevent that.

I suggest you change the pointer iteration for simple index iteration. This means the only line of code that would be unsafe is emplace(&src[i], val). From there, you could use the same trick as in std.array.array, to bypass emplace entirely when possible, making (at least) insertInPlace safe for the more basic types.

@tom-tan
Copy link
Contributor Author

tom-tan commented Sep 10, 2013

Sorry for late reply.

You could fix this by making a temp slice, and only assign back to the original slice at the end of the operation.

I found several things for that.

  • Simple assigning back does not work for the structs with an elaborate destructor. In fact, such change breaks one of the unittests because of this issue. (See the unittest in https://github.com/tom-tan/phobos/blob/c629e5579926484df1391bb7426556cc43fc7e0c/std/array.d#L1168)
  • In the current implementation of insertInPlace, we use copyBackwards but what we want to do is moving elements rather than copying them.
  • I think std.algorithm.move helps us to this purpose. Unfortunately, it cannot work as safe function (currently) and it is not CTFEable.
  • (Minor issue) I think any destructors of the struct should work even for the default value (i.e. T.init) but struct Int (which is defined in the unittest) does not work for the default value. If we use move instead of copyBackwords, it will cause segfalt.

First, I will make std.algorithm.move safe and CTFEable.
After that, I will fix this issue.

@tom-tan
Copy link
Contributor Author

tom-tan commented Sep 15, 2013

I filed one bug in druntime (Bugzilla: http://d.puremagic.com/issues/show_bug.cgi?id=11041).
Non-CTFEable core.stdc.string.memcpy prevents std.algorithm.move from being CTFEable
and it prevents insertInPlace from reimplementing with move without breaking its API.

@monarchdodra
Copy link
Collaborator

You can usually "bypass" these kinds of problem with "if (__ctfe)", where you "run" simpler code in case of ctfe.

Further more, you should mostly consider simplicity over efficiency for ctfe (after all, the code is never "run"). Bypassing "move" entirelly in copy backwards should be fine.

@tom-tan
Copy link
Contributor Author

tom-tan commented Sep 15, 2013

Thank you for your advice.
I will bypass move in copyBackwards.

BTW, should insertInPlace work for the struct with @disable opAssign? Currently the following code doesn't work.

    struct T
    {
        int a;
        @disable typeof(this) opAssign(typeof(this));
    }
    T[] a = [ T(1), T(2), T(3), T(4) ];
    a.insertInPlace(2, [ T(1), T(2) ]);
    assert(a == [ T(1), T(2), T(1), T(2), T(3), T(4) ]);

@dnadlinger
Copy link
Member

@AndrejMitrovic: Regarding @trusted for parts of a function, that's not exactly a new discussion: http://forum.dlang.org/thread/blrglebkzhrilxkbprgh@forum.dlang.org. Maybe the time has come to finally do something about this?

@tom-tan
Copy link
Contributor Author

tom-tan commented Oct 2, 2013

Now insertInPlace bypass the emplace for most cases.

Some notes:

  • I use a named trusted function instead of trusted lambda call in copyBackwards because currently it is not inlined.
  • I define a nested function in insertInPlace to encapsulate the assignment. It prevents a duplication of the bypass for emplace.
  • I leave the case for the struct with @disable opAssign (Currently it doesn't work with insertInPlace. It doesn't break the compatibility).
    If it should be work with insertInPlace, I will fix it.
  • I remove the workaround for bug 8740 in a unittest. It is already fixed.

@@ -897,20 +917,24 @@ void insertInPlace(T, U...)(ref T[] array, size_t pos, U stuff)
to_insert += stuff[i].length;
}
array.length += to_insert;
copyBackwards(array[pos..oldLen], array[pos+to_insert..$]);
auto ptr = array.ptr + pos;
auto tmp = new T[to_insert];
Copy link
Member

Choose a reason for hiding this comment

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

Wait, why the extra GC allocation? Or am I missing something (just looking at the diff right now)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it's a "quick and dirty" way to achieve strong exception safety: If one of the insertions go wrong, then the original range is never modified.

Copy link
Collaborator

Choose a reason for hiding this comment

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

In which case, that array.length should but moved down...

Copy link
Collaborator

Choose a reason for hiding this comment

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

BTW, when allocating a temp array/buffer like this, you are usually better of using uninitializedArray (or minimallyInitilaizedArray).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for your comments and advice.
I moved down array.length and now insertInPlace uses uninitializedArray to allocate buffer.

@monarchdodra
Copy link
Collaborator

Looking better :)

@tom-tan
Copy link
Contributor Author

tom-tan commented Oct 5, 2013

If there are no problem, I will squash the commits.

@monarchdodra
Copy link
Collaborator

Looks good. Indeed, please squash.

@tom-tan
Copy link
Contributor Author

tom-tan commented Oct 17, 2013

I squashed the commits.

@tom-tan
Copy link
Contributor Author

tom-tan commented Dec 16, 2013

Oh...
Sorry for bad rebase.
I will fix it soon.

@tom-tan
Copy link
Contributor Author

tom-tan commented Dec 16, 2013

Done.
I rebased it.

monarchdodra added a commit that referenced this pull request Dec 19, 2013
Make std.array.insertInPlace @safe for some cases
@monarchdodra monarchdodra merged commit 263d35f into dlang:master Dec 19, 2013
@monarchdodra
Copy link
Collaborator

Merged. TYVM.

@tom-tan tom-tan deleted the safe-array-insertInPlace branch December 20, 2013 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants