thindx 0.0.0-unsound.5

Thin DirectX wrappers
Documentation
use crate::*;
use crate::d3d::*;

use winapi::Interface;

use std::ptr::*;



/// <h1 id="reflection" class="section-header"><a href="#reflection">Bytecode Reflection</a></h1>
impl Compiler {
    /// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dreflect)\]
    /// D3DReflect
    ///
    /// Gets a pointer to a reflection interface.
    ///
    /// ### Arguments
    /// *   `src_data`  - A compiled HLSL shader.
    ///
    /// ### Errors
    /// *   [THINERR::MISSING_DLL_EXPORT]   - `d3dcompiler_4?.dll` and earlier
    /// *   [E::INVALIDARG]                 - On invalid `I`
    /// *   [D3DERR::INVALIDCALL]           - On invalid `src_data`
    /// *   [D3DERR::INVALIDCALL]           - On shaders not compatible with `I` (e.g. Shader Model 3 shaders with <code>I: [d3d11::ShaderReflection]</code>
    ///
    /// ### Example
    /// ```rust
    /// # use thindx::{*, d3d::*}; let d3dc = Compiler::load_system(47).unwrap();
    /// let shader = d3dc.compile_from_file(
    ///     r"test\data\basic.hlsl", None, None, "ps_main", "ps_4_0",
    ///     Compile::Debug, CompileEffect::None
    /// ).unwrap();
    ///
    /// // Should succeed:
    /// let r = d3dc.reflect::<d3d11::ShaderReflection>(&shader).unwrap();
    ///
    /// // Invalid interface:
    /// let r = d3dc.reflect::<Unknown>(&shader);
    /// assert_eq!(Some(E::INVALIDARG), r.err().map(|e| e.kind()));
    ///
    /// // Invalid `src_data`:
    /// let r = d3dc.reflect::<d3d11::ShaderReflection>(unsafe { Bytecode::from_unchecked(&[]) });
    /// assert_eq!(Some(D3DERR::INVALIDCALL), r.err().map(|e| e.kind()));
    /// ```
    ///
    /// ### See Also
    /// *   [d3d11::ShaderReflection] for a more complete example
    ///
    /// ### Remarks
    /// *   This was introduced by d3dcompiler_40.dll, and is unavailable in earlier versions.
    pub fn reflect<I: Raw>(&self, src_data: &Bytecode) -> Result<I, MethodError> where I::Raw : Interface {
        let f = self.D3DReflect.ok_or(MethodError("D3DReflect", THINERR::MISSING_DLL_EXPORT))?;
        let src_data = src_data.as_bytes();

        // SAFETY: ❌ needs fuzz testing against ~4GB `data` to attempt to induce alloc overflow bugs
        //  * `f`           ✔️ should be valid/sound like all `self.*`
        //  * `src_data`    ❌ needs fuzz testing against ~4GB data to attempt to induce alloc overflow bugs
        //  * `src_data`    ✔️ should be valid bytecode as implied by [`Bytecode`]
        //  * `uuid`        ✔️ is a simple GUID input
        //  * `reflector`   ✔️ is a simple out-param
        let mut reflector = null_mut();
        let hr = unsafe { f(src_data.as_ptr().cast(), src_data.len(), &I::Raw::uuidof(), &mut reflector) };
        MethodError::check("D3DReflect", hr)?;

        // SAFETY: ✔️ `reflector` should be null (from_raw panics) or a valid non-dangling I::Raw (from_raw takes ownership)
        Ok(unsafe { I::from_raw(reflector.cast()) })
    }

    /// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dreflect)\]
    /// D3DReflect
    ///
    /// Gets a pointer to a reflection interface.
    ///
    /// ### Arguments
    /// *   `src_data`  - A compiled HLSL shader.
    ///
    /// ### Errors
    /// *   [THINERR::MISSING_DLL_EXPORT]   - `d3dcompiler_4?.dll` and earlier
    /// *   [D3DERR::INVALIDCALL]           - On invalid `src_data`
    /// *   [D3DERR::INVALIDCALL]           - On shaders not compatible with [`d3d11::ShaderReflection`] (e.g. Shader Model 3.0 and earlier)
    ///
    /// ### Example
    /// ```rust
    /// # use thindx::{*, d3d::*}; let d3dc = Compiler::load_system(47).unwrap();
    /// let shader = d3dc.compile_from_file(
    ///     r"test\data\basic.hlsl", None, None, "ps_main", "ps_4_0",
    ///     Compile::Debug, CompileEffect::None
    /// ).unwrap();
    ///
    /// // Should succeed:
    /// let r = d3dc.reflect11(&shader).unwrap();
    ///
    /// // Invalid `src_data`:
    /// let r = d3dc.reflect11(unsafe { Bytecode::from_unchecked(&[]) });
    /// assert_eq!(Some(D3DERR::INVALIDCALL), r.err().map(|e| e.kind()));
    /// ```
    ///
    /// ### See Also
    /// *   [d3d11::ShaderReflection] for a more complete example
    ///
    /// ### Remarks
    /// *   This was introduced by d3dcompiler_40.dll, and is unavailable in earlier versions.
    pub fn reflect11(&self, src_data: &Bytecode) -> Result<d3d11::ShaderReflection, MethodError> {
        self.reflect(src_data)
    }

