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

design flaw: using a struct as an interface and @fieldParentPtr can lead to bad pointer dereference #591

Open
andrewrk opened this issue Nov 7, 2017 · 15 comments
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Milestone

Comments

@andrewrk
Copy link
Member

andrewrk commented Nov 7, 2017

Reported here: scurest/zimodre@8df208d#commitcomment-25444745

Any struct that uses @fieldParentPtr on an "interface" field relies on the API user not making a copy of the interface field.

@andrewrk andrewrk added this to the 0.2.0 milestone Nov 7, 2017
@andrewrk
Copy link
Member Author

andrewrk commented Nov 7, 2017

One possible solution:

andrewrk referenced this issue in scurest/zimodre Nov 7, 2017
@andrewrk
Copy link
Member Author

andrewrk commented Nov 7, 2017

Another possible solution:

  • Currently, a = b kind of violates "no hidden control flow". Because if b is a struct, it will do a hidden memcpy.
  • If you see a = b, it's not clear if you're copying a reference (pointer/slice), or copying bytes of data (byval struct / enum / array).

We could disallow = to copy structs/enums/arrays. You would have to do @memcpy(&a, &b, 1);.

With this change, plus #287 (well-defined copy elision semantics), zig's "no hidden control flow" guarantee would even include no hidden memcpy calls.

@andrewrk andrewrk modified the milestones: 0.2.0, 0.3.0 Feb 28, 2018
@andrewrk andrewrk modified the milestones: 0.3.0, 0.4.0 Jul 18, 2018
@ghost
Copy link

ghost commented Sep 3, 2018

Idea:

  • fixed modifier on container member
  • It's a compile error to copy a fixed field out of a container
  • fieldParentPtr only works with fixed

This comes close to promoting fieldParentPtr to full language feature, though.

@ul
Copy link
Contributor

ul commented Oct 1, 2018

Currently, a = b kind of violates "no hidden control flow". Because if b is a struct, it will do a hidden memcpy.

This bitten me so many times this weekend when I started to experiment with Zig! Few specific examples:

const Phasor = struct {
    // ... fields declaration ...

    fn init(freq: *Signal, phase0: *Signal) {
        const phasor = UnicastPhasor(freq, phase0);
        const multicast = Multicast(&phasor.signal);

        return Phasor {
            // oops, it's another phasor now and original is gone after return,
            // multicast will segfault trying to use signal reference
            .phasor = phasor,
            .multicast = multicast,
        };
    }
}
fn writeCallback(...) {
    const userdata = @ptrCast(*UserData, @alignCast(@alignOf(UserData), outstream.userdata));
    var context = userdata.context;
    ...
    for (frame < frame_count) : (frame += 1) {
        ...
        // oops, next time writeCallback is called sample_number starts from zero again!
        context.sample_number += 1;
    }
}
    var zero = audio.signal.zero.signal;
    var freq = audio.signal.Constant.init(440).signal;
    // oops, despite of those signals' parent structures being initialized in the same stack frame
    // they are gone and 440 being captured in Constant is silently lost
    var sine = audio.signal.Sine.init(&freq, &zero); 
    var userdata = audio.stream.UserData.init(&sine.signal);

Perhaps I just need to practice more (just started to write in Zig), but it tricks me again and again in different forms. I started to recognize it and understand how to fix (all mentioned cases are fixed now), but it requires a lot of attention to not make such mistake.

@andrewrk
Copy link
Member Author

andrewrk commented Oct 1, 2018

Thanks for your input @ul. I agree that this is a big flaw in the language. It's a top priority to fix in 0.4.0.

@ghost
Copy link

ghost commented Oct 2, 2018

I think it’s best to eliminate parentPointer as the preferred way to do interfaces because it’s hard to read, hard to write and inefficient/ not optimal because of pointer indirections which can be eliminated.

@tgschultz
Copy link
Contributor

Another issue with current interfaces is that you lose error set information. However, the current alternatives to this pattern aren't very good either. You can use var everywhere, but that makes the code more difficult to read because you don't know what is expected (potentially solvable with #1268), or you can use a type-generator like ArrayList does, but that gets verbose:

var sis = SliceInStream.init(data[0..]);
var reader = StreamReader(SliceInStream).init(sis.stream, builtin.Endian.Big);
var bit_reader = BitReader(@typeOf(reader)).init(&reader, builtin.Endian.Little);

@andrewrk
Copy link
Member Author

This is an important issue, but it's blocking on #287, so postponing it to 0.5.0.

@shawnl
Copy link
Contributor

shawnl commented May 16, 2019

@dbandstra Your fixed proposal also makes static analysis for strict aliasing much easier (which is currently not a zig thing). Given that struct members are not ordered, the compiler can then put the fixed member first.

@daurnimator
Copy link
Collaborator

This is an important issue, but it's blocking on #287, so postponing it to 0.5.0.

With copy elision part one complete is this issue unblocked?

@andrewrk andrewrk modified the milestones: 0.5.0, 0.6.0 Aug 22, 2019
@SpexGuy
Copy link
Contributor

SpexGuy commented Feb 12, 2021

I believe this is solved by #7769

@SpexGuy SpexGuy closed this as completed Feb 12, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.8.0 Feb 12, 2021
@ul
Copy link
Contributor

ul commented Feb 12, 2021

#7769 is an open proposal, not an implemented language feature, isn't it? Why did you close this issue?

@andrewrk
Copy link
Member Author

andrewrk commented Feb 12, 2021

From a language design perspective, the issue is solved because #7769 is an accepted (planned) proposal, so there is not a design flaw but rather an implementation TODO. That's why @SpexGuy closed the issue.

However, from a user perspective, this problem is still a footgun to look out for, and won't be solved until #7769 is actually implemented.

For cases like this, I do prefer to have issues represent things from the user perspective. That means we leave it open until it's actually implemented, then close both of them. Regardless, @SpexGuy I do appreciate the organization/cleanup efforts you have been making on the issue tracker.

@andrewrk andrewrk reopened this Feb 12, 2021
@ul
Copy link
Contributor

ul commented Feb 12, 2021

Thank you for the clarification!

@SpexGuy SpexGuy added the use case Describes a real use case that is difficult or impossible, but does not propose a solution. label Mar 20, 2021
@andrewrk andrewrk modified the milestones: 0.8.0, 0.9.0 Jun 4, 2021
@andrewrk andrewrk modified the milestones: 0.9.0, 0.10.0 Nov 24, 2021
@andrewrk andrewrk modified the milestones: 0.10.0, 0.11.0 Apr 16, 2022
@andrewrk andrewrk modified the milestones: 0.11.0, 0.12.0 Apr 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
use case Describes a real use case that is difficult or impossible, but does not propose a solution.
Projects
None yet
Development

No branches or pull requests

6 participants