Fix fortsh link contract
- SHA
39ac9c35387a869d5e68b6b72766df1b422cc551- Parents
-
1dd8886 - Tree
ffe5328
39ac9c3
39ac9c35387a869d5e68b6b72766df1b422cc5511dd8886
ffe5328| Status | File | + | - |
|---|---|---|---|
| M |
src/driver/mod.rs
|
0 | 1 |
| M |
src/ir/lower.rs
|
28 | 1 |
| M |
src/testing.rs
|
0 | 1 |
| M |
tests/artifact_audit_29_10.rs
|
45 | 11 |
| M |
tests/cli_driver.rs
|
152 | 0 |
| M |
tests/i128_cross_object.rs
|
37 | 6 |
| M |
tests/sprint29_audit_realworld.rs
|
40 | 6 |
src/driver/mod.rsmodified@@ -1413,7 +1413,6 @@ fn link_inputs(inputs: &[PathBuf], output: &Path, opts: &Options) -> Result<(), | ||
| 1413 | 1413 | args.extend([ |
| 1414 | 1414 | rt_path, |
| 1415 | 1415 | "-lSystem".into(), |
| 1416 | - "-no_uuid".into(), | |
| 1417 | 1416 | "-syslibroot".into(), |
| 1418 | 1417 | sysroot, |
| 1419 | 1418 | "-e".into(), |
src/ir/lower.rsmodified@@ -402,6 +402,7 @@ pub fn lower_file( | ||
| 402 | 402 | &no_host_param_consts, |
| 403 | 403 | &no_host, |
| 404 | 404 | None, |
| 405 | + None, | |
| 405 | 406 | &alloc_return_funcs, |
| 406 | 407 | &optional_params, |
| 407 | 408 | &descriptor_params, |
@@ -2201,6 +2202,7 @@ fn lower_unit( | ||
| 2201 | 2202 | // and character-kind for each host-associated variable the |
| 2202 | 2203 | // closure-passing ABI threads in as a hidden pointer param. |
| 2203 | 2204 | host_decls: &[crate::ast::decl::SpannedDecl], |
| 2205 | + host_link_name: Option<&str>, | |
| 2204 | 2206 | host_module: Option<&str>, |
| 2205 | 2207 | alloc_return_funcs: &HashSet<String>, |
| 2206 | 2208 | optional_params: &HashMap<String, Vec<bool>>, |
@@ -2302,6 +2304,7 @@ fn lower_unit( | ||
| 2302 | 2304 | &combined_uses, |
| 2303 | 2305 | &visible_param_consts, |
| 2304 | 2306 | &child_host_decls, |
| 2307 | + Some(body_fname.as_str()), | |
| 2305 | 2308 | host_module, |
| 2306 | 2309 | alloc_return_funcs, |
| 2307 | 2310 | optional_params, |
@@ -2328,6 +2331,7 @@ fn lower_unit( | ||
| 2328 | 2331 | let func_name = lowered_procedure_symbol_name( |
| 2329 | 2332 | name, |
| 2330 | 2333 | bind.as_ref(), |
| 2334 | + host_link_name, | |
| 2331 | 2335 | host_module, |
| 2332 | 2336 | internal_only, |
| 2333 | 2337 | internal_funcs, |
@@ -2604,6 +2608,7 @@ fn lower_unit( | ||
| 2604 | 2608 | &combined_uses, |
| 2605 | 2609 | &visible_param_consts, |
| 2606 | 2610 | &child_host_decls, |
| 2611 | + Some(func_name.as_str()), | |
| 2607 | 2612 | host_module, |
| 2608 | 2613 | alloc_return_funcs, |
| 2609 | 2614 | optional_params, |
@@ -2632,6 +2637,7 @@ fn lower_unit( | ||
| 2632 | 2637 | let func_name = lowered_procedure_symbol_name( |
| 2633 | 2638 | name, |
| 2634 | 2639 | bind.as_ref(), |
| 2640 | + host_link_name, | |
| 2635 | 2641 | host_module, |
| 2636 | 2642 | internal_only, |
| 2637 | 2643 | internal_funcs, |
@@ -3095,6 +3101,7 @@ fn lower_unit( | ||
| 3095 | 3101 | &combined_uses, |
| 3096 | 3102 | &visible_param_consts, |
| 3097 | 3103 | &child_host_decls, |
| 3104 | + Some(func_name.as_str()), | |
| 3098 | 3105 | host_module, |
| 3099 | 3106 | alloc_return_funcs, |
| 3100 | 3107 | optional_params, |
@@ -3139,6 +3146,7 @@ fn lower_unit( | ||
| 3139 | 3146 | &combined_uses, |
| 3140 | 3147 | &visible_param_consts, |
| 3141 | 3148 | &no_host_decls, |
| 3149 | + None, | |
| 3142 | 3150 | module_name, |
| 3143 | 3151 | alloc_return_funcs, |
| 3144 | 3152 | optional_params, |
@@ -3182,6 +3190,7 @@ fn lower_unit( | ||
| 3182 | 3190 | &combined_uses, |
| 3183 | 3191 | &visible_param_consts, |
| 3184 | 3192 | &no_host_decls, |
| 3193 | + None, | |
| 3185 | 3194 | Some(parent.as_str()), |
| 3186 | 3195 | alloc_return_funcs, |
| 3187 | 3196 | optional_params, |
@@ -7129,9 +7138,23 @@ fn module_procedure_symbol_name(module_name: &str, proc_name: &str) -> String { | ||
| 7129 | 7138 | format!("afs_modproc_{}_{}", module_name.to_lowercase(), proc_name) |
| 7130 | 7139 | } |
| 7131 | 7140 | |
| 7141 | +fn sanitize_internal_host_symbol(host_link_name: &str) -> String { | |
| 7142 | + host_link_name | |
| 7143 | + .chars() | |
| 7144 | + .map(|ch| { | |
| 7145 | + if ch.is_ascii_alphanumeric() { | |
| 7146 | + ch.to_ascii_lowercase() | |
| 7147 | + } else { | |
| 7148 | + '_' | |
| 7149 | + } | |
| 7150 | + }) | |
| 7151 | + .collect() | |
| 7152 | +} | |
| 7153 | + | |
| 7132 | 7154 | fn lowered_procedure_symbol_name( |
| 7133 | 7155 | name: &str, |
| 7134 | 7156 | bind: Option<&crate::ast::unit::BindInfo>, |
| 7157 | + host_link_name: Option<&str>, | |
| 7135 | 7158 | host_module: Option<&str>, |
| 7136 | 7159 | internal_only: bool, |
| 7137 | 7160 | internal_funcs: &HashMap<String, u32>, |
@@ -7147,7 +7170,11 @@ fn lowered_procedure_symbol_name( | ||
| 7147 | 7170 | } |
| 7148 | 7171 | if internal_only { |
| 7149 | 7172 | if let Some(idx) = internal_funcs.get(&name.to_lowercase()) { |
| 7150 | - return format!("afs_internal_{}", idx); | |
| 7173 | + let host_prefix = host_link_name | |
| 7174 | + .map(sanitize_internal_host_symbol) | |
| 7175 | + .filter(|prefix| !prefix.is_empty()) | |
| 7176 | + .unwrap_or_else(|| "local".into()); | |
| 7177 | + return format!("afs_internal_{}_{}", host_prefix, idx); | |
| 7151 | 7178 | } |
| 7152 | 7179 | } |
| 7153 | 7180 | if let Some(module_name) = host_module { |
src/testing.rsmodified@@ -893,7 +893,6 @@ fn link_with_runtime(obj: &Path, output: &Path) -> Result<(), String> { | ||
| 893 | 893 | obj.to_str().unwrap(), |
| 894 | 894 | &rt_path, |
| 895 | 895 | "-lSystem", |
| 896 | - "-no_uuid", | |
| 897 | 896 | "-syslibroot", |
| 898 | 897 | &sysroot, |
| 899 | 898 | "-e", |
tests/artifact_audit_29_10.rsmodified@@ -63,6 +63,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String { | ||
| 63 | 63 | String::from_utf8_lossy(&output.stdout).into_owned() |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | +fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> { | |
| 67 | + const MH_MAGIC_64: u32 = 0xfeedfacf; | |
| 68 | + const LC_UUID: u32 = 0x1b; | |
| 69 | + const MACH_HEADER_64_SIZE: usize = 32; | |
| 70 | + | |
| 71 | + if bytes.len() < MACH_HEADER_64_SIZE { | |
| 72 | + return bytes; | |
| 73 | + } | |
| 74 | + let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); | |
| 75 | + if magic != MH_MAGIC_64 { | |
| 76 | + return bytes; | |
| 77 | + } | |
| 78 | + let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize; | |
| 79 | + let mut offset = MACH_HEADER_64_SIZE; | |
| 80 | + for _ in 0..ncmds { | |
| 81 | + if offset + 8 > bytes.len() { | |
| 82 | + break; | |
| 83 | + } | |
| 84 | + let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()); | |
| 85 | + let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()); | |
| 86 | + let cmdsize = cmdsize as usize; | |
| 87 | + if cmdsize < 8 || offset + cmdsize > bytes.len() { | |
| 88 | + break; | |
| 89 | + } | |
| 90 | + if cmd == LC_UUID && cmdsize >= 24 { | |
| 91 | + bytes[offset + 8..offset + 24].fill(0); | |
| 92 | + } | |
| 93 | + offset += cmdsize; | |
| 94 | + } | |
| 95 | + bytes | |
| 96 | +} | |
| 97 | + | |
| 66 | 98 | fn opt_name(opt: OptLevel) -> &'static str { |
| 67 | 99 | match opt { |
| 68 | 100 | OptLevel::O0 => "-O0", |
@@ -149,7 +181,7 @@ fn object_snapshot_is_deterministic_with_module_globals_across_opt_levels() { | ||
| 149 | 181 | } |
| 150 | 182 | |
| 151 | 183 | #[test] |
| 152 | -fn linked_binary_is_deterministic_for_same_output_path_and_has_no_uuid() { | |
| 184 | +fn linked_binary_is_deterministic_for_same_output_path_and_has_uuid() { | |
| 153 | 185 | let compiler = find_compiler(); |
| 154 | 186 | let source = fixture("module_init.f90"); |
| 155 | 187 | let stem = source.file_stem().unwrap().to_str().unwrap(); |
@@ -165,19 +197,20 @@ fn linked_binary_is_deterministic_for_same_output_path_and_has_no_uuid() { | ||
| 165 | 197 | compile_binary(&compiler, &source, opt, &bin_path); |
| 166 | 198 | let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]); |
| 167 | 199 | assert!( |
| 168 | - !load_commands.contains("LC_UUID"), | |
| 169 | - "linked binary at {} should omit LC_UUID for reproducible builds:\n{}", | |
| 200 | + load_commands.contains("LC_UUID"), | |
| 201 | + "linked binary at {} should carry LC_UUID so dyld accepts it:\n{}", | |
| 170 | 202 | opt, |
| 171 | 203 | load_commands |
| 172 | 204 | ); |
| 173 | - let first = fs::read(&bin_path).expect("cannot read first binary image"); | |
| 205 | + let first = normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image")); | |
| 174 | 206 | |
| 175 | 207 | compile_binary(&compiler, &source, opt, &bin_path); |
| 176 | - let second = fs::read(&bin_path).expect("cannot read second binary image"); | |
| 208 | + let second = | |
| 209 | + normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image")); | |
| 177 | 210 | |
| 178 | 211 | assert_eq!( |
| 179 | 212 | first, second, |
| 180 | - "linked binary should be byte-identical when rebuilt at the same output path ({})", | |
| 213 | + "linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({})", | |
| 181 | 214 | opt |
| 182 | 215 | ); |
| 183 | 216 | |
@@ -202,19 +235,20 @@ fn linked_runtime_heavy_binary_is_deterministic_for_section_reads() { | ||
| 202 | 235 | compile_binary(&compiler, &source, opt, &bin_path); |
| 203 | 236 | let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]); |
| 204 | 237 | assert!( |
| 205 | - !load_commands.contains("LC_UUID"), | |
| 206 | - "runtime-heavy linked binary at {} should omit LC_UUID:\n{}", | |
| 238 | + load_commands.contains("LC_UUID"), | |
| 239 | + "runtime-heavy linked binary at {} should carry LC_UUID:\n{}", | |
| 207 | 240 | opt, |
| 208 | 241 | load_commands |
| 209 | 242 | ); |
| 210 | - let first = fs::read(&bin_path).expect("cannot read first binary image"); | |
| 243 | + let first = normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image")); | |
| 211 | 244 | |
| 212 | 245 | compile_binary(&compiler, &source, opt, &bin_path); |
| 213 | - let second = fs::read(&bin_path).expect("cannot read second binary image"); | |
| 246 | + let second = | |
| 247 | + normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image")); | |
| 214 | 248 | |
| 215 | 249 | assert_eq!( |
| 216 | 250 | first, second, |
| 217 | - "runtime-heavy linked binary should be byte-identical when rebuilt at the same output path ({})", | |
| 251 | + "runtime-heavy linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({})", | |
| 218 | 252 | opt |
| 219 | 253 | ); |
| 220 | 254 | |
tests/cli_driver.rsmodified@@ -3110,6 +3110,57 @@ fn use_renamed_procedure_call_uses_remote_symbol() { | ||
| 3110 | 3110 | let _ = std::fs::remove_dir_all(&dir); |
| 3111 | 3111 | } |
| 3112 | 3112 | |
| 3113 | +#[test] | |
| 3114 | +fn linked_binary_carries_uuid_and_launches() { | |
| 3115 | + let dir = unique_dir("linked_binary_uuid"); | |
| 3116 | + let src = write_program_in( | |
| 3117 | + &dir, | |
| 3118 | + "hello.f90", | |
| 3119 | + "program hello\n print *, 42\nend program hello\n", | |
| 3120 | + ); | |
| 3121 | + | |
| 3122 | + let exe = dir.join("hello.bin"); | |
| 3123 | + let compile = Command::new(compiler("armfortas")) | |
| 3124 | + .current_dir(&dir) | |
| 3125 | + .args([src.to_str().unwrap(), "-o", exe.to_str().unwrap()]) | |
| 3126 | + .output() | |
| 3127 | + .expect("hello compile spawn failed"); | |
| 3128 | + assert!( | |
| 3129 | + compile.status.success(), | |
| 3130 | + "linked hello should compile: {}", | |
| 3131 | + String::from_utf8_lossy(&compile.stderr) | |
| 3132 | + ); | |
| 3133 | + | |
| 3134 | + let otool = Command::new("otool") | |
| 3135 | + .args(["-l", exe.to_str().unwrap()]) | |
| 3136 | + .output() | |
| 3137 | + .expect("otool spawn failed"); | |
| 3138 | + assert!( | |
| 3139 | + otool.status.success(), | |
| 3140 | + "otool should inspect linked hello: {}", | |
| 3141 | + String::from_utf8_lossy(&otool.stderr) | |
| 3142 | + ); | |
| 3143 | + let load_commands = String::from_utf8_lossy(&otool.stdout); | |
| 3144 | + assert!( | |
| 3145 | + load_commands.contains("LC_UUID"), | |
| 3146 | + "linked hello should carry LC_UUID so dyld accepts it:\n{}", | |
| 3147 | + load_commands | |
| 3148 | + ); | |
| 3149 | + | |
| 3150 | + let run = Command::new(&exe) | |
| 3151 | + .current_dir(&dir) | |
| 3152 | + .output() | |
| 3153 | + .expect("hello run spawn failed"); | |
| 3154 | + assert!( | |
| 3155 | + run.status.success(), | |
| 3156 | + "linked hello should launch successfully:\nstdout:\n{}\nstderr:\n{}", | |
| 3157 | + String::from_utf8_lossy(&run.stdout), | |
| 3158 | + String::from_utf8_lossy(&run.stderr) | |
| 3159 | + ); | |
| 3160 | + | |
| 3161 | + let _ = std::fs::remove_dir_all(&dir); | |
| 3162 | +} | |
| 3163 | + | |
| 3113 | 3164 | #[test] |
| 3114 | 3165 | fn same_named_module_helpers_link_without_colliding() { |
| 3115 | 3166 | let dir = unique_dir("module_helper_link_names"); |
@@ -3211,6 +3262,107 @@ fn same_named_module_helpers_link_without_colliding() { | ||
| 3211 | 3262 | let _ = std::fs::remove_dir_all(&dir); |
| 3212 | 3263 | } |
| 3213 | 3264 | |
| 3265 | +#[test] | |
| 3266 | +fn contained_helpers_link_without_cross_object_internal_symbol_collisions() { | |
| 3267 | + let dir = unique_dir("contained_helper_link_names"); | |
| 3268 | + let mod_a = write_program_in( | |
| 3269 | + &dir, | |
| 3270 | + "mod_a.f90", | |
| 3271 | + "module mod_a\ncontains\n subroutine run_a()\n implicit none\n call helper()\n contains\n subroutine helper()\n print *, 11\n end subroutine helper\n end subroutine run_a\nend module mod_a\n", | |
| 3272 | + ); | |
| 3273 | + let mod_b = write_program_in( | |
| 3274 | + &dir, | |
| 3275 | + "mod_b.f90", | |
| 3276 | + "module mod_b\ncontains\n subroutine run_b()\n implicit none\n call helper()\n contains\n subroutine helper()\n print *, 22\n end subroutine helper\n end subroutine run_b\nend module mod_b\n", | |
| 3277 | + ); | |
| 3278 | + let main_src = write_program_in( | |
| 3279 | + &dir, | |
| 3280 | + "main.f90", | |
| 3281 | + "program p\n use mod_a, only: run_a\n use mod_b, only: run_b\n call run_a()\n call run_b()\nend program p\n", | |
| 3282 | + ); | |
| 3283 | + | |
| 3284 | + let mod_a_obj = dir.join("mod_a.o"); | |
| 3285 | + let compile_a = Command::new(compiler("armfortas")) | |
| 3286 | + .current_dir(&dir) | |
| 3287 | + .args([ | |
| 3288 | + "-c", | |
| 3289 | + "-J", | |
| 3290 | + dir.to_str().unwrap(), | |
| 3291 | + mod_a.to_str().unwrap(), | |
| 3292 | + "-o", | |
| 3293 | + mod_a_obj.to_str().unwrap(), | |
| 3294 | + ]) | |
| 3295 | + .output() | |
| 3296 | + .expect("mod_a compile spawn failed"); | |
| 3297 | + assert!( | |
| 3298 | + compile_a.status.success(), | |
| 3299 | + "mod_a should compile: {}", | |
| 3300 | + String::from_utf8_lossy(&compile_a.stderr) | |
| 3301 | + ); | |
| 3302 | + | |
| 3303 | + let mod_b_obj = dir.join("mod_b.o"); | |
| 3304 | + let compile_b = Command::new(compiler("armfortas")) | |
| 3305 | + .current_dir(&dir) | |
| 3306 | + .args([ | |
| 3307 | + "-c", | |
| 3308 | + "-I", | |
| 3309 | + dir.to_str().unwrap(), | |
| 3310 | + "-J", | |
| 3311 | + dir.to_str().unwrap(), | |
| 3312 | + mod_b.to_str().unwrap(), | |
| 3313 | + "-o", | |
| 3314 | + mod_b_obj.to_str().unwrap(), | |
| 3315 | + ]) | |
| 3316 | + .output() | |
| 3317 | + .expect("mod_b compile spawn failed"); | |
| 3318 | + assert!( | |
| 3319 | + compile_b.status.success(), | |
| 3320 | + "mod_b should compile: {}", | |
| 3321 | + String::from_utf8_lossy(&compile_b.stderr) | |
| 3322 | + ); | |
| 3323 | + | |
| 3324 | + let main_obj = dir.join("main.o"); | |
| 3325 | + let compile_main = Command::new(compiler("armfortas")) | |
| 3326 | + .current_dir(&dir) | |
| 3327 | + .args([ | |
| 3328 | + "-c", | |
| 3329 | + "-I", | |
| 3330 | + dir.to_str().unwrap(), | |
| 3331 | + "-J", | |
| 3332 | + dir.to_str().unwrap(), | |
| 3333 | + main_src.to_str().unwrap(), | |
| 3334 | + "-o", | |
| 3335 | + main_obj.to_str().unwrap(), | |
| 3336 | + ]) | |
| 3337 | + .output() | |
| 3338 | + .expect("main compile spawn failed"); | |
| 3339 | + assert!( | |
| 3340 | + compile_main.status.success(), | |
| 3341 | + "main should compile: {}", | |
| 3342 | + String::from_utf8_lossy(&compile_main.stderr) | |
| 3343 | + ); | |
| 3344 | + | |
| 3345 | + let exe = dir.join("contained_helpers.bin"); | |
| 3346 | + let link = Command::new(compiler("armfortas")) | |
| 3347 | + .current_dir(&dir) | |
| 3348 | + .args([ | |
| 3349 | + mod_a_obj.to_str().unwrap(), | |
| 3350 | + mod_b_obj.to_str().unwrap(), | |
| 3351 | + main_obj.to_str().unwrap(), | |
| 3352 | + "-o", | |
| 3353 | + exe.to_str().unwrap(), | |
| 3354 | + ]) | |
| 3355 | + .output() | |
| 3356 | + .expect("contained helper link spawn failed"); | |
| 3357 | + assert!( | |
| 3358 | + link.status.success(), | |
| 3359 | + "contained helpers in different objects should link cleanly: {}", | |
| 3360 | + String::from_utf8_lossy(&link.stderr) | |
| 3361 | + ); | |
| 3362 | + | |
| 3363 | + let _ = std::fs::remove_dir_all(&dir); | |
| 3364 | +} | |
| 3365 | + | |
| 3214 | 3366 | #[test] |
| 3215 | 3367 | fn program_internal_char_helper_assignment_uses_internal_symbol() { |
| 3216 | 3368 | let dir = unique_dir("program_internal_char_helper"); |
tests/i128_cross_object.rsmodified@@ -159,7 +159,6 @@ fn link_objects(objects: &[&Path], output: &Path) { | ||
| 159 | 159 | args.push(rt_path.to_string_lossy().into_owned()); |
| 160 | 160 | args.extend([ |
| 161 | 161 | "-lSystem".into(), |
| 162 | - "-no_uuid".into(), | |
| 163 | 162 | "-syslibroot".into(), |
| 164 | 163 | sysroot, |
| 165 | 164 | "-e".into(), |
@@ -204,6 +203,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String { | ||
| 204 | 203 | String::from_utf8_lossy(&output.stdout).into_owned() |
| 205 | 204 | } |
| 206 | 205 | |
| 206 | +fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> { | |
| 207 | + const MH_MAGIC_64: u32 = 0xfeedfacf; | |
| 208 | + const LC_UUID: u32 = 0x1b; | |
| 209 | + const MACH_HEADER_64_SIZE: usize = 32; | |
| 210 | + | |
| 211 | + if bytes.len() < MACH_HEADER_64_SIZE { | |
| 212 | + return bytes; | |
| 213 | + } | |
| 214 | + let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); | |
| 215 | + if magic != MH_MAGIC_64 { | |
| 216 | + return bytes; | |
| 217 | + } | |
| 218 | + let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize; | |
| 219 | + let mut offset = MACH_HEADER_64_SIZE; | |
| 220 | + for _ in 0..ncmds { | |
| 221 | + if offset + 8 > bytes.len() { | |
| 222 | + break; | |
| 223 | + } | |
| 224 | + let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()); | |
| 225 | + let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()); | |
| 226 | + let cmdsize = cmdsize as usize; | |
| 227 | + if cmdsize < 8 || offset + cmdsize > bytes.len() { | |
| 228 | + break; | |
| 229 | + } | |
| 230 | + if cmd == LC_UUID && cmdsize >= 24 { | |
| 231 | + bytes[offset + 8..offset + 24].fill(0); | |
| 232 | + } | |
| 233 | + offset += cmdsize; | |
| 234 | + } | |
| 235 | + bytes | |
| 236 | +} | |
| 237 | + | |
| 207 | 238 | fn run_cross_object_case_named( |
| 208 | 239 | program_name: &str, |
| 209 | 240 | helper_name: &str, |
@@ -290,18 +321,18 @@ fn deterministic_cross_object_case_named( | ||
| 290 | 321 | link_objects(&[&fortran_obj, &helper_obj], &binary); |
| 291 | 322 | let load_commands = tool_output("otool", &["-l", binary.to_str().unwrap()]); |
| 292 | 323 | assert!( |
| 293 | - !load_commands.contains("LC_UUID"), | |
| 294 | - "linked cross-object integer(16) binary should omit LC_UUID:\n{}", | |
| 324 | + load_commands.contains("LC_UUID"), | |
| 325 | + "linked cross-object integer(16) binary should carry LC_UUID:\n{}", | |
| 295 | 326 | load_commands |
| 296 | 327 | ); |
| 297 | - let first = fs::read(&binary).expect("cannot read first linked binary"); | |
| 328 | + let first = normalize_lc_uuid(fs::read(&binary).expect("cannot read first linked binary")); | |
| 298 | 329 | |
| 299 | 330 | link_objects(&[&fortran_obj, &helper_obj], &binary); |
| 300 | - let second = fs::read(&binary).expect("cannot read second linked binary"); | |
| 331 | + let second = normalize_lc_uuid(fs::read(&binary).expect("cannot read second linked binary")); | |
| 301 | 332 | |
| 302 | 333 | assert_eq!( |
| 303 | 334 | first, second, |
| 304 | - "linked cross-object integer(16) binary should be byte-identical across rebuilds" | |
| 335 | + "linked cross-object integer(16) binary should stay deterministic modulo LC_UUID" | |
| 305 | 336 | ); |
| 306 | 337 | |
| 307 | 338 | let _ = fs::remove_file(&fortran_obj); |
tests/sprint29_audit_realworld.rsmodified@@ -63,6 +63,38 @@ fn tool_output(tool: &str, args: &[&str]) -> String { | ||
| 63 | 63 | String::from_utf8_lossy(&output.stdout).into_owned() |
| 64 | 64 | } |
| 65 | 65 | |
| 66 | +fn normalize_lc_uuid(mut bytes: Vec<u8>) -> Vec<u8> { | |
| 67 | + const MH_MAGIC_64: u32 = 0xfeedfacf; | |
| 68 | + const LC_UUID: u32 = 0x1b; | |
| 69 | + const MACH_HEADER_64_SIZE: usize = 32; | |
| 70 | + | |
| 71 | + if bytes.len() < MACH_HEADER_64_SIZE { | |
| 72 | + return bytes; | |
| 73 | + } | |
| 74 | + let magic = u32::from_le_bytes(bytes[0..4].try_into().unwrap()); | |
| 75 | + if magic != MH_MAGIC_64 { | |
| 76 | + return bytes; | |
| 77 | + } | |
| 78 | + let ncmds = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize; | |
| 79 | + let mut offset = MACH_HEADER_64_SIZE; | |
| 80 | + for _ in 0..ncmds { | |
| 81 | + if offset + 8 > bytes.len() { | |
| 82 | + break; | |
| 83 | + } | |
| 84 | + let cmd = u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()); | |
| 85 | + let cmdsize = u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()); | |
| 86 | + let cmdsize = cmdsize as usize; | |
| 87 | + if cmdsize < 8 || offset + cmdsize > bytes.len() { | |
| 88 | + break; | |
| 89 | + } | |
| 90 | + if cmd == LC_UUID && cmdsize >= 24 { | |
| 91 | + bytes[offset + 8..offset + 24].fill(0); | |
| 92 | + } | |
| 93 | + offset += cmdsize; | |
| 94 | + } | |
| 95 | + bytes | |
| 96 | +} | |
| 97 | + | |
| 66 | 98 | #[test] |
| 67 | 99 | fn realworld_object_snapshots_stay_deterministic_at_o2() { |
| 68 | 100 | for name in [ |
@@ -154,7 +186,7 @@ fn realworld_opt_ir_differs_from_raw_ir_at_o2() { | ||
| 154 | 186 | } |
| 155 | 187 | |
| 156 | 188 | #[test] |
| 157 | -fn linked_realworld_binaries_are_deterministic_and_uuid_free() { | |
| 189 | +fn linked_realworld_binaries_are_deterministic_modulo_uuid() { | |
| 158 | 190 | let compiler = find_compiler(); |
| 159 | 191 | |
| 160 | 192 | for name in [ |
@@ -188,20 +220,22 @@ fn linked_realworld_binaries_are_deterministic_and_uuid_free() { | ||
| 188 | 220 | compile_binary(&compiler, &source, opt, &bin_path); |
| 189 | 221 | let load_commands = tool_output("otool", &["-l", bin_path.to_str().unwrap()]); |
| 190 | 222 | assert!( |
| 191 | - !load_commands.contains("LC_UUID"), | |
| 192 | - "linked binary at {} should omit LC_UUID for {}:\n{}", | |
| 223 | + load_commands.contains("LC_UUID"), | |
| 224 | + "linked binary at {} should carry LC_UUID for {}:\n{}", | |
| 193 | 225 | opt, |
| 194 | 226 | name, |
| 195 | 227 | load_commands |
| 196 | 228 | ); |
| 197 | - let first = fs::read(&bin_path).expect("cannot read first binary image"); | |
| 229 | + let first = | |
| 230 | + normalize_lc_uuid(fs::read(&bin_path).expect("cannot read first binary image")); | |
| 198 | 231 | |
| 199 | 232 | compile_binary(&compiler, &source, opt, &bin_path); |
| 200 | - let second = fs::read(&bin_path).expect("cannot read second binary image"); | |
| 233 | + let second = | |
| 234 | + normalize_lc_uuid(fs::read(&bin_path).expect("cannot read second binary image")); | |
| 201 | 235 | |
| 202 | 236 | assert_eq!( |
| 203 | 237 | first, second, |
| 204 | - "real-world linked binary should be byte-identical when rebuilt at the same output path ({} {})", | |
| 238 | + "real-world linked binary should stay deterministic modulo LC_UUID when rebuilt at the same output path ({} {})", | |
| 205 | 239 | name, |
| 206 | 240 | opt |
| 207 | 241 | ); |