    /// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dreflectlibrary)\]
    /// D3DReflectLibrary
    ///
    /// Creates a library-reflection interface from source data that contains an HLSL library of functions.
    ///
    /// > **Note:** This function is part of the HLSL shader linking technology that you can use on all Direct3D 11
    /// > platforms to create precompiled HLSL functions, package them into libraries, and link them into full shaders
    /// > at run time.
    ///
    /// ### Arguments
    /// *   `src_data`  - An HLSL library of functions.
    ///
    /// ### Errors
    /// *   [THINERR::MISSING_DLL_EXPORT]   - `d3dcompiler_4?.dll` and earlier
    /// *   [E::INVALIDARG]                 - On invalid `I`
    /// *   [D3DERR::INVALIDCALL]           - On invalid `src_data`
    /// *   [D3DERR::INVALIDCALL]           - On shaders not compatible with `I` (e.g. Shader Model 3 shaders with <code>I: [d3d11::ShaderReflection]</code>
    ///
    /// ### Example
    /// ```rust
    /// # use thindx::{*, d3d::*}; let d3dc = Compiler::load_system(47).unwrap();
    /// let shader = d3dc.compile_from_file(
    ///     r"test\data\library.hlsl", None, None, (), "lib_5_0",
    ///     Compile::Debug, CompileEffect::None
    /// ).unwrap();
    ///
    /// // Should succeed:
    /// let r = d3dc.reflect_library::<d3d11::LibraryReflection>(&shader).unwrap();
    ///
    /// // Invalid interface:
    /// let r = d3dc.reflect_library::<Unknown>(&shader);
    /// assert_eq!(Some(E::INVALIDARG), r.err().map(|e| e.kind()));
    ///
    /// // Invalid `src_data`:
    /// let r = d3dc.reflect_library::<d3d11::LibraryReflection>(unsafe { Bytecode::from_unchecked(&[]) });
    /// assert_eq!(Some(D3DERR::INVALIDCALL), r.err().map(|e| e.kind()));
    /// ```
    ///
    /// ### See Also
    /// *   [d3d11::LibraryReflection] for a more complete example
    // #[requires(d3dcompiler=47)] // ?
    pub fn reflect_library<I: Raw>(&self, src_data: &Bytecode) -> Result<I, MethodError> where I::Raw : Interface {
        let f = self.D3DReflectLibrary.ok_or(MethodError("D3DReflectLibrary", THINERR::MISSING_DLL_EXPORT))?;
        let src_data = src_data.as_bytes();

        // SAFETY: ❌ needs fuzz testing against ~4GB `data` to attempt to induce alloc overflow bugs
        //  * `f`           ✔️ should be valid/sound like all `self.*`
        //  * `src_data`    ❌ needs fuzz testing against ~4GB data to attempt to induce alloc overflow bugs
        //  * `src_data`    ✔️ should be valid bytecode as implied by [`Bytecode`]
        //  * `uuid`        ✔️ is a simple GUID input
        //  * `reflector`   ✔️ is a simple out-param
        let mut reflector = null_mut();
        let hr = unsafe { f(src_data.as_ptr().cast(), src_data.len(), &I::Raw::uuidof(), &mut reflector) };
        MethodError::check("D3DReflectLibrary", hr)?;

        // SAFETY: ✔️ `reflector` should be null (from_raw panics) or a valid non-dangling I::Raw (from_raw takes ownership)
        Ok(unsafe { I::from_raw(reflector.cast()) })
    }

