Skip to content

Commit 7f15521

Browse files
Fix build crash when compiler output is not valid UTF-8 (#8482)
* test: show the build panics on non-utf8 compiler output bsc can print a code frame that cuts a multi-byte character in half, so the captured stderr is not always valid utf-8. The Ok branch decodes it with from_utf8().expect(), which panics and takes down the whole build with a byte offset into stdout and no file name. This test feeds a truncated em dash and reproduces the panic. * fix: decode compiler output with from_utf8_lossy The Ok branch used from_utf8().expect() on the captured stderr, which panics when the output is not valid utf-8. The two sibling branches right above it already use from_utf8_lossy. Use it here too so a truncated character turns into a replacement character and the build keeps going instead of crashing. * Add changelog entry for the non-UTF-8 build crash fix
1 parent 44b1e81 commit 7f15521

2 files changed

Lines changed: 21 additions & 3 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
- Fix analysis namespace parsing after the Yojson migration. https://github.com/rescript-lang/rescript/pull/8454
3939
- Fix namespaced reference lookup in editor analysis. https://github.com/rescript-lang/rescript/pull/8455
4040
- Fix analysis segmentation fault for references after https://github.com/rescript-lang/rescript/pull/7887. https://github.com/rescript-lang/rescript/pull/8477
41+
- Fix build crash when the compiler emits output that is not valid UTF-8, such as a truncated multibyte character in a code frame. https://github.com/rescript-lang/rescript/pull/8482
4142

4243
#### :memo: Documentation
4344

rewatch/src/build/compile.rs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ use std::sync::mpsc;
2525
use std::time::SystemTime;
2626
use tracing::{info_span, instrument};
2727

28+
/// Decode captured compiler output (stdout or stderr) into a String.
29+
///
30+
/// The output is not guaranteed to be valid UTF-8: a code frame can truncate a
31+
/// multi-byte character. Decode lossily so a bad byte becomes a replacement
32+
/// character instead of crashing the build.
33+
fn compiler_output_to_string(bytes: &[u8]) -> String {
34+
String::from_utf8_lossy(bytes).to_string()
35+
}
36+
2837
/// Execute js-post-build command for a compiled JavaScript file.
2938
/// The command runs in the directory containing the rescript.json that defines it.
3039
/// The absolute path to the JS file is passed as an argument.
@@ -1039,9 +1048,7 @@ fn compile_file(
10391048
"Could not compile file. Error: {e}. Path to AST: {ast_path:?}"
10401049
)),
10411050
Ok(x) => {
1042-
let err = std::str::from_utf8(&x.stderr)
1043-
.expect("stdout should be non-null")
1044-
.to_string();
1051+
let err = compiler_output_to_string(&x.stderr);
10451052

10461053
let dir = Path::new(implementation_file_path).parent().unwrap();
10471054

@@ -1346,6 +1353,16 @@ mod tests {
13461353
use std::time::SystemTime;
13471354
use tempfile::TempDir;
13481355

1356+
// The compiler can write a code frame that truncates a multi-byte character, so the
1357+
// captured output is not always valid UTF-8. Decoding it must not panic.
1358+
#[test]
1359+
fn compiler_output_to_string_handles_invalid_utf8() {
1360+
// 0xe2 0x80 is the start of an em dash (U+2014); the third byte is missing.
1361+
let truncated = [b'W', b'a', b'r', b'n', b'i', b'n', b'g', b' ', 0xe2, 0x80];
1362+
let decoded = compiler_output_to_string(&truncated);
1363+
assert!(decoded.starts_with("Warning "));
1364+
}
1365+
13491366
fn test_project_context(root: &Path) -> ProjectContext {
13501367
let config = config::tests::create_config(config::tests::CreateConfigArgs {
13511368
name: "test-root".to_string(),

0 commit comments

Comments
 (0)