Fix fgof-temp file semantics
- SHA
f8aae86920dc51a9a51a183aa4e146ec5be43b04- Parents
-
cc571da - Tree
7402ad4
f8aae86
f8aae86920dc51a9a51a183aa4e146ec5be43b04cc571da
7402ad4| Status | File | + | - |
|---|---|---|---|
| M |
runtime/src/io_system.rs
|
133 | 17 |
| M |
src/ir/lower.rs
|
63 | 3 |
| M |
tests/cli_driver.rs
|
122 | 0 |
runtime/src/io_system.rsmodified@@ -97,6 +97,10 @@ struct Unit { | ||
| 97 | 97 | } |
| 98 | 98 | |
| 99 | 99 | impl Unit { |
| 100 | + fn is_stream_unformatted(&self) -> bool { | |
| 101 | + self.form == Form::Unformatted && self.access == Access::Stream | |
| 102 | + } | |
| 103 | + | |
| 100 | 104 | fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> { |
| 101 | 105 | match &mut self.stream { |
| 102 | 106 | UnitStream::Stdout => { |
@@ -510,15 +514,43 @@ pub extern "C" fn afs_open(cb: *const OpenControlBlock) { | ||
| 510 | 514 | /// Close a unit. |
| 511 | 515 | #[no_mangle] |
| 512 | 516 | pub extern "C" fn afs_close(unit: i32, iostat: *mut i32) { |
| 517 | + afs_close_ex(unit, std::ptr::null(), 0, iostat); | |
| 518 | +} | |
| 519 | + | |
| 520 | +/// Close a unit with optional STATUS= semantics. | |
| 521 | +#[no_mangle] | |
| 522 | +pub extern "C" fn afs_close_ex(unit: i32, status: *const u8, status_len: i64, iostat: *mut i32) { | |
| 523 | + let delete_on_close = if status.is_null() || status_len <= 0 { | |
| 524 | + false | |
| 525 | + } else { | |
| 526 | + let raw = unsafe { std::slice::from_raw_parts(status, status_len as usize) }; | |
| 527 | + std::str::from_utf8(raw) | |
| 528 | + .map(|s| s.trim().eq_ignore_ascii_case("delete")) | |
| 529 | + .unwrap_or(false) | |
| 530 | + }; | |
| 531 | + | |
| 513 | 532 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 514 | 533 | if let Some(mut u) = state.units.remove(&unit) { |
| 515 | 534 | let _ = u.flush(); |
| 516 | - // File is dropped here, closing the handle. | |
| 517 | - if !iostat.is_null() { | |
| 518 | - unsafe { | |
| 519 | - *iostat = 0; | |
| 535 | + let filename = u.filename.clone(); | |
| 536 | + drop(u); | |
| 537 | + | |
| 538 | + let mut close_status = 0; | |
| 539 | + if delete_on_close | |
| 540 | + && !matches!(filename.as_str(), "stdin" | "stdout" | "stderr") | |
| 541 | + && !filename.is_empty() | |
| 542 | + { | |
| 543 | + if let Err(e) = std::fs::remove_file(&filename) { | |
| 544 | + close_status = e.raw_os_error().unwrap_or(1); | |
| 520 | 545 | } |
| 521 | 546 | } |
| 547 | + | |
| 548 | + if !iostat.is_null() { | |
| 549 | + unsafe { *iostat = close_status }; | |
| 550 | + } else if close_status != 0 { | |
| 551 | + eprintln!("CLOSE: {}: {}", filename, io::Error::from_raw_os_error(close_status)); | |
| 552 | + std::process::exit(1); | |
| 553 | + } | |
| 522 | 554 | } else { |
| 523 | 555 | if !iostat.is_null() { |
| 524 | 556 | unsafe { |
@@ -535,7 +567,11 @@ pub extern "C" fn afs_close(unit: i32, iostat: *mut i32) { | ||
| 535 | 567 | pub extern "C" fn afs_write_int(unit: i32, val: i32) { |
| 536 | 568 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 537 | 569 | if let Some(u) = state.get_unit(unit) { |
| 538 | - let _ = u.write_str(&format!(" {}", val)); | |
| 570 | + if u.is_stream_unformatted() { | |
| 571 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 572 | + } else { | |
| 573 | + let _ = u.write_str(&format!(" {}", val)); | |
| 574 | + } | |
| 539 | 575 | } |
| 540 | 576 | } |
| 541 | 577 | |
@@ -544,7 +580,11 @@ pub extern "C" fn afs_write_int(unit: i32, val: i32) { | ||
| 544 | 580 | pub extern "C" fn afs_write_int64(unit: i32, val: i64) { |
| 545 | 581 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 546 | 582 | if let Some(u) = state.get_unit(unit) { |
| 547 | - let _ = u.write_str(&format!(" {}", val)); | |
| 583 | + if u.is_stream_unformatted() { | |
| 584 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 585 | + } else { | |
| 586 | + let _ = u.write_str(&format!(" {}", val)); | |
| 587 | + } | |
| 548 | 588 | } |
| 549 | 589 | } |
| 550 | 590 | |
@@ -553,7 +593,11 @@ pub extern "C" fn afs_write_int64(unit: i32, val: i64) { | ||
| 553 | 593 | pub extern "C" fn afs_write_int128(unit: i32, val: i128) { |
| 554 | 594 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 555 | 595 | if let Some(u) = state.get_unit(unit) { |
| 556 | - let _ = u.write_str(&format!(" {}", val)); | |
| 596 | + if u.is_stream_unformatted() { | |
| 597 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 598 | + } else { | |
| 599 | + let _ = u.write_str(&format!(" {}", val)); | |
| 600 | + } | |
| 557 | 601 | } |
| 558 | 602 | } |
| 559 | 603 | |
@@ -562,7 +606,11 @@ pub extern "C" fn afs_write_int128(unit: i32, val: i128) { | ||
| 562 | 606 | pub extern "C" fn afs_write_real(unit: i32, val: f32) { |
| 563 | 607 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 564 | 608 | if let Some(u) = state.get_unit(unit) { |
| 565 | - let _ = u.write_str(&format!(" {:14.7E}", val)); | |
| 609 | + if u.is_stream_unformatted() { | |
| 610 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 611 | + } else { | |
| 612 | + let _ = u.write_str(&format!(" {:14.7E}", val)); | |
| 613 | + } | |
| 566 | 614 | } |
| 567 | 615 | } |
| 568 | 616 | |
@@ -571,7 +619,11 @@ pub extern "C" fn afs_write_real(unit: i32, val: f32) { | ||
| 571 | 619 | pub extern "C" fn afs_write_real64(unit: i32, val: f64) { |
| 572 | 620 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 573 | 621 | if let Some(u) = state.get_unit(unit) { |
| 574 | - let _ = u.write_str(&format!(" {:22.15E}", val)); | |
| 622 | + if u.is_stream_unformatted() { | |
| 623 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 624 | + } else { | |
| 625 | + let _ = u.write_str(&format!(" {:22.15E}", val)); | |
| 626 | + } | |
| 575 | 627 | } |
| 576 | 628 | } |
| 577 | 629 | |
@@ -582,7 +634,12 @@ pub extern "C" fn afs_write_complex_f32(unit: i32, ptr: *const f32) { | ||
| 582 | 634 | let (re, im) = unsafe { (*ptr, *ptr.add(1)) }; |
| 583 | 635 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 584 | 636 | if let Some(u) = state.get_unit(unit) { |
| 585 | - let _ = u.write_str(&format!(" ({:14.7E},{:14.7E})", re, im)); | |
| 637 | + if u.is_stream_unformatted() { | |
| 638 | + let _ = u.write_raw(&re.to_ne_bytes()); | |
| 639 | + let _ = u.write_raw(&im.to_ne_bytes()); | |
| 640 | + } else { | |
| 641 | + let _ = u.write_str(&format!(" ({:14.7E},{:14.7E})", re, im)); | |
| 642 | + } | |
| 586 | 643 | } |
| 587 | 644 | } |
| 588 | 645 | |
@@ -593,7 +650,12 @@ pub extern "C" fn afs_write_complex_f64(unit: i32, ptr: *const f64) { | ||
| 593 | 650 | let (re, im) = unsafe { (*ptr, *ptr.add(1)) }; |
| 594 | 651 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 595 | 652 | if let Some(u) = state.get_unit(unit) { |
| 596 | - let _ = u.write_str(&format!(" ({:22.15E},{:22.15E})", re, im)); | |
| 653 | + if u.is_stream_unformatted() { | |
| 654 | + let _ = u.write_raw(&re.to_ne_bytes()); | |
| 655 | + let _ = u.write_raw(&im.to_ne_bytes()); | |
| 656 | + } else { | |
| 657 | + let _ = u.write_str(&format!(" ({:22.15E},{:22.15E})", re, im)); | |
| 658 | + } | |
| 597 | 659 | } |
| 598 | 660 | } |
| 599 | 661 | |
@@ -602,10 +664,17 @@ pub extern "C" fn afs_write_complex_f64(unit: i32, ptr: *const f64) { | ||
| 602 | 664 | pub extern "C" fn afs_write_string(unit: i32, ptr: *const u8, len: i64) { |
| 603 | 665 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 604 | 666 | if let Some(u) = state.get_unit(unit) { |
| 605 | - let _ = u.write_str(" "); | |
| 606 | - if !ptr.is_null() && len > 0 { | |
| 607 | - let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) }; | |
| 608 | - let _ = u.write_bytes(slice); | |
| 667 | + if u.is_stream_unformatted() { | |
| 668 | + if !ptr.is_null() && len > 0 { | |
| 669 | + let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) }; | |
| 670 | + let _ = u.write_raw(slice); | |
| 671 | + } | |
| 672 | + } else { | |
| 673 | + let _ = u.write_str(" "); | |
| 674 | + if !ptr.is_null() && len > 0 { | |
| 675 | + let slice = unsafe { std::slice::from_raw_parts(ptr, len as usize) }; | |
| 676 | + let _ = u.write_bytes(slice); | |
| 677 | + } | |
| 609 | 678 | } |
| 610 | 679 | } |
| 611 | 680 | } |
@@ -615,7 +684,11 @@ pub extern "C" fn afs_write_string(unit: i32, ptr: *const u8, len: i64) { | ||
| 615 | 684 | pub extern "C" fn afs_write_logical(unit: i32, val: i32) { |
| 616 | 685 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 617 | 686 | if let Some(u) = state.get_unit(unit) { |
| 618 | - let _ = u.write_str(if val != 0 { " T" } else { " F" }); | |
| 687 | + if u.is_stream_unformatted() { | |
| 688 | + let _ = u.write_raw(&val.to_ne_bytes()); | |
| 689 | + } else { | |
| 690 | + let _ = u.write_str(if val != 0 { " T" } else { " F" }); | |
| 691 | + } | |
| 619 | 692 | } |
| 620 | 693 | } |
| 621 | 694 | |
@@ -624,6 +697,10 @@ pub extern "C" fn afs_write_logical(unit: i32, val: i32) { | ||
| 624 | 697 | pub extern "C" fn afs_write_newline(unit: i32) { |
| 625 | 698 | let mut state = io_state().lock().unwrap_or_else(|e| e.into_inner()); |
| 626 | 699 | if let Some(u) = state.get_unit(unit) { |
| 700 | + if u.is_stream_unformatted() { | |
| 701 | + let _ = u.flush(); | |
| 702 | + return; | |
| 703 | + } | |
| 627 | 704 | let _ = u.write_str("\n"); |
| 628 | 705 | let _ = u.flush(); |
| 629 | 706 | } |
@@ -943,7 +1020,12 @@ pub extern "C" fn afs_read_string(unit: i32, dest: *mut u8, dest_len: i64, iosta | ||
| 943 | 1020 | |
| 944 | 1021 | match u.next_read_token() { |
| 945 | 1022 | Ok(Some(token)) => { |
| 946 | - crate::string::afs_assign_char_fixed(dest, dest_len, token.as_ptr(), token.len() as i64); | |
| 1023 | + crate::string::afs_assign_char_fixed( | |
| 1024 | + dest, | |
| 1025 | + dest_len, | |
| 1026 | + token.as_ptr(), | |
| 1027 | + token.len() as i64, | |
| 1028 | + ); | |
| 947 | 1029 | if !iostat.is_null() { |
| 948 | 1030 | unsafe { |
| 949 | 1031 | *iostat = 0; |
@@ -3268,6 +3350,40 @@ mod tests { | ||
| 3268 | 3350 | afs_write_newline(6); |
| 3269 | 3351 | } |
| 3270 | 3352 | |
| 3353 | + #[test] | |
| 3354 | + fn stream_unformatted_string_write_preserves_exact_bytes() { | |
| 3355 | + let path = "/tmp/afs_stream_unformatted_string_write.dat"; | |
| 3356 | + let mut iostat = -99i32; | |
| 3357 | + let cb = OpenControlBlock { | |
| 3358 | + unit: 94, | |
| 3359 | + filename: path.as_ptr(), | |
| 3360 | + filename_len: path.len() as i64, | |
| 3361 | + status: "replace".as_ptr(), | |
| 3362 | + status_len: 7, | |
| 3363 | + action: "write".as_ptr(), | |
| 3364 | + action_len: 5, | |
| 3365 | + access: "stream".as_ptr(), | |
| 3366 | + access_len: 6, | |
| 3367 | + form: "unformatted".as_ptr(), | |
| 3368 | + form_len: 11, | |
| 3369 | + recl: 0, | |
| 3370 | + iostat: &mut iostat, | |
| 3371 | + newunit: std::ptr::null_mut(), | |
| 3372 | + position: std::ptr::null(), | |
| 3373 | + position_len: 0, | |
| 3374 | + }; | |
| 3375 | + | |
| 3376 | + afs_open(&cb); | |
| 3377 | + assert_eq!(iostat, 0, "expected stream-unformatted OPEN to succeed"); | |
| 3378 | + | |
| 3379 | + afs_write_string(94, "alpha".as_ptr(), 5); | |
| 3380 | + afs_write_newline(94); | |
| 3381 | + afs_close(94, std::ptr::null_mut()); | |
| 3382 | + | |
| 3383 | + let content = std::fs::read(path).unwrap(); | |
| 3384 | + assert_eq!(content, b"alpha", "expected exact stream bytes"); | |
| 3385 | + } | |
| 3386 | + | |
| 3271 | 3387 | #[test] |
| 3272 | 3388 | fn write_i128_to_file() { |
| 3273 | 3389 | let path = "/tmp/afs_write_i128_test.dat"; |
src/ir/lower.rsmodified@@ -16228,15 +16228,51 @@ fn lower_stmt(b: &mut FuncBuilder, ctx: &mut LowerCtx, stmt: &SpannedStmt) { | ||
| 16228 | 16228 | } |
| 16229 | 16229 | |
| 16230 | 16230 | Stmt::Close { specs } => { |
| 16231 | - let unit = if let Some(s) = specs.first() { | |
| 16231 | + let unit_spec = specs | |
| 16232 | + .iter() | |
| 16233 | + .find(|s| { | |
| 16234 | + s.keyword | |
| 16235 | + .as_deref() | |
| 16236 | + .map(|k| k.eq_ignore_ascii_case("unit")) | |
| 16237 | + .unwrap_or(false) | |
| 16238 | + }) | |
| 16239 | + .or_else(|| specs.iter().find(|s| s.keyword.is_none())); | |
| 16240 | + let iostat_spec = specs.iter().find(|s| { | |
| 16241 | + s.keyword | |
| 16242 | + .as_deref() | |
| 16243 | + .map(|k| k.eq_ignore_ascii_case("iostat")) | |
| 16244 | + .unwrap_or(false) | |
| 16245 | + }); | |
| 16246 | + let status_spec = specs.iter().find(|s| { | |
| 16247 | + s.keyword | |
| 16248 | + .as_deref() | |
| 16249 | + .map(|k| k.eq_ignore_ascii_case("status")) | |
| 16250 | + .unwrap_or(false) | |
| 16251 | + }); | |
| 16252 | + let unit = if let Some(s) = unit_spec { | |
| 16232 | 16253 | lower_expr_ctx(b, ctx, &s.value) |
| 16233 | 16254 | } else { |
| 16234 | 16255 | b.const_i32(6) |
| 16235 | 16256 | }; |
| 16236 | 16257 | let null = b.const_i64(0); |
| 16258 | + let unit_i32 = coerce_to_type(b, unit, &IrType::Int(IntWidth::I32)); | |
| 16259 | + let iostat_ptr = iostat_spec | |
| 16260 | + .map(|spec| lower_arg_by_ref_ctx(b, ctx, &spec.value)) | |
| 16261 | + .unwrap_or(null); | |
| 16262 | + let (status_ptr, status_len) = status_spec | |
| 16263 | + .map(|spec| { | |
| 16264 | + lower_string_expr_with_layouts( | |
| 16265 | + b, | |
| 16266 | + &ctx.locals, | |
| 16267 | + &spec.value, | |
| 16268 | + ctx.st, | |
| 16269 | + Some(ctx.type_layouts), | |
| 16270 | + ) | |
| 16271 | + }) | |
| 16272 | + .unwrap_or_else(|| (null, null)); | |
| 16237 | 16273 | b.call( |
| 16238 | - FuncRef::External("afs_close".into()), | |
| 16239 | - vec![unit, null], | |
| 16274 | + FuncRef::External("afs_close_ex".into()), | |
| 16275 | + vec![unit_i32, status_ptr, status_len, iostat_ptr], | |
| 16240 | 16276 | IrType::Void, |
| 16241 | 16277 | ); |
| 16242 | 16278 | } |
@@ -24015,12 +24051,36 @@ fn emit_derived_value_copy( | ||
| 24015 | 24051 | let src_field = b.gep(src_ptr, vec![offset], IrType::Int(IntWidth::I8)); |
| 24016 | 24052 | |
| 24017 | 24053 | if field.allocatable && is_deferred_char_component_field(field) { |
| 24054 | + let src_alloc = b.call( | |
| 24055 | + FuncRef::External("afs_string_allocated".into()), | |
| 24056 | + vec![src_field], | |
| 24057 | + IrType::Int(IntWidth::I32), | |
| 24058 | + ); | |
| 24059 | + let zero_i32 = b.const_i32(0); | |
| 24060 | + let is_unallocated = b.icmp(CmpOp::Eq, src_alloc, zero_i32); | |
| 24061 | + let unalloc_bb = b.create_block("derived_char_unalloc"); | |
| 24062 | + let copy_bb = b.create_block("derived_char_copy"); | |
| 24063 | + let join_bb = b.create_block("derived_char_copy_join"); | |
| 24064 | + b.cond_branch(is_unallocated, unalloc_bb, vec![], copy_bb, vec![]); | |
| 24065 | + | |
| 24066 | + b.set_block(unalloc_bb); | |
| 24067 | + b.call( | |
| 24068 | + FuncRef::External("afs_dealloc_string".into()), | |
| 24069 | + vec![dest_field], | |
| 24070 | + IrType::Void, | |
| 24071 | + ); | |
| 24072 | + b.branch(join_bb, vec![]); | |
| 24073 | + | |
| 24074 | + b.set_block(copy_bb); | |
| 24018 | 24075 | let (src_data, src_len) = load_string_descriptor_view(b, src_field); |
| 24019 | 24076 | b.call( |
| 24020 | 24077 | FuncRef::External("afs_assign_char_deferred".into()), |
| 24021 | 24078 | vec![dest_field, src_data, src_len], |
| 24022 | 24079 | IrType::Void, |
| 24023 | 24080 | ); |
| 24081 | + b.branch(join_bb, vec![]); | |
| 24082 | + | |
| 24083 | + b.set_block(join_bb); | |
| 24024 | 24084 | continue; |
| 24025 | 24085 | } |
| 24026 | 24086 | |
tests/cli_driver.rsmodified@@ -496,6 +496,50 @@ fn stream_unformatted_scalar_char_read_preserves_each_byte() { | ||
| 496 | 496 | let _ = std::fs::remove_file(&src); |
| 497 | 497 | } |
| 498 | 498 | |
| 499 | +#[test] | |
| 500 | +fn stream_unformatted_char_write_preserves_exact_bytes() { | |
| 501 | + let output_file = unique_path("stream_unformatted_char_write", "bin"); | |
| 502 | + let src = write_program( | |
| 503 | + &format!( | |
| 504 | + "program p\n implicit none\n integer :: unit_num, ios\n open(newunit=unit_num, file='{}', status='replace', action='write', access='stream', form='unformatted', iostat=ios)\n if (ios /= 0) error stop 1\n write(unit_num, iostat=ios) 'alpha'\n if (ios /= 0) error stop 2\n close(unit_num)\nend program\n", | |
| 505 | + output_file.display() | |
| 506 | + ), | |
| 507 | + "stream_unformatted_char_write.f90", | |
| 508 | + ); | |
| 509 | + let out = unique_path("stream_unformatted_char_write", "bin"); | |
| 510 | + let compile = Command::new(compiler("armfortas")) | |
| 511 | + .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()]) | |
| 512 | + .output() | |
| 513 | + .expect("stream unformatted char write compile failed to spawn"); | |
| 514 | + assert!( | |
| 515 | + compile.status.success(), | |
| 516 | + "stream unformatted char write compile failed: {}", | |
| 517 | + String::from_utf8_lossy(&compile.stderr) | |
| 518 | + ); | |
| 519 | + | |
| 520 | + let run = Command::new(&out) | |
| 521 | + .output() | |
| 522 | + .expect("stream unformatted char write run failed"); | |
| 523 | + assert!( | |
| 524 | + run.status.success(), | |
| 525 | + "stream unformatted char write run failed: status={:?} stderr={}", | |
| 526 | + run.status, | |
| 527 | + String::from_utf8_lossy(&run.stderr) | |
| 528 | + ); | |
| 529 | + | |
| 530 | + let bytes = std::fs::read(&output_file).expect("cannot read stream unformatted char output"); | |
| 531 | + assert_eq!( | |
| 532 | + bytes, | |
| 533 | + b"alpha", | |
| 534 | + "expected exact bytes from stream-unformatted character write, got {:?}", | |
| 535 | + bytes | |
| 536 | + ); | |
| 537 | + | |
| 538 | + let _ = std::fs::remove_file(&output_file); | |
| 539 | + let _ = std::fs::remove_file(&out); | |
| 540 | + let _ = std::fs::remove_file(&src); | |
| 541 | +} | |
| 542 | + | |
| 499 | 543 | #[test] |
| 500 | 544 | fn repeated_nonadvancing_a1_read_preserves_embedded_nul_bytes() { |
| 501 | 545 | let input = unique_path("nonadvancing_a1_char_read", "bin"); |
@@ -2780,6 +2824,47 @@ fn contained_char_function_in_comparison_uses_internal_call_target() { | ||
| 2780 | 2824 | let _ = std::fs::remove_dir_all(&dir); |
| 2781 | 2825 | } |
| 2782 | 2826 | |
| 2827 | +#[test] | |
| 2828 | +fn close_status_delete_removes_file() { | |
| 2829 | + let path = unique_path("close_status_delete", "txt"); | |
| 2830 | + let src = write_program( | |
| 2831 | + &format!( | |
| 2832 | + "program p\n implicit none\n integer :: unit, ios\n open(newunit=unit, file='{}', status='replace', action='write', iostat=ios)\n if (ios /= 0) error stop 1\n write(unit, '(a)', iostat=ios) 'hello'\n if (ios /= 0) error stop 2\n close(unit, status='delete', iostat=ios)\n if (ios /= 0) error stop 3\n print *, 'ok'\nend program\n", | |
| 2833 | + path.display() | |
| 2834 | + ), | |
| 2835 | + "f90", | |
| 2836 | + ); | |
| 2837 | + let out = unique_path("close_status_delete", "bin"); | |
| 2838 | + let compile = Command::new(compiler("armfortas")) | |
| 2839 | + .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()]) | |
| 2840 | + .output() | |
| 2841 | + .expect("close status delete compile failed to spawn"); | |
| 2842 | + assert!( | |
| 2843 | + compile.status.success(), | |
| 2844 | + "close status delete compile failed: {}", | |
| 2845 | + String::from_utf8_lossy(&compile.stderr) | |
| 2846 | + ); | |
| 2847 | + | |
| 2848 | + let run = Command::new(&out) | |
| 2849 | + .output() | |
| 2850 | + .expect("close status delete run failed"); | |
| 2851 | + assert!( | |
| 2852 | + run.status.success(), | |
| 2853 | + "close status delete run failed: status={:?} stderr={}", | |
| 2854 | + run.status, | |
| 2855 | + String::from_utf8_lossy(&run.stderr) | |
| 2856 | + ); | |
| 2857 | + assert!( | |
| 2858 | + !path.exists(), | |
| 2859 | + "close(status='delete') should remove the file: {}", | |
| 2860 | + path.display() | |
| 2861 | + ); | |
| 2862 | + | |
| 2863 | + let _ = std::fs::remove_file(&out); | |
| 2864 | + let _ = std::fs::remove_file(&src); | |
| 2865 | + let _ = std::fs::remove_file(&path); | |
| 2866 | +} | |
| 2867 | + | |
| 2783 | 2868 | #[test] |
| 2784 | 2869 | fn bind_c_interface_function_returning_c_ptr_runs() { |
| 2785 | 2870 | let dir = unique_dir("bind_c_c_ptr_return"); |
@@ -12539,6 +12624,43 @@ fn empty_allocatable_char_component_copy_stays_allocated() { | ||
| 12539 | 12624 | let _ = std::fs::remove_file(&src); |
| 12540 | 12625 | } |
| 12541 | 12626 | |
| 12627 | +#[test] | |
| 12628 | +fn derived_function_result_keeps_unallocated_allocatable_char_components_unallocated() { | |
| 12629 | + let src = write_program( | |
| 12630 | + "module m\n implicit none\n type :: options_t\n logical :: cleanup_on_close = .true.\n character(len=:), allocatable :: prefix\n character(len=:), allocatable :: suffix\n character(len=:), allocatable :: parent_dir\n end type options_t\ncontains\n function clear_options() result(options)\n type(options_t) :: options\n options%cleanup_on_close = .true.\n end function\nend module\n\nprogram p\n use m\n implicit none\n type(options_t) :: options\n options = clear_options()\n if (.not. options%cleanup_on_close) error stop 1\n if (allocated(options%prefix)) error stop 2\n if (allocated(options%suffix)) error stop 3\n if (allocated(options%parent_dir)) error stop 4\n print *, 'ok'\nend program\n", | |
| 12631 | + "f90", | |
| 12632 | + ); | |
| 12633 | + let out = unique_path("derived_result_unalloc_char_components", "bin"); | |
| 12634 | + let compile = Command::new(compiler("armfortas")) | |
| 12635 | + .args([src.to_str().unwrap(), "-o", out.to_str().unwrap()]) | |
| 12636 | + .output() | |
| 12637 | + .expect("derived result unalloc char component compile failed to spawn"); | |
| 12638 | + assert!( | |
| 12639 | + compile.status.success(), | |
| 12640 | + "derived result unalloc char component compile failed: {}", | |
| 12641 | + String::from_utf8_lossy(&compile.stderr) | |
| 12642 | + ); | |
| 12643 | + | |
| 12644 | + let run = Command::new(&out) | |
| 12645 | + .output() | |
| 12646 | + .expect("derived result unalloc char component run failed"); | |
| 12647 | + assert!( | |
| 12648 | + run.status.success(), | |
| 12649 | + "derived result unalloc char component run failed: status={:?} stderr={}", | |
| 12650 | + run.status, | |
| 12651 | + String::from_utf8_lossy(&run.stderr) | |
| 12652 | + ); | |
| 12653 | + let stdout = String::from_utf8_lossy(&run.stdout); | |
| 12654 | + assert!( | |
| 12655 | + stdout.contains("ok"), | |
| 12656 | + "unexpected derived result unalloc char component output: {}", | |
| 12657 | + stdout | |
| 12658 | + ); | |
| 12659 | + | |
| 12660 | + let _ = std::fs::remove_file(&out); | |
| 12661 | + let _ = std::fs::remove_file(&src); | |
| 12662 | +} | |
| 12663 | + | |
| 12542 | 12664 | #[test] |
| 12543 | 12665 | fn derived_array_growth_keeps_unallocated_allocatable_components_clear() { |
| 12544 | 12666 | let src = write_program( |