    /// \[[docs.microsoft.com](https://docs.microsoft.com/en-us/windows/win32/api/d3dcompiler/nf-d3dcompiler-d3dreflectlibrary)\]
    /// D3DReflectLibrary
    ///
    /// Creates a library-reflection interface from source data that contains an HLSL library of functions.
    ///
    /// > **Note:** This function is part of the HLSL shader linking technology that you can use on all Direct3D 11
    /// > platforms to create precompiled HLSL functions, package them into libraries, and link them into full shaders
    /// > at run time.
    ///
    /// ### Arguments
    /// *   `src_data`  - An HLSL library of functions.
    ///
    /// ### Errors
    /// *   [THINERR::MISSING_DLL_EXPORT]   - `d3dcompiler_4?.dll` and earlier
    /// *   [D3DERR::INVALIDCALL]           - On invalid `src_data`
    /// *   [D3DERR::INVALIDCALL]           - On shaders not compatible with [`d3d11::LibraryReflection`] (e.g. Shader Model 3.0 and earlier)
    ///
    /// ### Example
    /// ```rust
    /// # use thindx::{*, d3d::*}; let d3dc = Compiler::load_system(47).unwrap();
    /// let shader = d3dc.compile_from_file(
    ///     r"test\data\library.hlsl", None, None, (), "lib_5_0",
    ///     Compile::Debug, CompileEffect::None
    /// ).unwrap();
    ///
    /// // Should succeed:
    /// let r = d3dc.reflect_library_11(&shader).unwrap();
    ///
    /// // Invalid `src_data`:
    /// let r = d3dc.reflect_library_11(unsafe { Bytecode::from_unchecked(&[]) });
    /// assert_eq!(Some(D3DERR::INVALIDCALL), r.err().map(|e| e.kind()));
    /// ```
    ///
    /// ### See Also
    /// *   [d3d11::LibraryReflection] for a more complete example
    // #[requires(d3dcompiler=47)] // ?
    pub fn reflect_library_11(&self, src_data: &Bytecode) -> Result<d3d11::LibraryReflection, MethodError> {
        self.reflect_library(src_data)
    }
}

#[test] fn library_reflection() {
    let d3dc = Compiler::load_system(47).unwrap();

    let shader = d3dc.compile_from_file(
        r"test\data\library.hlsl", None, None, (), "lib_5_0",
        Compile::Debug, CompileEffect::None
    ).unwrap();

    let r : d3d11::LibraryReflection = d3dc.reflect_library(&shader).unwrap();
    let desc = r.get_desc().unwrap();
    assert!(desc.function_count > 0);

    let mut found_xyz1 = false;
    for i in 0..desc.function_count {
        let f : d3d11::FunctionReflection = r.get_function_by_index(i);
        let desc = f.get_desc().unwrap();
        if desc.name.to_bytes() == b"xyz1" {
            found_xyz1 = true;
            assert_eq!(desc.function_parameter_count,   1   );
            assert_eq!(desc.has_return,                 true);
        }
    }
    assert!(found_xyz1);

    // Reflection likes to use the null object pattern for functions returning interface pointers.
    // Most out-of-bounds accesses result in a reused "null" object that E::FAIL on most calls.

    let invalid1 = r.get_function_by_index(desc.function_count);
    let invalid2 = r.get_function_by_index(!0);
    assert_eq!(invalid1.as_raw(), invalid2.as_raw()); // same object

    let valid = r.get_function_by_index(0);
    assert_ne!(valid.as_raw(), invalid1.as_raw()); // valid is a different object
    assert_ne!(valid.as_raw(), invalid2.as_raw()); // valid is a different object

    for invalid_index in [
        1000, 10000,
        !0/2-100, !0/2-10, !0/2-4, !0/2, !0/2+1, !0/2+4, !0/2+100,
        !0-100, !0-10, !0-4, !0-1, !0,
    ].iter().copied() {
        println!("testing get_function_by_index({})", invalid_index);
        let f = r.get_function_by_index(invalid_index);
        assert_eq!(Some(E::FAIL), f.get_desc().err().map(|e| e.kind()));

        let cb = f.get_constant_buffer_by_name("SomeNonexistantCBuffer");
        assert_eq!(Some(E::FAIL), cb.get_desc().err().map(|e| e.kind()));

        for invalid_index in [
            0, 1, 4, 10, 100, 1000, 100000,  100000,
            !0/2-100, !0/2-10, !0/2-4, !0/2, !0/2+1, !0/2+4, !0/2+100,
            !0-100, !0-10, !0-4, !0-1, !0,
        ].iter().copied() {
            let cb = f.get_constant_buffer_by_index(invalid_index);
            assert_eq!(Some(E::FAIL), cb.get_desc().err().map(|e| e.kind()));

            let param = f.get_function_parameter(invalid_index as _);
            assert_eq!(Some(E::FAIL), param.get_desc().err().map(|e| e.kind()));
        }
    }

    // TODO: full test coverage of all methods
}