feat(context): add Abandon for compositor-destroyed objects
- commit
- 2f95df7ca480dff3a506968175bc1616fa62b981
- author
- brice · · 2026-10-01 18:00 UTC
- parent
- 9be920f
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +51 -0
M core.go +9 -0
| @@ -212,6 +212,15 @@ func (c *Context) claimDestroy(proxy Proxy) error { | |||
| 212 | 212 | return nil | |
| 213 | 213 | } | |
| 214 | 214 | ||
| 215 | + | // Abandon stops delivering events to proxy without sending any request. Use | |
| 216 | + | // it for objects the compositor destroys itself, such as a wl_callback whose | |
| 217 | + | // done event may still be in flight: the object stays a zombie, so a late | |
| 218 | + | // event is discarded and the compositor's wl_display.delete_id retires the ID. | |
| 219 | + | // It returns an error when proxy is not registered on this context. | |
| 220 | + | func (c *Context) Abandon(proxy Proxy) error { | |
| 221 | + | return c.claimDestroy(proxy) | |
| 222 | + | } | |
| 223 | + | ||
| 215 | 224 | // zombie stands in for an object the client destroyed until the compositor | |
| 216 | 225 | // acknowledges the destruction with wl_display.delete_id. Events that were in | |
| 217 | 226 | // flight for it are discarded; their descriptors are closed. | |
| @@ -212,6 +212,15 @@ func (c *Context) claimDestroy(proxy Proxy) error { | |||||
| 212 | return nil | 212 | return nil | ||
| 213 | } | 213 | } | ||
| 214 | 214 | ||||
| 215 | + | // Abandon stops delivering events to proxy without sending any request. Use | |||
| 216 | + | // it for objects the compositor destroys itself, such as a wl_callback whose | |||
| 217 | + | // done event may still be in flight: the object stays a zombie, so a late | |||
| 218 | + | // event is discarded and the compositor's wl_display.delete_id retires the ID. | |||
| 219 | + | // It returns an error when proxy is not registered on this context. | |||
| 220 | + | func (c *Context) Abandon(proxy Proxy) error { | |||
| 221 | + | return c.claimDestroy(proxy) | |||
| 222 | + | } | |||
| 223 | + | ||||
| 215 | // zombie stands in for an object the client destroyed until the compositor | 224 | // zombie stands in for an object the client destroyed until the compositor | ||
| 216 | // acknowledges the destruction with wl_display.delete_id. Events that were in | 225 | // acknowledges the destruction with wl_display.delete_id. Events that were in | ||
| 217 | // flight for it are discarded; their descriptors are closed. | 226 | // flight for it are discarded; their descriptors are closed. | ||
M hardening_test.go +42 -0
| @@ -125,6 +125,48 @@ func TestDestroyedObjectEventsAreDroppedUntilDeleteID(t *testing.T) { | |||
| 125 | 125 | } | |
| 126 | 126 | } | |
| 127 | 127 | ||
| 128 | + | // An abandoned object (a wl_callback the compositor destroys itself) sends no | |
| 129 | + | // request: a late event is dropped and the compositor's delete_id retires the | |
| 130 | + | // ID, both without a protocol error. | |
| 131 | + | func TestAbandonedObjectAcceptsLateEventAndServerDeleteID(t *testing.T) { | |
| 132 | + | client, peer := unixSocketPair(t) | |
| 133 | + | d := newDisplay(client) | |
| 134 | + | p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}} | |
| 135 | + | d.context.Register(p) | |
| 136 | + | if err := d.context.Abandon(p); err != nil { | |
| 137 | + | t.Fatal(err) | |
| 138 | + | } | |
| 139 | + | if err := d.context.Abandon(p); err == nil { | |
| 140 | + | t.Fatal("second Abandon succeeded") | |
| 141 | + | } | |
| 142 | + | if err := d.context.SendRequest(p, 0); err == nil { | |
| 143 | + | t.Fatal("request on abandoned proxy succeeded") | |
| 144 | + | } | |
| 145 | + | if err := peer.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { | |
| 146 | + | t.Fatal(err) | |
| 147 | + | } | |
| 148 | + | if n, _ := peer.Read(make([]byte, 64)); n != 0 { | |
| 149 | + | t.Fatalf("Abandon sent %d bytes", n) | |
| 150 | + | } | |
| 151 | + | ||
| 152 | + | sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil) | |
| 153 | + | if err := d.Dispatch(); err != nil { | |
| 154 | + | t.Fatalf("late event for abandoned object: %v", err) | |
| 155 | + | } | |
| 156 | + | if p.calls != 0 { | |
| 157 | + | t.Fatalf("abandoned proxy received %d events", p.calls) | |
| 158 | + | } | |
| 159 | + | body := make([]byte, 4) | |
| 160 | + | binary.LittleEndian.PutUint32(body, p.ID()) | |
| 161 | + | sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil) | |
| 162 | + | if err := d.Dispatch(); err != nil { | |
| 163 | + | t.Fatalf("delete_id for abandoned object: %v", err) | |
| 164 | + | } | |
| 165 | + | if got := d.AllocateID(); got != p.ID() { | |
| 166 | + | t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got) | |
| 167 | + | } | |
| 168 | + | } | |
| 169 | + | ||
| 128 | 170 | // A plain Go int is ambiguous (value or descriptor) and must be rejected | |
| 129 | 171 | // instead of being written as zero. | |
| 130 | 172 | func TestMarshalRejectsPlainInt(t *testing.T) { | |
| @@ -125,6 +125,48 @@ func TestDestroyedObjectEventsAreDroppedUntilDeleteID(t *testing.T) { | |||||
| 125 | } | 125 | } | ||
| 126 | } | 126 | } | ||
| 127 | 127 | ||||
| 128 | + | // An abandoned object (a wl_callback the compositor destroys itself) sends no | |||
| 129 | + | // request: a late event is dropped and the compositor's delete_id retires the | |||
| 130 | + | // ID, both without a protocol error. | |||
| 131 | + | func TestAbandonedObjectAcceptsLateEventAndServerDeleteID(t *testing.T) { | |||
| 132 | + | client, peer := unixSocketPair(t) | |||
| 133 | + | d := newDisplay(client) | |||
| 134 | + | p := &fdSigProxy{BaseProxy: BaseProxy{id: d.AllocateID(), context: d.context}} | |||
| 135 | + | d.context.Register(p) | |||
| 136 | + | if err := d.context.Abandon(p); err != nil { | |||
| 137 | + | t.Fatal(err) | |||
| 138 | + | } | |||
| 139 | + | if err := d.context.Abandon(p); err == nil { | |||
| 140 | + | t.Fatal("second Abandon succeeded") | |||
| 141 | + | } | |||
| 142 | + | if err := d.context.SendRequest(p, 0); err == nil { | |||
| 143 | + | t.Fatal("request on abandoned proxy succeeded") | |||
| 144 | + | } | |||
| 145 | + | if err := peer.SetReadDeadline(time.Now().Add(50 * time.Millisecond)); err != nil { | |||
| 146 | + | t.Fatal(err) | |||
| 147 | + | } | |||
| 148 | + | if n, _ := peer.Read(make([]byte, 64)); n != 0 { | |||
| 149 | + | t.Fatalf("Abandon sent %d bytes", n) | |||
| 150 | + | } | |||
| 151 | + | ||||
| 152 | + | sendMessageWithFDs(t, peer, message(p.ID(), 0, []byte{1, 0, 0, 0}), nil) | |||
| 153 | + | if err := d.Dispatch(); err != nil { | |||
| 154 | + | t.Fatalf("late event for abandoned object: %v", err) | |||
| 155 | + | } | |||
| 156 | + | if p.calls != 0 { | |||
| 157 | + | t.Fatalf("abandoned proxy received %d events", p.calls) | |||
| 158 | + | } | |||
| 159 | + | body := make([]byte, 4) | |||
| 160 | + | binary.LittleEndian.PutUint32(body, p.ID()) | |||
| 161 | + | sendMessageWithFDs(t, peer, message(displayObjectID, 1, body), nil) | |||
| 162 | + | if err := d.Dispatch(); err != nil { | |||
| 163 | + | t.Fatalf("delete_id for abandoned object: %v", err) | |||
| 164 | + | } | |||
| 165 | + | if got := d.AllocateID(); got != p.ID() { | |||
| 166 | + | t.Fatalf("ID %d not reused after delete_id (got %d)", p.ID(), got) | |||
| 167 | + | } | |||
| 168 | + | } | |||
| 169 | + | ||||
| 128 | // A plain Go int is ambiguous (value or descriptor) and must be rejected | 170 | // A plain Go int is ambiguous (value or descriptor) and must be rejected | ||
| 129 | // instead of being written as zero. | 171 | // instead of being written as zero. | ||
| 130 | func TestMarshalRejectsPlainInt(t *testing.T) { | 172 | func TestMarshalRejectsPlainInt(t *testing.T) { | ||