Summary
UMaterialEditingLibrary::RecompileMaterial() calls
FMaterialEditorUtilities::BuildTextureStreamingData() before it checks whether the
material has a valid resource. If the recompile has just failed, that path reaches
FMaterialResource::GetMaterialDomain() (MaterialShared.cpp:1782), which dereferences
its TObjectPtr Material with no null guard, and the editor dies with an
access violation reading 0x430.
The same function guards for exactly this case two lines later
(MaterialEditingLibrary.cpp:1054), which is what makes it look like an ordering slip
rather than an unanticipated state.
This is reachable from any script or plugin that authors a material and recompiles it.
A failed compile is a normal outcome of authoring, not a corrupt asset - the engine
diagnosed our invalid material correctly and clearly, then crashed afterwards.
UE 5.8.0 built from source (release @ 7deeb413d3dc), Windows 11, PCD3D_SM6.
What Type of Bug are you experiencing?
Rendering (Graphics / Niagara)
Steps to Reproduce
Believed minimal (see note below):
- Create a new Material asset, e.g. /Game/Test/MM_DecalGrid. Leave all defaults.
- Set MaterialDomain = MD_DeferredDecal and BlendMode = BLEND_Translucent.
This pair is illegal and the engine correctly rejects it - a compile failure is
logged immediately, as a Warning. - Call UMaterialEditingLibrary::RecompileMaterial() on it.
As Python:
import unreal
mat = unreal.AssetToolsHelpers.get_asset_tools().create_asset(
"MM_DecalGrid", "/Game/Test", unreal.Material, unreal.MaterialFactoryNew())
mat.set_editor_property("material_domain", unreal.MaterialDomain.MD_DEFERRED_DECAL)
mat.set_editor_property("blend_mode", unreal.BlendMode.BLEND_TRANSLUCENT)
unreal.MaterialEditingLibrary.recompile_material(mat)
NOTE ON THIS REDUCTION - please read. What we actually ran also built a 16-expression
graph between steps 2 and 3, including SubstrateUnlitBSDF into SubstrateConvertToDecal
(a second illegal construction, also correctly diagnosed). We believe the graph is not
required, because the log shows the first compile failure landing 7ms into the run on
the property set alone, with the material then sitting uncompilable for 14.3 seconds
before the recompile was called. We have NOT verified the reduced form - the crash
took the editor down and we have not gone back in.
The asset was never saved to disk, and no Material Editor window was open for it.
Expected Result
RecompileMaterial() returns the compile errors for the material. This is what the
function is written to do - it returns Resource->GetCompileErrors() at
MaterialEditingLibrary.cpp:1056, behind a check for exactly the null resource that a
failed compile produces.
Observed Result
Access violation reading location 0x0000000000000430 in UnrealEditor-Engine.dll.
Editor lost, with unsaved work.
Both compile failures log as Warning, not Error, so any automation watching for Error
sees a clean run right up to the crash.
Affects Versions
5.8
Platform(s)
Windows
For crash reports, include your callstack
UnrealEditor-Engine.dll!FMaterialResource::GetMaterialDomain() Line 1782
UnrealEditor-Engine.dll!CompileDebugViewModeShaders(…) Line 377
UnrealEditor-MaterialEditor.dll!FMaterialEditorUtilities::BuildTextureStreamingData(UMaterialInterface *) Line 841
UnrealEditor-MaterialEditor.dll!UMaterialEditingLibrary::RecompileMaterial(UMaterial *) Line 1054
UnrealEditor-MaterialEditor.dll!UMaterialEditingLibrary::execRecompileMaterial(…) Line 4184
UnrealEditor-CoreUObject.dll!UFunction::Invoke(…) Line 7595
UnrealEditor-CoreUObject.dll!UObject::ProcessEvent(…) Line 2234
UnrealEditor-PythonScriptPlugin.dll!PyUtil::InvokeFunctionCall(…) Line 647
[… Python binding and tool-dispatch frames …]
UnrealEditor-Core.dll!FTSTicker::Tick(float) Line 121
UnrealEditor.exe!FEngineLoop::Tick() Line 6104
UnrealEditor.exe!GuardedMain(const wchar_t *) Line 190
The frames between are the Python bindings and our automation layer dispatching the
call. They reach RecompileMaterial through the ordinary reflected-function bindings;
the top four frames are entirely first-party.
Additional Notes
WHERE IT GOES WRONG
The dereference - Runtime/Engine/Private/Materials/MaterialShared.cpp:1782
EMaterialDomain FMaterialResource::GetMaterialDomain() const { return Material->MaterialDomain; }
Material is TObjectPtr (MaterialShared.h:3447). No null guard, and none of
the neighbouring one-line accessors on the following lines has one either. Reading
offset 0x430 off a null base is consistent with Material == nullptr.
The caller - Runtime/Engine/Private/DebugViewModeHelpers.cpp:370-378
FMaterial* Material = MaterialInterface->GetMaterialResource(Platform, QualityLevel);
if (!Material) { continue; }
if (Material->GetMaterialDomain() != MD_Surface || Material->IsUsedWithLandscape())
The FMaterial* IS null-checked. What is not checked is the UMaterial* inside the
returned FMaterialResource. A resource that exists but has no owning material passes
the guard and dies on the next line.
The ordering - Editor/MaterialEditor/Private/MaterialEditingLibrary.cpp:1042-1060
RecompileMaterialInternal( { Material }, nullptr);
...
FMaterialEditorUtilities::BuildTextureStreamingData(Material); // line 1052, crashes
if (const FMaterialResource* Resource = Material->GetMaterialResource(GMaxRHIShaderPlatform))
{
return Resource->GetCompileErrors(); // lines 1054-1058
}
The function already knows the resource can come back unusable after a recompile.
It just runs the streaming build first.
ONE MORE LEAD
BuildTextureStreamingData opens with CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS) at
MaterialEditorUtilities.cpp:826, before it touches the material. Our log shows
“LogUObjectHash: Compacting FUObjectHashTables data took 0.62ms” 31ms before the
crash with nothing between the two. So a GC demonstrably ran between the failed
compile and the unguarded dereference. We are not claiming that is the cause - only
that it would be the first thing we looked at.
SUGGESTED FIXES
Smallest, and it matches the function’s own intent: move the streaming build behind
the resource check already on MaterialEditingLibrary.cpp:1054, or skip it when the
recompile produced errors. A material that failed to compile has no meaningful
texture-streaming data to build.
Defensive: null-guard FMaterialResource::GetMaterialDomain() and its neighbours at
MaterialShared.cpp:1782+.
Most principled: have CompileDebugViewModeShaders skip any resource with no owning
UMaterial, alongside the FMaterial* check it already does at
DebugViewModeHelpers.cpp:371. That covers every caller of BuildTextureStreamingData,
including EditorBuildUtils.cpp:1589 and :1708, not just the one we came in through.
WHAT WE DID NOT TEST
- The reduced repro above, without the graph. Strongly suggested by the timings.
- r.Substrate=False. Substrate was on in our project; nothing in the call stack is
Substrate-specific, so we do not expect it to be required. - The Material Editor UI path. We expect it not to reproduce, since the UI does not
route through UMaterialEditingLibrary::RecompileMaterial. - Whether either compile error alone is sufficient.
- Whether the asset being unsaved matters.
- Any platform other than PCD3D_SM6 on Windows.