Repository: godotengine/godot (language: cpp)

Issue:
VideoStreamPlayer causes game to crash upon playing video
### Tested versions

- Reproducible in v4.4.beta4.official [93d270693]
- Not reproducible in v4.4.beta3.official [06acfccf8]

The only VideoStreamPlayer-related commit I saw in beta4 was #101958, so I think it is a likely cause of this issue.

### System information

Windows 11 - both Forward+ and Compatibility - both with and without dedicated GPU

### Issue description

Game closes without displaying any in-editor error messages upon playing a VideoStreamPlayer in 4.4 beta4.

### Steps to reproduce

Play an Ogg Theora stream through a VideoStreamPlayer, either with `autoplay=true` or `play()`. Seems to affect both Forward+ and Compatibility renderers (haven't tested Mobile).

### Minimal reproduction project (MRP)

[videoplayerbug.zip](https://github.com/user-attachments/files/18838902/videoplayerbug.zip)
The main scene can be played to reproduce the issue. In beta3, this works as expected and a blue box moves across the screen. In beta4, the game exits immediately.


Discussion / hints:
CC @berarma 
I can't reproduce it on Linux. The MRP works well here.

I'll try it on a machine with Windows 11. In the mean time if someone could get a crash backtrace it may speed things up.

Does it crash before displaying anything or when the video is already playing?
Can't reproduce on macOS (using provided videoplayerbug project).

Godot v4.4.beta4 - macOS Sequoia (15.3.1) - Multi-window, 2 monitors - Metal (Forward+) - integrated Apple M1 Pro (Apple7) - Apple M1 Pro (10 threads)
Can replicate this on Windows 11, will provide a stacktrace soon
I can't confirm it on a custom build on the same branch, so unsure what is different between the two versions here, so can't provide a stacktrace as it won't occur in a debug build
What compiler and compiler version are you using? What compiler does the official beta use?
> I can't confirm it on a custom build on the same branch, so unsure what is different between the two versions here, so can't provide a stacktrace as it won't occur in a debug build

Thanks for testing it.

> What compiler and compiler version are you using? What compiler does the official beta use?

Looking at the build scripts it seems to be built using ~~mingw-llvm~~ (read comment below from akien-mga).
Testing a production build currently, using MSVC in both cases, so might be MinGW specific, if it doesn't happen on a production MSVC build someone would need to build using MinGW to test (I don't have an editor build for that set up so it'd be easier if someone using it already can test to avoid the build time)
> > What compiler and compiler version are you using? What compiler does the official beta use?
> 
> Looking at the build scripts it seems to be built using mingw-llvm.

It's actually mingw-gcc 14.2.1 from Fedora 41 for x86_64 and x86_32, and [llvm-mingw](https://github.com/mstorsjo/llvm-mingw/) for arm64 only.

Also relevant, official builds are made with GCC LTO (`production=yes`), which may be a factor here.
Doesn't happen in a `production=yes` MSVC build so likely gcc specific
GCC and Windows specific.

I've been able to reproduce the issue on a Windows 10 VM using the official build, tested that beta3 works on the same VM. Now I'm trying to build a custom version for Windows with D3D12 support but it doesn't run on the same VM because it requries OpenGL3.3 or D3D11. It looks like I haven't built with D3D12 support although I've followed the instructions.
You probably need to build with ANGLE to be able to run on that VM. https://docs.godotengine.org/en/stable/contributing/development/compiling/compiling_for_windows.html#compiling-with-angle-support
I've made a custom build with the command `scons platform=windows debug_symbols=yes optimize=debug d3d12=yes angle_libs=/opt/angle/ linker=mold`, and it works.

Now I'm building with `production=yes`.
This is the backtrace and this is the build command: `scons platform=windows d3d12=yes angle_libs=/opt/angle/ linker=mold use_static_cpp=yes lto=auto debug_symbols=yes`
```
CrashHandlerException: Program crashed with signal 11
Engine version: Godot Engine v4.4.beta.custom_build (93d270693079ea7802c9e1334a2e0ecd8529eeed)                          Dumping the backtrace. Please include this when reporting the bug to the project developer.
[1] _gnu_exception_handler (./mingw-w64-crt/crt/crt_handler.c:223)
[2] __tmainCRTStartup (./mingw-w64-crt/crt/crtexe.c:321)
[3] WinMainCRTStartup (./mingw-w64-crt/crt/crtexe.c:176)
-- END OF BACKTRACE --
```

Is this an [exception handler crash](https://github.com/godotengine/godot/pull/102900)?
Not sure why the crash handler backtrace is like this, it's probably a separate issue we need to keep looking into @bruvzg.

I ran the reproduction project through gdb and got a more helpful backtrace:
```
Thread 1 received signal SIGSEGV, Segmentation fault.
0x00000001404242e6 in oc_huff_token_decode_c ()
(gdb) bt
#0  0x00000001404242e6 in oc_huff_token_decode_c ()
#1  0x000000014049b09e in VideoStreamPlaybackTheora::update(double) [clone .part.0] ()
#2  0x0000000142cfd9f2 in VideoStreamPlayer::play() ()
#3  0x00000001449648c1 in Object::notification(int, bool) ()
#4  0x0000000142802d1f in Node::_propagate_enter_tree() ()
#5  0x0000000142803015 in Node::_propagate_enter_tree() ()
#6  0x000000014282a0c1 in Node::_set_tree(SceneTree*) ()
#7  0x000000014006f748 in widechar_main(int, wchar_t**) ()
#8  0x0000000145353ba3 in main ()
```

Here's how I did it for the record, which might be helpful for @berarma to debug further from Linux:

- Compiled Godot in a `fedora:41` podman container with mingw-gcc and just `scons p=windows lto=full debug_symbols=yes`
- Installed `wine`, and `mingw64-gdb`, which provides a Windows version of gdb in `/usr/x86_64-w64-mingw32/sys-root/mingw/bin/gdb.exe`
- `cd` to the MRP folder
- `wine /usr/x86_64-w64-mingw32/sys-root/mingw/bin/gdb.exe path/to/godot.windows.editor.x86_64.exe`
- In the gdb prompt, `r --headless video_player.tscn`

I discussed this with @hpvb who had a hunch that it might be related to another GCC LTO bug we're currently debugging in #102867. The flags suggested in https://github.com/godotengine/godot/issues/102867#issuecomment-2663930093 seem to solve this crash indeed in my test.
@akien-mga, I got the same backtrace as you. The process was a bit different since I'm on Debian, but I just had to translate your steps.

There seems to be a gap in the backtrace, I'm not directly calling `oc_huff_token_decode_c` in the code but they're next to each other in the backtrace. Also, I can't see line numbers or the source code, gdb complains that there is no symbol table. Without this information it's hard to know what's happening.

In my tests, setting LTO and default optimization triggered the crash, but it wasn't always the case. Since I couldn't get a good backtrace I put several `printf` in the code to try to narrow down the code causing the crash and then it stopped crashing. The only change was the `printf` lines.

If I could get the debug symbols to work I could investigate more, without this, I'm out of ideas to try. I'll test a build with the flags suggested in #102867 to confirm your results and follow your work there.
Confirmed it doesn't crash with the flags in https://github.com/godotengine/godot/issues/102867#issuecomment-2663930093. Tested also adding `d3d12=yes angle=...` both on Wine and Windows and it also works.

I've resorted to reading the assembly to find out the line, and this is the line that causes the segmentation fault:

https://github.com/godotengine/godot/blob/e567f49cbb292dd6bb628348e67c1f3c2a3a5f99/thirdparty/libtheora/huffdec.c#L482

The second argument (_tree) to the function `oc_huff_token_decode_c` is a bad pointer. This function is called with static int16 arrays defined inside static functions for the second argument, like this:

https://github.com/godotengine/godot/blob/e567f49cbb292dd6bb628348e67c1f3c2a3a5f99/thirdparty/libtheora/decode.c#L319

And this is where the array is defined:

https://github.com/godotengine/godot/blob/e567f49cbb292dd6bb628348e67c1f3c2a3a5f99/thirdparty/libtheora/decode.c#L309

There are several calls to this function in this file, but since I don't have a complete backtrace I can't tell which one is causing the crash. Most of them use these static arrays, while some others use arrays read from the video headers.

I've tried building with `ccflags="-fno-inline"` to try and get a complete backtrace, but it fails with these errors:

```
lto1: error: two or more sections for .gnu.lto__ZZL40_register_variant_builtin_methods_stringvEN28Method_StringName_hex_to_int18get_argument_countEv.34588887.ba4a6b712d465bb9
lto1: error: two or more sections for .gnu.lto__ZZN6embree12parallel_forIyZNS_17ParallelRadixSortINS_4sse212PresplitItemEjE17tbbRadixIterationEjbPKS3_PS3_yEUlyE0_EEvT_RKT0_ENKUlRKNS_5rangeIyEEE_clESG_.9227336.fefa44b2c0907de9
(null):0: confused by earlier errors, bailing out
make: *** [/tmp/ccphOoha.mk:305: /tmp/cc6f9UAr.ltrans101.ltrans.o] Error 1
make: *** Waiting for unfinished jobs....
(null):0: confused by earlier errors, bailing out
make: *** [/tmp/ccphOoha.mk:383: /tmp/cc6f9UAr.ltrans127.ltrans.o] Error 1
./core/templates/sort_array.h: In function 'unguarded_insertion_sort.constprop.isra':
./core/templates/sort_array.h:288:59: warning: iteration 1537228672809129301 invokes undefined behavior [-Waggressive-loop-optimizations]
  288 |                         unguarded_linear_insert(i, p_array[i], p_array);
      |                                                           ^
./core/templates/sort_array.h:287:45: note: within this loop
  287 |                 for (int64_t i = p_first; i != p_last; i++) {
      |                                             ^
thirdparty/misc/polypartition.cpp: In function 'Triangulate_OPT.isra':
thirdparty/misc/polypartition.cpp:711:29: warning: argument 1 value '18446744073709551615' exceeds maximum object size 9223372036854775807 [-Walloc-size-larger-than=]
  711 |   dpstates = new DPState *[n];
      |                             ^
/usr/lib/gcc/x86_64-w64-mingw32/12-posix/include/c++/new:128:26: note: in a call to allocation function 'operator new []' declared here
  128 | _GLIBCXX_NODISCARD void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
      |                          ^
lto-wrapper: fatal error: make returned 2 exit status
compilation terminated.
/usr/bin/x86_64-w64-mingw32-ld: error: lto-wrapper failed
collect2: error: ld returned 1 exit status
scons: *** [bin/godot.windows.editor.x86_64.exe] Error 1
scons: building terminated because of errors.
```
Amended first paragraph in my last comment which didn't make any sense. Had a mental lapse between long builds.

Fix patch:
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index b27453d48ada..f253e11ec548 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -1278,167 +1278,6 @@ struct ExportLibsData {
 	String dest_dir;
 };
 
-bool EditorExportPlatformIOS::_archive_has_arm64(const String &p_path, uint32_t *r_cputype, uint32_t *r_cpusubtype) const {
-	bool has_arm64_image = false;
-	if (FileAccess::exists(p_path)) {
-		if (LipO::is_lipo(p_path)) {
-			// LipO.
-			Ref<LipO> lipo;
-			lipo.instantiate();
-			if (lipo->open_file(p_path)) {
-				for (int i = 0; i < lipo->get_arch_count(); i++) {
-					if (lipo->get_arch_cputype(i) == 0x100000c && lipo->get_arch_cpusubtype(i) == 0) {
-						has_arm64_image = true;
-						break;
-					}
-				}
-			}
-			lipo->close();
-		} else {
-			// Single architecture archive.
-			Ref<FileAccess> sim_f = FileAccess::open(p_path, FileAccess::READ);
-			if (sim_f.is_valid()) {
-				char magic[9] = {};
-				sim_f->get_buffer((uint8_t *)&magic[0], 8);
-				if (String(magic) == String("!<arch>\n")) {
-					while (!sim_f->eof_reached()) {
-						// Read file metadata.
-						char name_short[17] = {};
-						char size_short[11] = {};
-						sim_f->get_buffer((uint8_t *)&name_short[0], 16);
-						sim_f->seek(sim_f->get_position() + 12 + 6 + 6 + 8); // Skip modification time, owner ID, group ID, file mode.
-						sim_f->get_buffer((uint8_t *)&size_short[0], 10);
-						sim_f->seek(sim_f->get_position() + 2); // Skip end marker.
-
-						int64_t file_size = String(size_short).to_int();
-						int64_t next_off = sim_f->get_position() + file_size;
-
-						String name = String(name_short); // Skip extended name.
-						if (name.is_empty() || file_size == 0) {
-							break;
-						}
-						if (name.begins_with("#1/")) {
-							int64_t name_len = String(name_short).replace("#1/", "").to_int();
-							sim_f->seek(sim_f->get_position() + name_len);
-						}
-
-						// Read file content.
-						uint32_t obj_magic = sim_f->get_32();
-
-						bool swap = (obj_magic == 0xcffaedfe || obj_magic == 0xcefaedfe);
-						if (obj_magic == 0xcefaedfe || obj_magic == 0xfeedface || obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
-							uint32_t cputype = sim_f->get_32();
-							uint32_t cpusubtype = sim_f->get_32();
-							if (swap) {
-								cputype = BSWAP32(cputype);
-								cpusubtype = BSWAP32(cpusubtype);
-							}
-							if (r_cputype) {
-								*r_cputype = cputype;
-							}
-							if (r_cpusubtype) {
-								*r_cpusubtype = cpusubtype;
-							}
-							if (cputype == 0x100000c && cpusubtype == 0) {
-								has_arm64_image = true;
-							}
-							break;
-						}
-						sim_f->seek(next_off);
-					}
-				}
-				sim_f->close();
-			}
-		}
-	}
-	return has_arm64_image;
-}
-
-int EditorExportPlatformIOS::_archive_convert_to_simulator(const String &p_path) const {
-	int commands_patched = 0;
-	Ref<FileAccess> sim_f = FileAccess::open(p_path, FileAccess::READ_WRITE);
-	if (sim_f.is_valid()) {
-		char magic[9] = {};
-		sim_f->get_buffer((uint8_t *)&magic[0], 8);
-		if (String(magic) == String("!<arch>\n")) {
-			while (!sim_f->eof_reached()) {
-				// Read file metadata.
-				char name_short[17] = {};
-				char size_short[11] = {};
-				sim_f->get_buffer((uint8_t *)&name_short[0], 16);
-				sim_f->seek(sim_f->get_position() + 12 + 6 + 6 + 8); // Skip modification time, owner ID, group ID, file mode.
-				sim_f->get_buffer((uint8_t *)&size_short[0], 10);
-				sim_f->seek(sim_f->get_position() + 2); // Skip end marker.
-
-				int64_t file_size = String(size_short).to_int();
-				int64_t next_off = sim_f->get_position() + file_size;
-
-				String name = String(name_short); // Skip extended name.
-				if (name.is_empty() || file_size == 0) {
-					break;
-				}
-				if (name.begins_with("#1/")) {
-					int64_t name_len = String(name_short).replace("#1/", "").to_int();
-					sim_f->seek(sim_f->get_position() + name_len);
-				}
-
-				// Read file content.
-				uint32_t obj_magic = sim_f->get_32();
-
-				bool swap = (obj_magic == 0xcffaedfe || obj_magic == 0xcefaedfe);
-				if (obj_magic == 0xcefaedfe || obj_magic == 0xfeedface || obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
-					uint32_t cputype = sim_f->get_32();
-					uint32_t cpusubtype = sim_f->get_32();
-					uint32_t filetype = sim_f->get_32();
-					uint32_t ncmds = sim_f->get_32();
-					sim_f->get_32(); // Commands total size.
-					sim_f->get_32(); // Commands flags.
-					if (obj_magic == 0xcffaedfe || obj_magic == 0xfeedfacf) {
-						sim_f->get_32(); // Reserved, 64-bit only.
-					}
-					if (swap) {
-						ncmds = BSWAP32(ncmds);
-						cputype = BSWAP32(cputype);
-						cpusubtype = BSWAP32(cpusubtype);
-						filetype = BSWAP32(filetype);
-					}
-					if (cputype == 0x100000C && cpusubtype == 0 && filetype == 1) {
-						// ARM64, object file.
-						for (uint32_t i = 0; i < ncmds; i++) {
-							int64_t cmdofs = sim_f->get_position();
-							uint32_t cmdid = sim_f->get_32();
-							uint32_t cmdsize = sim_f->get_32();
-							if (swap) {
-								cmdid = BSWAP32(cmdid);
-								cmdsize = BSWAP32(cmdsize);
-							}
-							if (cmdid == MachO::LoadCommandID::LC_BUILD_VERSION) {
-								int64_t platform = sim_f->get_32();
-								if (swap) {
-									platform = BSWAP32(platform);
-								}
-								if (platform == MachO::PlatformID::PLATFORM_IOS) {
-									sim_f->seek(cmdofs + 4 + 4);
-									uint32_t new_id = MachO::PlatformID::PLATFORM_IOSSIMULATOR;
-									if (swap) {
-										new_id = BSWAP32(new_id);
-									}
-									sim_f->store_32(new_id);
-									commands_patched++;
-								}
-							}
-							sim_f->seek(cmdofs + cmdsize);
-						}
-					}
-				}
-				sim_f->seek(next_off);
-			}
-		}
-		sim_f->close();
-	}
-	return commands_patched;
-}
-
 void EditorExportPlatformIOS::_check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const {
 	Ref<PList> plist;
 	plist.instantiate();
@@ -2443,82 +2282,6 @@ Error EditorExportPlatformIOS::_export_project_helper(const Ref<EditorExportPres
 		return ERR_FILE_NOT_FOUND;
 	}
 
-	// HACK: We don't want to run the simulator library generation code anymore after GH-102179, but removing it
-	// triggers an internal compiler error with latest mingw-gcc... For now we bring it back as a workaround
-	// with a condition that should never be true (that project setting doesn't exist).
-
-	// Check and generate missing ARM64 simulator library.
-	if (ProjectSettings::get_singleton()->has_setting("__dummy_setting_blame_akien_if_you_define_this_somehow")) {
-		String sim_lib_path = dest_dir + String(binary_name + ".xcframework").path_join("ios-arm64_x86_64-simulator").path_join("libgodot.a");
-		String dev_lib_path = dest_dir + String(binary_name + ".xcframework").path_join("ios-arm64").path_join("libgodot.a");
-		String tmp_lib_path = EditorPaths::get_singleton()->get_temp_dir().path_join(binary_name + "_lipo_");
-		uint32_t cputype = 0;
-		uint32_t cpusubtype = 0;
-		if (!_archive_has_arm64(sim_lib_path, &cputype, &cpusubtype) && _archive_has_arm64(dev_lib_path) && FileAccess::exists(dev_lib_path)) {
-			add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("ARM64 simulator library, generating from device library."));
-
-			Vector<String> tmp_lib_files;
-			Vector<Vector2i> tmp_lib_cputypes;
-			// Extract/copy simulator lib.
-			if (FileAccess::exists(sim_lib_path)) {
-				if (LipO::is_lipo(sim_lib_path)) {
-					Ref<LipO> lipo;
-					lipo.instantiate();
-					if (lipo->open_file(sim_lib_path)) {
-						for (int i = 0; i < lipo->get_arch_count(); i++) {
-							const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
-							lipo->extract_arch(i, f_name);
-							tmp_lib_files.push_back(f_name);
-							tmp_lib_cputypes.push_back(Vector2i(lipo->get_arch_cputype(i), lipo->get_arch_cpusubtype(i)));
-						}
-					}
-				} else {
-					const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
-					tmp_app_path->copy(sim_lib_path, f_name);
-					tmp_lib_files.push_back(f_name);
-					tmp_lib_cputypes.push_back(Vector2i(cputype, cpusubtype));
-				}
-			}
-			// Copy device lib.
-			if (LipO::is_lipo(dev_lib_path)) {
-				Ref<LipO> lipo;
-				lipo.instantiate();
-				if (lipo->open_file(dev_lib_path)) {
-					for (int i = 0; i < lipo->get_arch_count(); i++) {
-						if (lipo->get_arch_cputype(i) == 0x100000c && lipo->get_arch_cpusubtype(i) == 0) {
-							const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
-							lipo->extract_arch(i, f_name);
-							tmp_lib_files.push_back(f_name);
-							tmp_lib_cputypes.push_back(Vector2i(0x100000c, 0)); // ARM64.
-							break;
-						}
-					}
-				}
-			} else {
-				const String &f_name = tmp_lib_path + itos(tmp_lib_files.size());
-				tmp_app_path->copy(dev_lib_path, f_name);
-				tmp_lib_files.push_back(f_name);
-				tmp_lib_cputypes.push_back(Vector2i(0x100000c, 0)); // ARM64.
-			}
-
-			// Patch device lib.
-			int patch_count = _archive_convert_to_simulator(tmp_lib_path + itos(tmp_lib_files.size() - 1));
-			if (patch_count == 0) {
-				add_message(EXPORT_MESSAGE_WARNING, TTR("Export"), TTR("Unable to generate ARM64 simulator library."));
-			} else {
-				// Repack.
-				Ref<LipO> lipo;
-				lipo.instantiate();
-				lipo->create_file(sim_lib_path, tmp_lib_files, tmp_lib_cputypes);
-			}
-
-			// Cleanup.
-			for (const String &E : tmp_lib_files) {
-				tmp_app_path->remove(E);
-			}
-		}
-	}
-
 	// Generate translations files.
 
 	Dictionary appnames = GLOBAL_GET("application/config/name_localized");
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index d5a8981fa245..8fc436153a15 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -136,8 +136,6 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
 	Vector<ExportArchitecture> _get_supported_architectures() const;
 	Vector<String> _get_preset_architectures(const Ref<EditorExportPreset> &p_preset) const;
 
-	bool _archive_has_arm64(const String &p_path, uint32_t *r_cputype = nullptr, uint32_t *r_cpusubtype = nullptr) const;
-	int _archive_convert_to_simulator(const String &p_path) const;
 	void _check_xcframework_content(const String &p_path, int &r_total_libs, int &r_static_libs, int &r_dylibs, int &r_frameworks) const;
 	Error _convert_to_framework(const String &p_source, const String &p_destination, const String &p_id) const;
 
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index db65bb64a7b5..6f57b880441f 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -781,6 +781,10 @@ def configure_mingw(env: "SConsEnvironment"):
         else:
             env.Append(CCFLAGS=["-flto"])
             env.Append(LINKFLAGS=["-flto"])
+        if not env["use_llvm"]:
+            # For mingw-gcc LTO, disable linker plugin and enable whole program to work around GH-102867.
+            env.Append(CCFLAGS=["-fno-use-linker-plugin", "-fwhole-program"])
+            env.Append(LINKFLAGS=["-fno-use-linker-plugin", "-fwhole-program"])
 
     if env["use_asan"]:
         env.Append(LINKFLAGS=["-Wl,--stack," + str(STACK_SIZE_SANITIZERS)])


Test patch: