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

Headerless xml parsing fix #715

Merged
merged 7 commits into from
Apr 23, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 12 additions & 16 deletions wayland-protocols-plasma/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,12 @@ pub mod fake_input {
);
}

// This protocol is disabled for now as the file is not valid XML because it does not have a XML header
//
// pub mod fullscreen_shell {
// wayland_protocol!(
// "./plasma-wayland-protocols/src/protocols/fullscreen-shell.xml",
// []
// );
// }
pub mod fullscreen_shell {
wayland_protocol!(
"./plasma-wayland-protocols/src/protocols/fullscreen-shell.xml",
[]
);
}

pub mod idle {
wayland_protocol!(
Expand Down Expand Up @@ -179,14 +177,12 @@ pub mod slide {
);
}

// This protocol is disabled for now as the file is not valid XML because it does not have a XML header
//
// pub mod surface_extension {
// wayland_protocol!(
// "./plasma-wayland-protocols/src/protocols/surface-extension.xml",
// []
// );
// }
pub mod surface_extension {
wayland_protocol!(
"./plasma-wayland-protocols/src/protocols/surface-extension.xml",
[]
);
}

pub mod text_input {
pub mod v1 {
Expand Down
51 changes: 39 additions & 12 deletions wayland-scanner/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ macro_rules! extract_end_tag(
pub fn parse<S: Read>(stream: S) -> Protocol {
let mut reader = Reader::from_reader(BufReader::new(stream));
reader.trim_text(true).expand_empty_elements(true);
// Skip first <?xml ... ?> event
let _ = reader.read_event_into(&mut Vec::new());
parse_protocol(reader)
}

Expand All @@ -52,18 +50,30 @@ fn parse_or_panic<T: FromStr>(txt: &[u8]) -> T {
}
}

fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Protocol {
let mut protocol = extract_from!(
reader => Event::Start(bytes) => {
assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag");
if let Some(attr) = bytes.attributes().filter_map(|res| res.ok()).find(|attr| attr.key.into_inner() == b"name") {
Protocol::new(decode_utf8_or_panic(attr.value.into_owned()))
} else {
panic!("Protocol must have a name");
}
fn init_protocol<R: BufRead>(reader: &mut Reader<R>) -> Protocol {
// Check two firsts lines for protocol tag
for _ in 0..2 {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Decl(_)) => {
continue;
},
Ok(Event::Start(bytes)) => {
assert!(bytes.name().into_inner() == b"protocol", "Missing protocol toplevel tag");
if let Some(attr) = bytes.attributes().filter_map(|res| res.ok()).find(|attr| attr.key.into_inner() == b"name") {
return Protocol::new(decode_utf8_or_panic(attr.value.into_owned()))
} else {
panic!("Protocol must have a name");
}
},
_ => panic!("Ill-formed protocol file")
}
);
}
panic!("Ill-formed protocol file");
}

fn parse_protocol<R: BufRead>(mut reader: Reader<R>) -> Protocol {
let mut protocol = init_protocol(&mut reader);

loop {
match reader.read_event_into(&mut Vec::new()) {
Ok(Event::Start(bytes)) => {
Expand Down Expand Up @@ -366,3 +376,20 @@ fn parse_entry<R: BufRead>(reader: &mut Reader<R>, attrs: Attributes) -> Entry {

entry
}

#[cfg(test)]
mod tests {
#[test]
fn xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}

#[test]
fn headerless_xml_parse() {
let protocol_file =
std::fs::File::open("./tests/scanner_assets/test-headerless-protocol.xml").unwrap();
let _ = crate::parse::parse(protocol_file);
}
}
252 changes: 252 additions & 0 deletions wayland-scanner/tests/scanner_assets/test-headerless-protocol.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
<protocol name="test-protocol">
Copy link
Member

Choose a reason for hiding this comment

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

Given this test file is only used to ensure the scanner doesn't choke on it, I don't think it needs to be as big as the main one, it can be much more minimalistic I'd say.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's done, thanks.


<copyright>
A dummy copyright to make the parser work

Including a few more lines.
Of text.
</copyright>

<!-- Include the core interfaces display/registry/callback -->

<interface name="wl_display" version="1">
<description summary="core global object">
The core global object. This is a special singleton object. It
is used for internal Wayland protocol features.
</description>

<request name="sync">
<description summary="asynchronous roundtrip">
The sync request asks the server to emit the 'done' event
on the returned wl_callback object. Since requests are
handled in-order and events are delivered in-order, this can
be used as a barrier to ensure all previous requests and the
resulting events have been handled.

The object returned by this request will be destroyed by the
compositor after the callback is fired and as such the client must not
attempt to use it after that point.

The callback_data passed in the callback is the event serial.
</description>
<arg name="callback" type="new_id" interface="wl_callback"
summary="callback object for the sync request"/>
</request>

<request name="get_registry">
<description summary="get global registry object">
This request creates a registry object that allows the client
to list and bind the global objects available from the
compositor.

It should be noted that the server side resources consumed in
response to a get_registry request can only be released when the
client disconnects, not when the client side proxy is destroyed.
Therefore, clients should invoke get_registry as infrequently as
possible to avoid wasting memory.
</description>
<arg name="registry" type="new_id" interface="wl_registry"
summary="global registry object"/>
</request>

<event name="error">
<description summary="fatal error event">
The error event is sent out when a fatal (non-recoverable)
error has occurred. The object_id argument is the object
where the error occurred, most often in response to a request
to that object. The code identifies the error and is defined
by the object interface. As such, each interface defines its
own set of error codes. The message is a brief description
of the error, for (debugging) convenience.
</description>
<arg name="object_id" type="object" summary="object where the error occurred"/>
<arg name="code" type="uint" summary="error code"/>
<arg name="message" type="string" summary="error description"/>
</event>

<enum name="error">
<description summary="global error values">
These errors are global and can be emitted in response to any
server request.
</description>
<entry name="invalid_object" value="0"
summary="server couldn't find object"/>
<entry name="invalid_method" value="1"
summary="method doesn't exist on the specified interface or malformed request"/>
<entry name="no_memory" value="2"
summary="server is out of memory"/>
<entry name="implementation" value="3"
summary="implementation error in compositor"/>
</enum>

<event name="delete_id">
<description summary="acknowledge object ID deletion">
This event is used internally by the object ID management
logic. When a client deletes an object that it had created,
the server will send this event to acknowledge that it has
seen the delete request. When the client receives this event,
it will know that it can safely reuse the object ID.
</description>
<arg name="id" type="uint" summary="deleted object ID"/>
</event>
</interface>

<interface name="wl_registry" version="1">
<description summary="global registry object">
The singleton global registry object. The server has a number of
global objects that are available to all clients. These objects
typically represent an actual object in the server (for example,
an input device) or they are singleton objects that provide
extension functionality.

When a client creates a registry object, the registry object
will emit a global event for each global currently in the
registry. Globals come and go as a result of device or
monitor hotplugs, reconfiguration or other events, and the
registry will send out global and global_remove events to
keep the client up to date with the changes. To mark the end
of the initial burst of events, the client can use the
wl_display.sync request immediately after calling
wl_display.get_registry.

A client can bind to a global object by using the bind
request. This creates a client-side handle that lets the object
emit events to the client and lets the client invoke requests on
the object.
</description>

<request name="bind">
<description summary="bind an object to the display">
Binds a new, client-created object to the server using the
specified name as the identifier.
</description>
<arg name="name" type="uint" summary="unique numeric name of the object"/>
<arg name="id" type="new_id" summary="bounded object"/>
</request>

<event name="global">
<description summary="announce global object">
Notify the client of global objects.

The event notifies the client that a global object with
the given name is now available, and it implements the
given version of the given interface.
</description>
<arg name="name" type="uint" summary="numeric name of the global object"/>
<arg name="interface" type="string" summary="interface implemented by the object"/>
<arg name="version" type="uint" summary="interface version"/>
</event>

<event name="global_remove">
<description summary="announce removal of global object">
Notify the client of removed global objects.

This event notifies the client that the global identified
by name is no longer available. If the client bound to
the global using the bind request, the client should now
destroy that object.

The object remains valid and requests to the object will be
ignored until the client destroys it, to avoid races between
the global going away and a client sending a request to it.
</description>
<arg name="name" type="uint" summary="numeric name of the global object"/>
</event>
</interface>

<interface name="wl_callback" version="1">
<description summary="callback object">
Clients can handle the 'done' event to get notified when
the related request is done.
</description>

<event name="done" type="destructor">
<description summary="done event">
Notify the client when the related request is done.
</description>
<arg name="callback_data" type="uint" summary="request-specific data for the callback"/>
</event>
</interface>

<!-- And now the test interfaces -->

<interface name="test_global" version="5">
<request name="many_args">
<description summary="a request with every possible non-object arg"></description>
<arg name="unsigned_int" type="uint" summary="an unsigned int" />
<arg name="signed_int" type="int" summary="a singed int" />
<arg name="fixed_point" type="fixed" summary="a fixed point number" />
<arg name="number_array" type="array" summary="an array" />
<arg name="some_text" type="string" summary="some text" />
<arg name="file_descriptor" type="fd" summary="a file descriptor" />
</request>

<request name="get_secondary" since="2">
<arg name="sec" type="new_id" interface="secondary" summary="create a secondary" />
</request>

<request name="get_tertiary" since="3">
<arg name="ter" type="new_id" interface="tertiary" summary="create a tertiary" />
</request>

<request name="link" since="3">
<description summary="link a secondary and a tertiary"></description>
<arg name="sec" type="object" interface="secondary" />
<arg name="ter" type="object" interface="tertiary" allow-null="true" />
<arg name="time" type="uint" />
</request>

<request name="destroy" type="destructor" since="4">
</request>

<request name="reverse_link" since="5">
<description summary="reverse link a secondary and a tertiary"></description>
<arg name="sec" type="object" interface="secondary" allow-null="true" />
<arg name="ter" type="object" interface="tertiary" />
</request>

<request name="newid_and_allow_null" since="5">
<description summary="a newid request that also takes allow null arg"></description>
<arg name="quad" type="new_id" interface="quad" />
<arg name="sec" type="object" interface="secondary" allow-null="true" />
<arg name="ter" type="object" interface="tertiary" />
</request>

<event name="many_args_evt">
<description summary="an event with every possible non-object arg"></description>
<arg name="unsigned_int" type="uint" summary="an unsigned int" />
<arg name="signed_int" type="int" summary="a singed int" />
<arg name="fixed_point" type="fixed" summary="a fixed point number" />
<arg name="number_array" type="array" summary="an array" />
<arg name="some_text" type="string" summary="some text" />
<arg name="file_descriptor" type="fd" summary="a file descriptor" />
</event>

<event name="ack_secondary">
<description summary="acking the creation of a secondary"></description>
<arg name="sec" type="object" interface="secondary" />
</event>

<event name="cycle_quad">
<description summary="create a new quad optionally replacing a previous one"></description>
<arg name="new_quad" type="new_id" interface="quad" />
<arg name="old_quad" type="object" interface="quad" allow-null="true" />
</event>
</interface>

<interface name="secondary" version="5">
<request name="destroy" type="destructor" since="2">
</request>
</interface>

<interface name="tertiary" version="5">
<request name="destroy" type="destructor" since="3">
</request>
</interface>

<interface name="quad" version="5">
<request name="destroy" type="destructor" since="3">
</request>
</interface>

</protocol>
Loading