On Mon, 2018-03-26 at 16:57 +0200, Christophe de Dinechin wrote:
> > On 23 Mar 2018, at 14:53, Lukáš Hrázký <lhra...@redhat.com> wrote:
> > 
> > On Fri, 2018-03-23 at 13:05 +0100, Christophe de Dinechin wrote:
> > > From: Christophe de Dinechin <dinec...@redhat.com>
> > > 
> > > This change addresses three issues related to plugin version checking:
> > > 
> > > 1. It is possible for plugins to bypass version checking or do it wrong
> > >   (as a matter of fact, the mjpeg fallback sets a bad example)
> > > 
> > > 2. The current plugin version check violates the C++ ODR, i.e.
> > >   it relies on undefined behaviors when the header used to compile
> > >   the plugin and to compile the agent are not identical,
> > > 
> > > 3. A major.minor numbering scheme is not ideal for ABI checks.
> > >   In particular, it makes it difficult to react to an incompatibility
> > >   that was detected post release.
> > > 
> > > [More info]
> > > 
> > > 1. Make it impossible to bypass version checking
> > > 
> > > The current code depends on the plugin implementing the version check
> > > correctly by calling PluginVersionIsCompatible. To make things worse,
> > > the only publicly available example gets this wrong and uses an
> > > ad-hoc version check, so anybody copy-pasting this code will get it
> > > wrong.
> > > 
> > > It is more robust to do the version check in the agent before calling
> > > any method in the plugin. It ensures that version checking cannot be
> > > bypassed, is done consistently and generates consistent error messages.
> > > 
> > > As an indication that the aproach is robust, the new check correctly
> > > refuses to load older plugins that use the old version checking method:
> > > 
> > >    spice-streaming-agent[5167]:
> > >        error loading plugin [...].so: no version information
> > > 
> > > 2. ODR-related problems
> > > 
> > > The C++ One Definition Rule (ODR) states that all translation units
> > > must see the same definitions. In the current code, when we call
> > > Agent::PluginVersionIsCompatible from the plugin, it is an ODR
> > > violation as soon as we have made any change in the Agent class
> > > compared to what the plugin was compiled against.
> > > 
> > > The current code therefore relies on implementation dependent knowlege
> > > of how virtual functions are laid out in the vtable, and puts
> > > unnecessary constraints on the changes allowed in the classes
> > > (e.g. it's not allowed to put anything before some member functions)
> > > 
> > > 3. Major.minor numbering scheme
> > > 
> > > The major.minor numbering scheme initially selected makes it harder
> > > to fixes cases where an incompatibility was detected after release.
> > > 
> > > For example, the major.minor version checking assumes that agent 1.21
> > > is compatible with plugins 1.21, 1.13 or 1.03. If later testing
> > > shows that 1.13 actually introduced an incompatiliy, you have to
> > > special-case 1.13 in the compatibiliy check.
> > > 
> > > An approach that does not have this problem is to rely on incremented
> > > version numbers, with a "current" and "oldest compatible" version
> > > number. This is used for example by libtools [1].
> > > 
> > > Since the change required for #1 and #2  introduces an ABI break,
> > > it is as good a time as any to also change the numbering scheme,
> > > since changing it later would introduce another unnecessary ABI break.
> > 
> > Great! AFAIK we haven't made a release yet so we don't need to worry
> > about ABI breakage yet?
> 
> Well, I’d recommend we take the habit of incrementing if we make significant 
> changes, as a way to warn fellow SPICE developers that something changed.

Ok, good idea.

> > 
> > > [1] 
> > > https://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
> > > 
> > > Signed-off-by: Christophe de Dinechin <dinec...@redhat.com>
> > > ---
> > > include/spice-streaming-agent/plugin.hpp | 50 
> > > +++++++++++++++++---------------
> > > src/concrete-agent.cpp                   | 35 +++++++++++-----------
> > > src/concrete-agent.hpp                   |  4 ---
> > > src/mjpeg-fallback.cpp                   |  3 --
> > > 4 files changed, 45 insertions(+), 47 deletions(-)
> > > 
> > > diff --git a/include/spice-streaming-agent/plugin.hpp 
> > > b/include/spice-streaming-agent/plugin.hpp
> > > index e08e3a6..0ec5040 100644
> > > --- a/include/spice-streaming-agent/plugin.hpp
> > > +++ b/include/spice-streaming-agent/plugin.hpp
> > > @@ -23,11 +23,22 @@ namespace streaming_agent {
> > > class FrameCapture;
> > > 
> > > /*!
> > > - * Plugin version, only using few bits, schema is 0xMMmm
> > > - * where MM is major and mm is the minor, can be easily expanded
> > > - * using more bits in the future
> > > + * Plugins use a versioning system similar to that implemented by libtool
> > > + * 
> > > http://www.gnu.org/software/libtool/manual/html_node/Libtool-versioning.html
> > > + * Update the version information as follows:
> > > + * [ANY CHANGE] If any interfaces have been added, removed, or changed 
> > > since the last update,
> > > + * increment PluginInterfaceVersion.
> > > + * [COMPATIBLE CHANGE] If interfaces have only been added since the last 
> > > public release,
> > > + * leave PluginInterfaceOldestCompatibleVersion identical.
> > > + * [INCOMPATIBLE CHANGE] If any interfaces have been removed or changed 
> > > since the last release,
> > > + * set PluginInterfaceOldestCompatibleVersion to PluginInterfaceVersion.
> > > + * [DETECTED INCOMPATIBILITY]: If an incompatibility is detected after a 
> > > release,
> > > + * set PluginInterfaceOldestCompatibleVersion to the last known 
> > > compatible version.
> > >  */
> > > -enum Constants : unsigned { PluginVersion = 0x100u };
> > > +enum Constants : unsigned {
> > > +    PluginInterfaceVersion = 1,
> > > +    PluginInterfaceOldestCompatibleVersion = 1
> > > +};
> > 
> > This is still not too pretty, consider at least renaming Constants to
> > something better, or even use something like:
> > 
> > struct PluginInterfaceVersion {
> >    static constexpr uint16_t current = 1;
> >    static constexpr uint16_t oldest_compatible = 1;
> > };
> > 
> > For the encapsulation?
> 
> The two values are of the same type, and can be compared only with one 
> another, which is what the enum type says. This is completely lost with your 
> suggestion.

Well, they are also integer numbers...

> That being said, Constants is not a very good name choice, it makes it sound 
> like it’s just a bag of random unrelated constants. And it would be 
> beneficial to use an enum class here to enforce the “compare with one 
> another".
> 
> What about the additional patch below?
> 
> Author: Christophe de Dinechin <dinec...@redhat.com>
> Date:   Mon Mar 26 16:26:57 2018 +0200
> 
>     Use an enumeration class for the plugin interface version
> 
> diff --git a/include/spice-streaming-agent/plugin.hpp 
> b/include/spice-streaming-agent/plugin.hpp
> index b01cd82..a98ee58 100644
> --- a/include/spice-streaming-agent/plugin.hpp
> +++ b/include/spice-streaming-agent/plugin.hpp
> @@ -35,9 +35,9 @@ class FrameCapture;
>   * [DETECTED INCOMPATIBILITY]: If an incompatibility is detected after a 
> release,
>   * set PluginInterfaceOldestCompatibleVersion to the last known compatible 
> version.
>   */
> -enum Constants : unsigned {
> -    PluginInterfaceVersion = 1,
> -    PluginInterfaceOldestCompatibleVersion = 1
> +enum class PluginInterfaceVersion : unsigned {
> +    Current = 1,
> +    OldestCompatible = 1
>  };
>  
>  enum Ranks : unsigned {
> @@ -154,8 +154,9 @@ extern "C" spice::streaming_agent::PluginInitFunc 
> spice_streaming_agent_plugin_i
>  
>  #define SPICE_STREAMING_AGENT_PLUGIN(agent)                             \
>      __attribute__ ((visibility ("default")))                            \
> -    unsigned spice_streaming_agent_plugin_interface_version =           \
> -        spice::streaming_agent::PluginInterfaceVersion;                 \
> +    spice::streaming_agent::PluginInterfaceVersion                      \
> +    spice_streaming_agent_plugin_interface_version =                    \
> +        spice::streaming_agent::PluginInterfaceVersion::Current;        \
>                                                                          \
>      __attribute__ ((visibility ("default")))                            \
>      bool spice_streaming_agent_plugin_init(spice::streaming_agent::Agent* 
> agent)
> diff --git a/src/concrete-agent.cpp b/src/concrete-agent.cpp
> index eb4f333..d7b9fbc 100644
> --- a/src/concrete-agent.cpp
> +++ b/src/concrete-agent.cpp
> @@ -66,21 +66,21 @@ void ConcreteAgent::LoadPlugin(const std::string 
> &plugin_filename)
>          return;
>      }
>  
> -    unsigned *version =
> -        (unsigned *) dlsym(dl, 
> "spice_streaming_agent_plugin_interface_version");
> +    PluginInterfaceVersion *version =
> +        (PluginInterfaceVersion *) dlsym(dl, 
> "spice_streaming_agent_plugin_interface_version");
>      if (!version) {
>          syslog(LOG_ERR, "error loading plugin %s: no version information",
>                 plugin_filename.c_str());
>          return;
>      }
> -    if (*version < PluginInterfaceOldestCompatibleVersion ||
> -        *version > PluginInterfaceVersion) {
> +    if (*version < PluginInterfaceVersion::OldestCompatible ||
> +        *version > PluginInterfaceVersion::Current) {
>          syslog(LOG_ERR,
>                 "error loading plugin %s: plugin interface version %u, "
>                 "agent accepts version %u...%u",
>                 plugin_filename.c_str(), *version,
> -               PluginInterfaceOldestCompatibleVersion,
> -               PluginInterfaceVersion);
> +               PluginInterfaceVersion::OldestCompatible,
> +               PluginInterfaceVersion::Current);
>          return;
>      }

Great, I haven't realized you can compare enum class items with one
another... I wanted to suggest an enum class but kept thinking of
numbers and that wouldn't work.

Cheers,
Lukas
_______________________________________________
Spice-devel mailing list
Spice-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/spice-devel

Reply via email to