Browse Source

Trade, Audio: explicitly disallow custom deleters returned from plugins.

findsdl-include-root
Vladimír Vondruš 7 years ago
parent
commit
84fc685c87
  1. 5
      src/Magnum/Audio/AbstractImporter.cpp
  2. 22
      src/Magnum/Audio/AbstractImporter.h
  3. 22
      src/Magnum/Audio/Test/AbstractImporterTest.cpp
  4. 21
      src/Magnum/Image.h
  5. 16
      src/Magnum/Trade/AbstractImageConverter.cpp
  6. 24
      src/Magnum/Trade/AbstractImageConverter.h
  7. 16
      src/Magnum/Trade/AbstractImporter.cpp
  8. 40
      src/Magnum/Trade/AbstractImporter.h
  9. 5
      src/Magnum/Trade/AnimationData.h
  10. 6
      src/Magnum/Trade/ImageData.h
  11. 86
      src/Magnum/Trade/Test/AbstractImageConverterTest.cpp
  12. 105
      src/Magnum/Trade/Test/AbstractImporterTest.cpp

5
src/Magnum/Audio/AbstractImporter.cpp

@ -127,7 +127,10 @@ UnsignedInt AbstractImporter::frequency() const {
Containers::Array<char> AbstractImporter::data() { Containers::Array<char> AbstractImporter::data() {
CORRADE_ASSERT(isOpened(), "Audio::AbstractImporter::data(): no file opened", nullptr); CORRADE_ASSERT(isOpened(), "Audio::AbstractImporter::data(): no file opened", nullptr);
return doData();
Containers::Array<char> out = doData();
CORRADE_ASSERT(!out.deleter(), "Audio::AbstractImporter::data(): implementation is not allowed to use a custom Array deleter", {});
return out;
} }
Debug& operator<<(Debug& debug, const AbstractImporter::Feature value) { Debug& operator<<(Debug& debug, const AbstractImporter::Feature value) {

22
src/Magnum/Audio/AbstractImporter.h

@ -43,6 +43,15 @@ Provides interface for importing various audio formats. See @ref plugins for
more information and `*Importer` classes in @ref Audio namespace for available more information and `*Importer` classes in @ref Audio namespace for available
importer plugins. importer plugins.
@section Audio-AbstractImporter-data-dependency Data dependency
The data returned from various functions *by design* have no dependency on the
importer instance and neither on the dynamic plugin module. In other words, you don't need to keep the importer instance (or the plugin manager instance)
around in order to have the returned data valid. Moreover, all returned
@ref Corrade::Containers::Array instances are only allowed to have default
deleters --- this is to avoid potential dangling function pointer calls when
destructing such instances after the plugin module has been unloaded.
@section Audio-AbstractImporter-subclassing Subclassing @section Audio-AbstractImporter-subclassing Subclassing
Plugin implements function @ref doFeatures(), @ref doIsOpened(), one of or both Plugin implements function @ref doFeatures(), @ref doIsOpened(), one of or both
@ -60,10 +69,15 @@ checked by the implementation:
- All `do*()` implementations working on opened file are called only if - All `do*()` implementations working on opened file are called only if
there is any file opened. there is any file opened.
@attention @ref Corrade::Containers::Array instances returned from the plugin @m_class{m-block m-warning}
should *not* use anything else than the default deleter, otherwise this can
cause dangling function pointer call on array destruction if the plugin @par Dangling function pointers on plugin unload
gets unloaded before the array is destroyed. As @ref Trade-AbstractImporter-data-dependency "mentioned above",
@ref Corrade::Containers::Array instances returned from plugin
implementations are not allowed to use anything else than the default
deleter, otherwise this could cause dangling function pointer call on array
destruction if the plugin gets unloaded before the array is destroyed. This
is asserted by the base implementation on return.
*/ */
class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagingPlugin<AbstractImporter> { class MAGNUM_AUDIO_EXPORT AbstractImporter: public PluginManager::AbstractManagingPlugin<AbstractImporter> {
public: public:

22
src/Magnum/Audio/Test/AbstractImporterTest.cpp

@ -61,6 +61,7 @@ struct AbstractImporterTest: TestSuite::Tester {
void data(); void data();
void dataNoFile(); void dataNoFile();
void dataCustomDeleter();
void debugFeature(); void debugFeature();
void debugFeatures(); void debugFeatures();
@ -85,6 +86,7 @@ AbstractImporterTest::AbstractImporterTest() {
&AbstractImporterTest::data, &AbstractImporterTest::data,
&AbstractImporterTest::dataNoFile, &AbstractImporterTest::dataNoFile,
&AbstractImporterTest::dataCustomDeleter,
&AbstractImporterTest::debugFeature, &AbstractImporterTest::debugFeature,
&AbstractImporterTest::debugFeatures}); &AbstractImporterTest::debugFeatures});
@ -339,6 +341,26 @@ void AbstractImporterTest::dataNoFile() {
CORRADE_COMPARE(out.str(), "Audio::AbstractImporter::data(): no file opened\n"); CORRADE_COMPARE(out.str(), "Audio::AbstractImporter::data(): no file opened\n");
} }
void AbstractImporterTest::dataCustomDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
BufferFormat doFormat() const override { return {}; }
UnsignedInt doFrequency() const override { return {}; }
Containers::Array<char> doData() override {
return Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.data();
CORRADE_COMPARE(out.str(), "Audio::AbstractImporter::data(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::debugFeature() { void AbstractImporterTest::debugFeature() {
std::ostringstream out; std::ostringstream out;

21
src/Magnum/Image.h

@ -36,6 +36,11 @@
namespace Magnum { namespace Magnum {
#ifndef DOXYGEN_GENERATING_OUTPUT
/** @todo remove once AbstractImageConverter returns ImageData instead */
namespace Trade { class AbstractImageConverter; }
#endif
/** /**
@brief Image @brief Image
@ -443,6 +448,14 @@ template<UnsignedInt dimensions> class Image {
Containers::Array<char> release(); Containers::Array<char> release();
private: private:
#ifndef DOXYGEN_GENERATING_OUTPUT
/* For custom deleter checks. Not done in the constructors here because
the restriction is pointless when used outside of plugin
implementations. */
/** @todo figure out a better way (return ImageData there instead?) */
friend Trade::AbstractImageConverter;
#endif
PixelStorage _storage; PixelStorage _storage;
PixelFormat _format; PixelFormat _format;
UnsignedInt _formatExtra; UnsignedInt _formatExtra;
@ -660,6 +673,14 @@ template<UnsignedInt dimensions> class CompressedImage {
Containers::Array<char> release(); Containers::Array<char> release();
private: private:
#ifndef DOXYGEN_GENERATING_OUTPUT
/* For custom deleter checks. Not done in the constructors here because
the restriction is pointless when used outside of plugin
implementations. */
/** @todo figure out a better way (return ImageData there instead?) */
friend Trade::AbstractImageConverter;
#endif
/* To be made public once block size and block data size are stored /* To be made public once block size and block data size are stored
together with the image */ together with the image */
explicit CompressedImage(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept; explicit CompressedImage(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data) noexcept;

16
src/Magnum/Trade/AbstractImageConverter.cpp

@ -86,7 +86,9 @@ Containers::Optional<Image2D> AbstractImageConverter::exportToImage(const ImageV
CORRADE_ASSERT(features() & Feature::ConvertImage, CORRADE_ASSERT(features() & Feature::ConvertImage,
"Trade::AbstractImageConverter::exportToImage(): feature not supported", {}); "Trade::AbstractImageConverter::exportToImage(): feature not supported", {});
return doExportToImage(image); Containers::Optional<Image2D> out = doExportToImage(image);
CORRADE_ASSERT(!out || !out->_data.deleter(), "Trade::AbstractImageConverter::exportToImage(): implementation is not allowed to use a custom Array deleter", {});
return out;
} }
Containers::Optional<Image2D> AbstractImageConverter::doExportToImage(const ImageView2D&) { Containers::Optional<Image2D> AbstractImageConverter::doExportToImage(const ImageView2D&) {
@ -97,7 +99,9 @@ Containers::Optional<CompressedImage2D> AbstractImageConverter::exportToCompress
CORRADE_ASSERT(features() & Feature::ConvertCompressedImage, CORRADE_ASSERT(features() & Feature::ConvertCompressedImage,
"Trade::AbstractImageConverter::exportToCompressedImage(): feature not supported", {}); "Trade::AbstractImageConverter::exportToCompressedImage(): feature not supported", {});
return doExportToCompressedImage(image); Containers::Optional<CompressedImage2D> out = doExportToCompressedImage(image);
CORRADE_ASSERT(!out || !out->_data.deleter(), "Trade::AbstractImageConverter::exportToCompressedImage(): implementation is not allowed to use a custom Array deleter", {});
return out;
} }
Containers::Optional<CompressedImage2D> AbstractImageConverter::doExportToCompressedImage(const ImageView2D&) { Containers::Optional<CompressedImage2D> AbstractImageConverter::doExportToCompressedImage(const ImageView2D&) {
@ -108,7 +112,9 @@ Containers::Array<char> AbstractImageConverter::exportToData(const ImageView2D&
CORRADE_ASSERT(features() & Feature::ConvertData, CORRADE_ASSERT(features() & Feature::ConvertData,
"Trade::AbstractImageConverter::exportToData(): feature not supported", nullptr); "Trade::AbstractImageConverter::exportToData(): feature not supported", nullptr);
return doExportToData(image); Containers::Array<char> out = doExportToData(image);
CORRADE_ASSERT(!out.deleter(), "Trade::AbstractImageConverter::exportToData(): implementation is not allowed to use a custom Array deleter", {});
return out;
} }
Containers::Array<char> AbstractImageConverter::doExportToData(const ImageView2D&) { Containers::Array<char> AbstractImageConverter::doExportToData(const ImageView2D&) {
@ -119,7 +125,9 @@ Containers::Array<char> AbstractImageConverter::exportToData(const CompressedIma
CORRADE_ASSERT(features() & Feature::ConvertCompressedData, CORRADE_ASSERT(features() & Feature::ConvertCompressedData,
"Trade::AbstractImageConverter::exportToData(): feature not supported", nullptr); "Trade::AbstractImageConverter::exportToData(): feature not supported", nullptr);
return doExportToData(image); Containers::Array<char> out = doExportToData(image);
CORRADE_ASSERT(!out.deleter(), "Trade::AbstractImageConverter::exportToData(): implementation is not allowed to use a custom Array deleter", {});
return out;
} }
Containers::Array<char> AbstractImageConverter::doExportToData(const CompressedImageView2D&) { Containers::Array<char> AbstractImageConverter::doExportToData(const CompressedImageView2D&) {

24
src/Magnum/Trade/AbstractImageConverter.h

@ -44,6 +44,17 @@ Provides functionality for converting images between various internal formats
or compressing them. See @ref plugins for more information and `*ImageConverter` or compressing them. See @ref plugins for more information and `*ImageConverter`
classes in @ref Trade namespace for available image converter plugins. classes in @ref Trade namespace for available image converter plugins.
@section Trade-AbstractImageConverter-data-dependency Data dependency
The instances returned from various functions *by design* have no dependency on
the importer instance and neither on the dynamic plugin module. In other words,
you don't need to keep the importer instance (or the plugin manager instance)
around in order to have the `*Data` instances valid. Moreover, all
@ref Corrade::Containers::Array instances returned through @ref Image,
@ref CompressedImage and others are only allowed to have default deleters ---
this is to avoid potential dangling function pointer calls when destructing
such instances after the plugin module has been unloaded.
@section Trade-AbstractImageConverter-subclassing Subclassing @section Trade-AbstractImageConverter-subclassing Subclassing
The plugin needs to implement the@ref doFeatures() function and one or more of The plugin needs to implement the@ref doFeatures() function and one or more of
@ -63,10 +74,15 @@ checked by the implementation:
- The function @ref doExportToData(const CompressedImageView2D&) is called - The function @ref doExportToData(const CompressedImageView2D&) is called
only if @ref Feature::ConvertCompressedData is supported. only if @ref Feature::ConvertCompressedData is supported.
@attention @ref Corrade::Containers::Array instances returned from the plugin @m_class{m-block m-warning}
should *not* use anything else than the default deleter, otherwise this can
cause dangling function pointer call on array destruction if the plugin @par Dangling function pointers on plugin unload
gets unloaded before the array is destroyed. As @ref Trade-AbstractImageConverter-data-dependency "mentioned above",
@ref Corrade::Containers::Array instances returned from plugin
implementations are not allowed to use anything else than the default
deleter, otherwise this could cause dangling function pointer call on array
destruction if the plugin gets unloaded before the array is destroyed. This
is asserted by the base implementation on return.
*/ */
class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::AbstractManagingPlugin<AbstractImageConverter> { class MAGNUM_TRADE_EXPORT AbstractImageConverter: public PluginManager::AbstractManagingPlugin<AbstractImageConverter> {
public: public:

16
src/Magnum/Trade/AbstractImporter.cpp

@ -265,7 +265,9 @@ std::string AbstractImporter::doAnimationName(UnsignedInt) { return {}; }
Containers::Optional<AnimationData> AbstractImporter::animation(const UnsignedInt id) { Containers::Optional<AnimationData> AbstractImporter::animation(const UnsignedInt id) {
CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {});
CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {});
return doAnimation(id); Containers::Optional<AnimationData> animation = doAnimation(id);
CORRADE_ASSERT(!animation || (!animation->_data.deleter() && !animation->_tracks.deleter()), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {});
return animation;
} }
Containers::Optional<AnimationData> AbstractImporter::doAnimation(UnsignedInt) { Containers::Optional<AnimationData> AbstractImporter::doAnimation(UnsignedInt) {
@ -553,7 +555,9 @@ std::string AbstractImporter::doImage1DName(UnsignedInt) { return {}; }
Containers::Optional<ImageData1D> AbstractImporter::image1D(const UnsignedInt id) { Containers::Optional<ImageData1D> AbstractImporter::image1D(const UnsignedInt id) {
CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1D(): no file opened", {}); CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1D(): no file opened", {});
CORRADE_ASSERT(id < doImage1DCount(), "Trade::AbstractImporter::image1D(): index" << id << "out of range for" << doImage1DCount() << "entries", {}); CORRADE_ASSERT(id < doImage1DCount(), "Trade::AbstractImporter::image1D(): index" << id << "out of range for" << doImage1DCount() << "entries", {});
return doImage1D(id); Containers::Optional<ImageData1D> image = doImage1D(id);
CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {});
return image;
} }
Containers::Optional<ImageData1D> AbstractImporter::doImage1D(UnsignedInt) { Containers::Optional<ImageData1D> AbstractImporter::doImage1D(UnsignedInt) {
@ -585,7 +589,9 @@ std::string AbstractImporter::doImage2DName(UnsignedInt) { return {}; }
Containers::Optional<ImageData2D> AbstractImporter::image2D(const UnsignedInt id) { Containers::Optional<ImageData2D> AbstractImporter::image2D(const UnsignedInt id) {
CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2D(): no file opened", {}); CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2D(): no file opened", {});
CORRADE_ASSERT(id < doImage2DCount(), "Trade::AbstractImporter::image2D(): index" << id << "out of range for" << doImage2DCount() << "entries", {}); CORRADE_ASSERT(id < doImage2DCount(), "Trade::AbstractImporter::image2D(): index" << id << "out of range for" << doImage2DCount() << "entries", {});
return doImage2D(id); Containers::Optional<ImageData2D> image = doImage2D(id);
CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {});
return image;
} }
Containers::Optional<ImageData2D> AbstractImporter::doImage2D(UnsignedInt) { Containers::Optional<ImageData2D> AbstractImporter::doImage2D(UnsignedInt) {
@ -617,7 +623,9 @@ std::string AbstractImporter::doImage3DName(UnsignedInt) { return {}; }
Containers::Optional<ImageData3D> AbstractImporter::image3D(const UnsignedInt id) { Containers::Optional<ImageData3D> AbstractImporter::image3D(const UnsignedInt id) {
CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3D(): no file opened", {}); CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3D(): no file opened", {});
CORRADE_ASSERT(id < doImage3DCount(), "Trade::AbstractImporter::image3D(): index" << id << "out of range for" << doImage3DCount() << "entries", {}); CORRADE_ASSERT(id < doImage3DCount(), "Trade::AbstractImporter::image3D(): index" << id << "out of range for" << doImage3DCount() << "entries", {});
return doImage3D(id); Containers::Optional<ImageData3D> image = doImage3D(id);
CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {});
return image;
} }
Containers::Optional<ImageData3D> AbstractImporter::doImage3D(UnsignedInt) { Containers::Optional<ImageData3D> AbstractImporter::doImage3D(UnsignedInt) {

40
src/Magnum/Trade/AbstractImporter.h

@ -145,6 +145,24 @@ Another option is making use of the @ref Containers::pointerCast() utility, but
note that in that case the original @ref Corrade::Containers::Pointer will be note that in that case the original @ref Corrade::Containers::Pointer will be
* *moved into* a new instance and that might not be desirable. * *moved into* a new instance and that might not be desirable.
@section Trade-AbstractImporter-data-dependency Data dependency
The `*Data` instances returned from various functions *by design* have no
dependency on the importer instance and neither on the dynamic plugin module.
In other words, you don't need to keep the importer instance (or the plugin
manager instance) around in order to have the `*Data` instances valid.
Moreover, all @ref Corrade::Containers::Array instances returned through
@ref ImageData, @ref AnimationData and others are only allowed to have default
deleters --- this is to avoid potential dangling function pointer calls when
destructing such instances after the plugin module has been unloaded.
The only exception are various `importerState()` functions
@ref Trade-AbstractImporter-usage-state "described above", but in that case the
relation is *weak* --- these are valid only as long as the currently opened
file is kept open. If the file gets closed or the importer instance deleted,
the state pointers become dangling, and that's fine as long as you don't access
them.
@section Trade-AbstractImporter-subclassing Subclassing @section Trade-AbstractImporter-subclassing Subclassing
The plugin needs to implement the @ref doFeatures(), @ref doIsOpened() The plugin needs to implement the @ref doFeatures(), @ref doIsOpened()
@ -183,19 +201,25 @@ checked by the implementation:
- All `do*()` implementations taking data ID as parameter are called only if - All `do*()` implementations taking data ID as parameter are called only if
the ID is from valid range. the ID is from valid range.
@attention @m_class{m-block m-warning}
@ref Corrade::Containers::Array instances returned from the plugin
should *not* use anything else than the default deleter, otherwise this can @par Dangling function pointers on plugin unload
cause dangling function pointer call on array destruction if the plugin As @ref Trade-AbstractImporter-data-dependency "mentioned above",
gets unloaded before the array is destroyed. @ref Corrade::Containers::Array instances returned from plugin
@attention implementations are not allowed to use anything else than the default
deleter, otherwise this could cause dangling function pointer call on array
destruction if the plugin gets unloaded before the array is destroyed. This
is asserted by the base implementation on return.
@par
Similarly for interpolator functions passed through Similarly for interpolator functions passed through
@ref Animation::TrackView instances to @ref AnimationData --- to avoid @ref Animation::TrackView instances to @ref AnimationData --- to avoid
dangling pointers, be sure to always include an interpolator returned from dangling pointers, be sure to always include an interpolator returned from
@ref animationInterpolatorFor(), which guarantees the function is *not* @ref animationInterpolatorFor(), which guarantees the function is *not*
instantiated in the plugin binary. Avoid using instantiated in the plugin binary. Avoid using
@ref Animation::interpolatorFor() (or indirectly it by specifying @ref Animation::interpolatorFor() (or indirectly using it by specifying
just @ref Animation::Interpolation), as it doesn't have such guarantee. just @ref Animation::Interpolation), as it doesn't have such a guarantee.
Note that unlike with array instances, the base implementation can't easily
check for this.
*/ */
class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagingPlugin<AbstractImporter> { class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagingPlugin<AbstractImporter> {
public: public:

5
src/Magnum/Trade/AnimationData.h

@ -433,6 +433,11 @@ class MAGNUM_TRADE_EXPORT AnimationData {
const void* importerState() const { return _importerState; } const void* importerState() const { return _importerState; }
private: private:
/* For custom deleter checks. Not done in the constructors here because
the restriction is pointless when used outside of plugin
implementations. */
friend AbstractImporter;
Range1D _duration; Range1D _duration;
Containers::Array<char> _data; Containers::Array<char> _data;
Containers::Array<AnimationTrackData> _tracks; Containers::Array<AnimationTrackData> _tracks;

6
src/Magnum/Trade/ImageData.h

@ -33,6 +33,7 @@
#include "Magnum/DimensionTraits.h" #include "Magnum/DimensionTraits.h"
#include "Magnum/PixelStorage.h" #include "Magnum/PixelStorage.h"
#include "Magnum/Trade/Trade.h"
#include "Magnum/Trade/visibility.h" #include "Magnum/Trade/visibility.h"
namespace Magnum { namespace Trade { namespace Magnum { namespace Trade {
@ -427,6 +428,11 @@ template<UnsignedInt dimensions> class ImageData {
const void* importerState() const { return _importerState; } const void* importerState() const { return _importerState; }
private: private:
/* For custom deleter checks. Not done in the constructors here because
the restriction is pointless when used outside of plugin
implementations. */
friend AbstractImporter;
explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data, const void* importerState = nullptr) noexcept; explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor<dimensions, Int>& size, Containers::Array<char>&& data, const void* importerState = nullptr) noexcept;
bool _compressed; bool _compressed;

86
src/Magnum/Trade/Test/AbstractImageConverterTest.cpp

@ -52,18 +52,22 @@ class AbstractImageConverterTest: public TestSuite::Tester {
void exportToImage(); void exportToImage();
void exportToImageNotSupported(); void exportToImageNotSupported();
void exportToImageNotImplemented(); void exportToImageNotImplemented();
void exportToImageCustomDeleter();
void exportToCompressedImage(); void exportToCompressedImage();
void exportToCompressedImageNotSupported(); void exportToCompressedImageNotSupported();
void exportToCompressedImageNotImplemented(); void exportToCompressedImageNotImplemented();
void exportToCompressedImageCustomDeleter();
void exportToData(); void exportToData();
void exportToDataNotSupported(); void exportToDataNotSupported();
void exportToDataNotImplemented(); void exportToDataNotImplemented();
void exportToDataCustomDeleter();
void exportCompressedToData(); void exportCompressedToData();
void exportCompressedToDataNotSupported(); void exportCompressedToDataNotSupported();
void exportCompressedToDataNotImplemented(); void exportCompressedToDataNotImplemented();
void exportCompressedToDataCustomDeleter();
void exportImageDataToData(); void exportImageDataToData();
@ -92,18 +96,22 @@ AbstractImageConverterTest::AbstractImageConverterTest() {
&AbstractImageConverterTest::exportToImage, &AbstractImageConverterTest::exportToImage,
&AbstractImageConverterTest::exportToImageNotSupported, &AbstractImageConverterTest::exportToImageNotSupported,
&AbstractImageConverterTest::exportToImageNotImplemented, &AbstractImageConverterTest::exportToImageNotImplemented,
&AbstractImageConverterTest::exportToImageCustomDeleter,
&AbstractImageConverterTest::exportToCompressedImage, &AbstractImageConverterTest::exportToCompressedImage,
&AbstractImageConverterTest::exportToCompressedImageNotSupported, &AbstractImageConverterTest::exportToCompressedImageNotSupported,
&AbstractImageConverterTest::exportToCompressedImageNotImplemented, &AbstractImageConverterTest::exportToCompressedImageNotImplemented,
&AbstractImageConverterTest::exportToCompressedImageCustomDeleter,
&AbstractImageConverterTest::exportToData, &AbstractImageConverterTest::exportToData,
&AbstractImageConverterTest::exportToDataNotSupported, &AbstractImageConverterTest::exportToDataNotSupported,
&AbstractImageConverterTest::exportToDataNotImplemented, &AbstractImageConverterTest::exportToDataNotImplemented,
&AbstractImageConverterTest::exportToDataCustomDeleter,
&AbstractImageConverterTest::exportCompressedToData, &AbstractImageConverterTest::exportCompressedToData,
&AbstractImageConverterTest::exportCompressedToDataNotSupported, &AbstractImageConverterTest::exportCompressedToDataNotSupported,
&AbstractImageConverterTest::exportCompressedToDataNotImplemented, &AbstractImageConverterTest::exportCompressedToDataNotImplemented,
&AbstractImageConverterTest::exportCompressedToDataCustomDeleter,
&AbstractImageConverterTest::exportImageDataToData, &AbstractImageConverterTest::exportImageDataToData,
@ -192,6 +200,22 @@ void AbstractImageConverterTest::exportToImageNotImplemented() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToImage(): feature advertised but not implemented\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToImage(): feature advertised but not implemented\n");
} }
void AbstractImageConverterTest::exportToImageCustomDeleter() {
class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertImage; }
Containers::Optional<Image2D> doExportToImage(const ImageView2D&) override {
return Image2D{PixelFormat::RGBA8Unorm, {}, Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}};
}
};
std::ostringstream out;
Error redirectError{&out};
Converter converter;
converter.exportToImage(ImageView2D{PixelFormat::R8Unorm, {}});
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToImage(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImageConverterTest::exportToCompressedImage() { void AbstractImageConverterTest::exportToCompressedImage() {
class Converter: public AbstractImageConverter { class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertCompressedImage; } Features doFeatures() const override { return Feature::ConvertCompressedImage; }
@ -233,6 +257,22 @@ void AbstractImageConverterTest::exportToCompressedImageNotImplemented() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToCompressedImage(): feature advertised but not implemented\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToCompressedImage(): feature advertised but not implemented\n");
} }
void AbstractImageConverterTest::exportToCompressedImageCustomDeleter() {
class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertCompressedImage; }
Containers::Optional<CompressedImage2D> doExportToCompressedImage(const ImageView2D&) override {
return CompressedImage2D{CompressedPixelFormat::Bc1RGBAUnorm, {}, Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}};
}
};
std::ostringstream out;
Error redirectError{&out};
Converter converter;
converter.exportToCompressedImage(ImageView2D{PixelFormat::R8Unorm, {}});
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToCompressedImage(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImageConverterTest::exportToData() { void AbstractImageConverterTest::exportToData() {
class Converter: public AbstractImageConverter { class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertData; } Features doFeatures() const override { return Feature::ConvertData; }
@ -272,6 +312,22 @@ void AbstractImageConverterTest::exportToDataNotImplemented() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): feature advertised but not implemented\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): feature advertised but not implemented\n");
} }
void AbstractImageConverterTest::exportToDataCustomDeleter() {
class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertData; }
Containers::Array<char> doExportToData(const ImageView2D&) override {
return Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}};
}
};
std::ostringstream out;
Error redirectError{&out};
Converter converter;
converter.exportToData(ImageView2D{PixelFormat::RGBA8Unorm, {}});
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImageConverterTest::exportCompressedToData() { void AbstractImageConverterTest::exportCompressedToData() {
class Converter: public AbstractImageConverter { class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertCompressedData; } Features doFeatures() const override { return Feature::ConvertCompressedData; }
@ -311,16 +367,42 @@ void AbstractImageConverterTest::exportCompressedToDataNotImplemented() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): feature advertised but not implemented\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): feature advertised but not implemented\n");
} }
void AbstractImageConverterTest::exportCompressedToDataCustomDeleter() {
class Converter: public AbstractImageConverter {
Features doFeatures() const override { return Feature::ConvertCompressedData; }
Containers::Array<char> doExportToData(const CompressedImageView2D&) override {
return Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}};
}
};
std::ostringstream out;
Error redirectError{&out};
Converter converter;
converter.exportToData(CompressedImageView2D{CompressedPixelFormat::Bc1RGBAUnorm, {}});
CORRADE_COMPARE(out.str(), "Trade::AbstractImageConverter::exportToData(): implementation is not allowed to use a custom Array deleter\n");
}
class ImageDataExporter: public Trade::AbstractImageConverter { class ImageDataExporter: public Trade::AbstractImageConverter {
private: private:
Features doFeatures() const override { return Feature::ConvertData|Feature::ConvertCompressedData; } Features doFeatures() const override { return Feature::ConvertData|Feature::ConvertCompressedData; }
Containers::Array<char> doExportToData(const ImageView2D&) override { Containers::Array<char> doExportToData(const ImageView2D&) override {
return Containers::Array<char>{Containers::InPlaceInit, {'B'}}; /* DirectInit / InPlaceInit is unfortunately causing a custom
deleter right now */
/** @todo clean up when fixed */
Containers::Array<char> out{1};
out[0] = 'B';
return out;
}; };
Containers::Array<char> doExportToData(const CompressedImageView2D&) override { Containers::Array<char> doExportToData(const CompressedImageView2D&) override {
return Containers::Array<char>{Containers::InPlaceInit, {'C'}}; /* DirectInit / InPlaceInit is unfortunately causing a custom
deleter right now */
/** @todo clean up when fixed */
Containers::Array<char> out{1};
out[0] = 'C';
return out;
}; };
}; };

105
src/Magnum/Trade/Test/AbstractImporterTest.cpp

@ -105,6 +105,8 @@ struct AbstractImporterTest: TestSuite::Tester {
void animationNotImplemented(); void animationNotImplemented();
void animationNoFile(); void animationNoFile();
void animationOutOfRange(); void animationOutOfRange();
void animationCustomDataDeleter();
void animationCustomTrackDeleter();
void light(); void light();
void lightCountNotImplemented(); void lightCountNotImplemented();
@ -213,6 +215,7 @@ struct AbstractImporterTest: TestSuite::Tester {
void image1DNotImplemented(); void image1DNotImplemented();
void image1DNoFile(); void image1DNoFile();
void image1DOutOfRange(); void image1DOutOfRange();
void image1DCustomDeleter();
void image2D(); void image2D();
void image2DCountNotImplemented(); void image2DCountNotImplemented();
@ -225,6 +228,7 @@ struct AbstractImporterTest: TestSuite::Tester {
void image2DNotImplemented(); void image2DNotImplemented();
void image2DNoFile(); void image2DNoFile();
void image2DOutOfRange(); void image2DOutOfRange();
void image2DCustomDeleter();
void image3D(); void image3D();
void image3DCountNotImplemented(); void image3DCountNotImplemented();
@ -237,6 +241,7 @@ struct AbstractImporterTest: TestSuite::Tester {
void image3DNotImplemented(); void image3DNotImplemented();
void image3DNoFile(); void image3DNoFile();
void image3DOutOfRange(); void image3DOutOfRange();
void image3DCustomDeleter();
void importerState(); void importerState();
void importerStateNotImplemented(); void importerStateNotImplemented();
@ -300,6 +305,8 @@ AbstractImporterTest::AbstractImporterTest() {
&AbstractImporterTest::animationNotImplemented, &AbstractImporterTest::animationNotImplemented,
&AbstractImporterTest::animationNoFile, &AbstractImporterTest::animationNoFile,
&AbstractImporterTest::animationOutOfRange, &AbstractImporterTest::animationOutOfRange,
&AbstractImporterTest::animationCustomDataDeleter,
&AbstractImporterTest::animationCustomTrackDeleter,
&AbstractImporterTest::light, &AbstractImporterTest::light,
&AbstractImporterTest::lightCountNotImplemented, &AbstractImporterTest::lightCountNotImplemented,
@ -408,6 +415,7 @@ AbstractImporterTest::AbstractImporterTest() {
&AbstractImporterTest::image1DNotImplemented, &AbstractImporterTest::image1DNotImplemented,
&AbstractImporterTest::image1DNoFile, &AbstractImporterTest::image1DNoFile,
&AbstractImporterTest::image1DOutOfRange, &AbstractImporterTest::image1DOutOfRange,
&AbstractImporterTest::image1DCustomDeleter,
&AbstractImporterTest::image2D, &AbstractImporterTest::image2D,
&AbstractImporterTest::image2DCountNotImplemented, &AbstractImporterTest::image2DCountNotImplemented,
@ -420,6 +428,7 @@ AbstractImporterTest::AbstractImporterTest() {
&AbstractImporterTest::image2DNotImplemented, &AbstractImporterTest::image2DNotImplemented,
&AbstractImporterTest::image2DNoFile, &AbstractImporterTest::image2DNoFile,
&AbstractImporterTest::image2DOutOfRange, &AbstractImporterTest::image2DOutOfRange,
&AbstractImporterTest::image2DCustomDeleter,
&AbstractImporterTest::image3D, &AbstractImporterTest::image3D,
&AbstractImporterTest::image3DCountNotImplemented, &AbstractImporterTest::image3DCountNotImplemented,
@ -432,6 +441,7 @@ AbstractImporterTest::AbstractImporterTest() {
&AbstractImporterTest::image3DNotImplemented, &AbstractImporterTest::image3DNotImplemented,
&AbstractImporterTest::image3DNoFile, &AbstractImporterTest::image3DNoFile,
&AbstractImporterTest::image3DOutOfRange, &AbstractImporterTest::image3DOutOfRange,
&AbstractImporterTest::image3DCustomDeleter,
&AbstractImporterTest::importerState, &AbstractImporterTest::importerState,
&AbstractImporterTest::importerStateNotImplemented, &AbstractImporterTest::importerStateNotImplemented,
@ -1295,6 +1305,44 @@ void AbstractImporterTest::animationOutOfRange() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): index 8 out of range for 8 entries\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): index 8 out of range for 8 entries\n");
} }
void AbstractImporterTest::animationCustomDataDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doAnimationCount() const override { return 1; }
Containers::Optional<AnimationData> doAnimation(UnsignedInt) override {
return AnimationData{Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}, nullptr};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.animation(0);
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::animationCustomTrackDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doAnimationCount() const override { return 1; }
Containers::Optional<AnimationData> doAnimation(UnsignedInt) override {
return AnimationData{nullptr, Containers::Array<AnimationTrackData>{nullptr, 0, [](AnimationTrackData*, std::size_t) {}}};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.animation(0);
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::light() { void AbstractImporterTest::light() {
struct: AbstractImporter { struct: AbstractImporter {
Features doFeatures() const override { return {}; } Features doFeatures() const override { return {}; }
@ -2813,6 +2861,25 @@ void AbstractImporterTest::image1DOutOfRange() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): index 8 out of range for 8 entries\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): index 8 out of range for 8 entries\n");
} }
void AbstractImporterTest::image1DCustomDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doImage1DCount() const override { return 1; }
Containers::Optional<ImageData1D> doImage1D(UnsignedInt) override {
return ImageData1D{PixelFormat::RGBA8Unorm, {}, Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.image1D(0);
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::image2D() { void AbstractImporterTest::image2D() {
struct: AbstractImporter { struct: AbstractImporter {
Features doFeatures() const override { return {}; } Features doFeatures() const override { return {}; }
@ -2979,6 +3046,25 @@ void AbstractImporterTest::image2DOutOfRange() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): index 8 out of range for 8 entries\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): index 8 out of range for 8 entries\n");
} }
void AbstractImporterTest::image2DCustomDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doImage2DCount() const override { return 1; }
Containers::Optional<ImageData2D> doImage2D(UnsignedInt) override {
return ImageData2D{PixelFormat::RGBA8Unorm, {}, Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.image2D(0);
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::image3D() { void AbstractImporterTest::image3D() {
struct: AbstractImporter { struct: AbstractImporter {
Features doFeatures() const override { return {}; } Features doFeatures() const override { return {}; }
@ -3145,6 +3231,25 @@ void AbstractImporterTest::image3DOutOfRange() {
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): index 8 out of range for 8 entries\n"); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): index 8 out of range for 8 entries\n");
} }
void AbstractImporterTest::image3DCustomDeleter() {
struct: AbstractImporter {
Features doFeatures() const override { return {}; }
bool doIsOpened() const override { return true; }
void doClose() override {}
UnsignedInt doImage3DCount() const override { return 1; }
Containers::Optional<ImageData3D> doImage3D(UnsignedInt) override {
return ImageData3D{PixelFormat::RGBA8Unorm, {}, Containers::Array<char>{nullptr, 0, [](char*, std::size_t) {}}};
}
} importer;
std::ostringstream out;
Error redirectError{&out};
importer.image3D(0);
CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n");
}
void AbstractImporterTest::importerState() { void AbstractImporterTest::importerState() {
struct: AbstractImporter { struct: AbstractImporter {
Features doFeatures() const override { return {}; } Features doFeatures() const override { return {}; }

Loading…
Cancel
Save