Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ option(MATERIALX_BUILD_OIIO "Build OpenImageIO support for MaterialXRender." OFF
option(MATERIALX_BUILD_OCIO "Build OpenColorIO support for shader generators." OFF)
option(MATERIALX_BUILD_TESTS "Build unit tests." OFF)
option(MATERIALX_BUILD_BENCHMARK_TESTS "Build benchmark tests." OFF)
option(MATERIALX_BUILD_FUZZ_TESTS "Build Google FuzzTest fuzz tests for the XML I/O layer." OFF)
option(MATERIALX_BUILD_OSOS "Build OSL .oso's of standard library shaders for the OSL Network generator" OFF)
option(MATERIALX_BUILD_PERFETTO_TRACING "Build with Perfetto tracing support for performance analysis." OFF)

Expand Down Expand Up @@ -370,6 +371,14 @@ else()
add_compile_options(${DYNAMIC_ANALYSIS_OPTIONS})
add_link_options(${DYNAMIC_ANALYSIS_OPTIONS})
endif()
if(MATERIALX_BUILD_FUZZ_TESTS AND FUZZTEST_FUZZING_MODE)
# Add SanitizerCoverage instrumentation to all MaterialX libraries.
# -fsanitize=fuzzer-no-link injects edge counters and cmp tables without
# linking any fuzzer runtime — FuzzTest's own engine reads those counters
# to guide mutation.
add_compile_options(-fsanitize=fuzzer-no-link)
add_link_options(-fsanitize=fuzzer-no-link)
endif()
if(MATERIALX_BUILD_JS)
if (CMAKE_BUILD_TYPE MATCHES Debug)
add_compile_options(-fexceptions)
Expand Down Expand Up @@ -596,6 +605,29 @@ if(MATERIALX_BUILD_TESTS)
add_subdirectory(source/MaterialXTest)
endif()

# Add Google FuzzTest subdirectory
if(MATERIALX_BUILD_FUZZ_TESTS)
include(FetchContent)
FetchContent_Declare(
fuzztest
GIT_REPOSITORY https://github.com/google/fuzztest.git
GIT_TAG main
SYSTEM
)

# CMake propagates the current directory's COMPILE_OPTIONS into every
# subdirectory added from this point. Temporarily clear them before fetching
# FuzzTest so that FuzzTest, abseil, re2, and googletest compile with their
# own warning policies rather than MaterialX's -Wshadow/-Wunused-parameter/etc.
# Restore afterwards so source/MaterialXFuzz still gets the full set.
get_directory_property(_mx_saved_compile_options COMPILE_OPTIONS)
set_directory_properties(PROPERTIES COMPILE_OPTIONS "")
FetchContent_MakeAvailable(fuzztest)
set_directory_properties(PROPERTIES COMPILE_OPTIONS "${_mx_saved_compile_options}")

add_subdirectory(source/MaterialXFuzz)
endif()

if (MATERIALX_BUILD_DOCS)
add_subdirectory(documents)
endif()
Expand Down
38 changes: 38 additions & 0 deletions documents/DeveloperGuide/MainPage.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,44 @@ Select the `MATERIALX_BUILD_VIEWER` option to build the MaterialX Viewer. Insta

To generate HTML documentation for the MaterialX C++ API, make sure a version of [Doxygen](https://www.doxygen.org/) is on your path, and select the advanced option `MATERIALX_BUILD_DOCS` in CMake. This option will add a target named `MaterialXDocs` to your project, which can be built as an independent step from your development environment.

### Building Fuzz Tests

Select the `MATERIALX_BUILD_FUZZ_TESTS` option to build the [Google FuzzTest](https://github.com/google/fuzztest) fuzz target for the XML I/O layer. The fuzz target is located in `source/MaterialXFuzz/` and is fetched automatically via CMake's FetchContent.

**Unit-test mode** (any compiler, no special toolchain required):

```sh
cmake -B build -DMATERIALX_BUILD_FUZZ_TESTS=ON
cmake --build build --target MaterialXFuzz
./build/bin/MaterialXFuzz
```

In this mode the fuzz target runs as a standard GoogleTest case.

**Coverage-guided fuzzing mode** (requires Clang and libFuzzer):

```sh
cmake -B build \
-DMATERIALX_BUILD_FUZZ_TESTS=ON \
-DFUZZTEST_FUZZING_MODE=ON \
-DMATERIALX_DYNAMIC_ANALYSIS=ON \
-DCMAKE_C_COMPILER=clang \
-DCMAKE_CXX_COMPILER=clang++
cmake --build build --target MaterialXFuzz
./build/bin/MaterialXFuzz --fuzz=MaterialXXmlIoFuzz.ParseXmlString
```

Setting `FUZZTEST_FUZZING_MODE=ON` enables SanitizerCoverage instrumentation across all MaterialX libraries, allowing FuzzTest's mutation engine to explore new code paths. Adding `MATERIALX_DYNAMIC_ANALYSIS=ON` further enables AddressSanitizer and UndefinedBehaviorSanitizer for more thorough bug detection.

To persist the corpus between runs and limit fuzzing duration:

```sh
./build/bin/MaterialXFuzz \
--fuzz=MaterialXXmlIoFuzz.ParseXmlString \
--fuzz_for=300s \
--corpus_database=./corpus/
```

## Editor Setup

MaterialX should work in any editor that supports CMake, or that CMake can generate a project for.
Expand Down
43 changes: 43 additions & 0 deletions source/MaterialXFuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
include(GoogleTest)

add_executable(MaterialXFuzz XmlIo_fuzz.cpp)

target_link_libraries(MaterialXFuzz PRIVATE
MaterialXFormat
MaterialXCore)

target_include_directories(MaterialXFuzz PRIVATE
${CMAKE_SOURCE_DIR}/source)

# link_fuzztest selects the fuzzing or unit-test backend based on FUZZTEST_FUZZING_MODE.
link_fuzztest(MaterialXFuzz)

# FuzzTest's CMakeLists.txt calls add_compile_options(-Werror) globally, which propagates here.
# FuzzTest's own headers then trigger -Wshadow/-Wreturn-type during template instantiation.
# Override with -Wno-error so warnings from third-party headers don't fail the build.
if(NOT MSVC)
target_compile_options(MaterialXFuzz PRIVATE
-Wno-error # FuzzTest's CMakeLists propagates -Werror globally
-Wno-return-type # meta.h:167 falls off end without return; GCC 14 hard-errors this
-Wno-shadow # meta.h:67 lambda captures shadow outer parameter
-Wno-sign-compare) # value_mutation_helpers.h:62 int vs size_t comparison

# FuzzTest compiles abseil with -fsanitize=address by default in fuzzing mode.
# Link the ASan runtime into the final executable to resolve those symbols.
if(FUZZTEST_FUZZING_MODE)
target_link_options(MaterialXFuzz PRIVATE -fsanitize=address)
endif()
endif()

# Always copy resources to build/bin/resources/
# add_custom_target(ALL) ensures this runs on every build, not just when the
# binary is relinked.
add_custom_target(MaterialXFuzzResources ALL
COMMAND ${CMAKE_COMMAND} -E copy_directory_if_different
${CMAKE_SOURCE_DIR}/resources ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/resources
COMMENT "Copying resources to build/bin/resources/ for fuzz tests")
add_dependencies(MaterialXFuzz MaterialXFuzzResources)

gtest_discover_tests(MaterialXFuzz
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
DISCOVERY_MODE PRE_TEST)
25 changes: 25 additions & 0 deletions source/MaterialXFuzz/XmlIo_fuzz.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#include "fuzztest/fuzztest.h"
#include "gtest/gtest.h"
#include <MaterialXFormat/XmlIo.h>
#include <MaterialXCore/Document.h>

namespace mx = MaterialX;

void ParseXmlString(const std::string& xml_string)
{
mx::DocumentPtr doc = mx::createDocument();
try
{
mx::readFromXmlString(doc, xml_string);
doc->validate();
}
catch (const mx::Exception&)
{
// MaterialX exceptions indicate expected parse/validation failures, not bugs.
}
}

FUZZ_TEST(MaterialXXmlIoFuzz, ParseXmlString)
.WithDomains(fuzztest::Arbitrary<std::string>().WithMaxSize(1024 * 1024))
.WithSeeds(fuzztest::ReadFilesFromDirectory(
"resources/Materials/Examples"));
Loading