1use super::Command;
20use crate::Runtime;
21use crate::trap::run_exit_trap;
22use enumset::EnumSet;
23use itertools::Itertools;
24use std::ops::ControlFlow::{Break, Continue};
25use std::rc::Rc;
26use yash_env::Env;
27use yash_env::io::Fd;
28use yash_env::job::Pid;
29use yash_env::job::add_job_if_suspended;
30use yash_env::option::Option::{Exec, Interactive, PipeFail};
31use yash_env::option::State::{Off, On};
32use yash_env::semantics::Divert;
33use yash_env::semantics::ExitStatus;
34use yash_env::semantics::Result;
35use yash_env::stack::Frame;
36use yash_env::subshell::JobControl;
37use yash_env::subshell::Subshell;
38use yash_env::system::{Close, Dup, Errno, Fcntl, Isatty, Pipe, Write};
39use yash_syntax::syntax;
40
41impl<S: Runtime + 'static> Command<S> for syntax::Pipeline {
79 async fn execute(&self, env: &mut Env<S>) -> Result {
80 if env.options.get(Exec) == Off && env.options.get(Interactive) == Off {
81 return Continue(());
82 }
83
84 if !self.negation {
85 return execute_commands_in_pipeline(env, &self.commands).await;
86 }
87
88 let mut env = env.push_frame(Frame::Condition);
89 execute_commands_in_pipeline(&mut env, &self.commands).await?;
90 env.exit_status = if env.exit_status.is_successful() {
91 ExitStatus::FAILURE
92 } else {
93 ExitStatus::SUCCESS
94 };
95 Continue(())
96 }
97}
98
99async fn execute_commands_in_pipeline<S: Runtime + 'static>(
100 env: &mut Env<S>,
101 commands: &[Rc<syntax::Command>],
102) -> Result {
103 match commands.len() {
104 0 => {
105 env.exit_status = ExitStatus::SUCCESS;
106 Continue(())
107 }
108
109 1 => commands[0].execute(env).await,
110
111 _ => {
112 if env.controls_jobs() {
113 execute_job_controlled_pipeline(env, commands).await?
114 } else {
115 execute_multi_command_pipeline(env, commands).await?
116 }
117 env.apply_errexit()
118 }
119 }
120}
121
122async fn execute_job_controlled_pipeline<S: Runtime + 'static>(
123 env: &mut Env<S>,
124 commands: &[Rc<syntax::Command>],
125) -> Result {
126 let commands_2 = commands.to_vec();
127 let subshell = Subshell::new(|sub_env, _job_control| {
128 Box::pin(async move {
129 let result = execute_multi_command_pipeline(sub_env, &commands_2).await;
130 sub_env.apply_result(result);
131 run_exit_trap(sub_env).await;
132 })
133 })
134 .job_control(JobControl::Foreground);
135
136 match subshell.start_and_wait(env).await {
137 Ok((pid, result)) => {
138 env.exit_status = add_job_if_suspended(env, pid, result, || to_job_name(commands))?;
139 Continue(())
140 }
141 Err(errno) => {
142 let message = format!("cannot start a subshell in the pipeline: {errno}\n");
144 env.system.print_error(&message).await;
145 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
146 }
147 }
148}
149
150fn to_job_name(commands: &[Rc<syntax::Command>]) -> String {
151 commands
152 .iter()
153 .format_with(" | ", |cmd, f| f(&format_args!("{cmd}")))
154 .to_string()
155}
156
157async fn execute_multi_command_pipeline<S: Runtime + 'static>(
158 env: &mut Env<S>,
159 commands: &[Rc<syntax::Command>],
160) -> Result {
161 let mut commands = commands.iter().cloned();
163 let mut pipes = PipeSet::new();
164 let mut pids = Vec::new();
165 while let Some(command) = commands.next() {
166 let has_next = commands.len() > 0; shift_or_fail(env, &mut pipes, has_next).await?;
168
169 let pipes = pipes;
170 let subshell = Subshell::new(move |env, _job_control| {
171 Box::pin(async move {
172 let result = connect_pipe_and_execute_command(env, pipes, command).await;
173 env.apply_result(result);
174 run_exit_trap(env).await;
175 })
176 });
177 let start_result = subshell.start(env).await;
178 pids.push(pid_or_fail(env, start_result).await?);
179 }
180
181 shift_or_fail(env, &mut pipes, false).await?;
182
183 let mut final_exit_status = ExitStatus::SUCCESS;
185 let pipefail = env.options.get(PipeFail) == On;
186 for pid in pids {
187 let exit_status = env
188 .wait_for_subshell_to_finish(pid)
189 .await
190 .expect("cannot receive exit status of child process")
191 .1;
192 if !exit_status.is_successful() || !pipefail {
193 final_exit_status = exit_status;
194 }
195 }
196 env.exit_status = final_exit_status;
197
198 Continue(())
199}
200
201async fn shift_or_fail<S>(env: &mut Env<S>, pipes: &mut PipeSet, has_next: bool) -> Result
202where
203 S: Close + Fcntl + Isatty + Pipe + Write,
204{
205 match pipes.shift(env, has_next) {
206 Ok(()) => Continue(()),
207 Err(errno) => {
208 let message = format!("cannot connect pipes in the pipeline: {errno}\n");
210 env.system.print_error(&message).await;
211 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
212 }
213 }
214}
215
216async fn connect_pipe_and_execute_command<S: Runtime + 'static>(
217 env: &mut Env<S>,
218 pipes: PipeSet,
219 command: Rc<syntax::Command>,
220) -> Result {
221 match pipes.move_to_stdin_stdout(env) {
222 Ok(()) => (),
223 Err(errno) => {
224 let message = format!("cannot connect pipes in the pipeline: {errno}\n");
226 env.system.print_error(&message).await;
227 return Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)));
228 }
229 }
230
231 command.execute(env).await
232}
233
234async fn pid_or_fail<S>(
235 env: &mut Env<S>,
236 start_result: std::result::Result<(Pid, Option<JobControl>), Errno>,
237) -> Result<Pid>
238where
239 S: Fcntl + Isatty + Write,
240{
241 match start_result {
242 Ok((pid, job_control)) => {
243 debug_assert_eq!(job_control, None);
244 Continue(pid)
245 }
246 Err(errno) => {
247 env.system
249 .print_error(&format!(
250 "cannot start a subshell in the pipeline: {errno}\n"
251 ))
252 .await;
253 Break(Divert::Interrupt(Some(ExitStatus::NOEXEC)))
254 }
255 }
256}
257
258#[derive(Clone, Copy, Default)]
260struct PipeSet {
261 read_previous: Option<Fd>,
262 next: Option<(Fd, Fd)>,
264}
265
266impl PipeSet {
267 fn new() -> Self {
268 Self::default()
269 }
270
271 fn shift<S: Pipe + Close>(
276 &mut self,
277 env: &mut Env<S>,
278 has_next: bool,
279 ) -> std::result::Result<(), Errno> {
280 if let Some(fd) = self.read_previous {
281 let _ = env.system.close(fd);
282 }
283
284 if let Some((reader, writer)) = self.next {
285 let _ = env.system.close(writer);
286 self.read_previous = Some(reader);
287 } else {
288 self.read_previous = None;
289 }
290
291 self.next = None;
292 if has_next {
293 self.next = Some(env.system.pipe()?);
294 }
295
296 Ok(())
297 }
298
299 fn move_to_stdin_stdout<S: Dup + Close>(
302 mut self,
303 env: &mut Env<S>,
304 ) -> std::result::Result<(), Errno> {
305 if let Some((reader, writer)) = self.next {
306 assert_ne!(reader, writer);
307 assert_ne!(self.read_previous, Some(reader));
308 assert_ne!(self.read_previous, Some(writer));
309
310 env.system.close(reader)?;
311 if writer != Fd::STDOUT {
312 if self.read_previous == Some(Fd::STDOUT) {
313 self.read_previous =
314 Some(env.system.dup(Fd::STDOUT, Fd(0), EnumSet::empty())?);
315 }
316 env.system.dup2(writer, Fd::STDOUT)?;
317 env.system.close(writer)?;
318 }
319 }
320 if let Some(reader) = self.read_previous {
321 if reader != Fd::STDIN {
322 env.system.dup2(reader, Fd::STDIN)?;
323 env.system.close(reader)?;
324 }
325 }
326 Ok(())
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333 use crate::tests::cat_builtin;
334 use crate::tests::return_builtin;
335 use crate::tests::suspend_builtin;
336 use assert_matches::assert_matches;
337 use futures_util::FutureExt;
338 use std::pin::Pin;
339 use std::rc::Rc;
340 use yash_env::VirtualSystem;
341 use yash_env::builtin::Builtin;
342 use yash_env::builtin::Type::Special;
343 use yash_env::job::ProcessResult;
344 use yash_env::job::ProcessState;
345 use yash_env::option::Option::{ErrExit, Monitor};
346 use yash_env::semantics::Field;
347 use yash_env::system::GetPid as _;
348 use yash_env::system::r#virtual::FileBody;
349 use yash_env::system::r#virtual::SIGSTOP;
350 use yash_env_test_helper::assert_stdout;
351 use yash_env_test_helper::in_virtual_system;
352 use yash_env_test_helper::stub_tty;
353
354 #[test]
355 fn empty_pipeline() {
356 let mut env = Env::new_virtual();
357 let pipeline = syntax::Pipeline {
358 commands: vec![],
359 negation: false,
360 };
361 let result = pipeline.execute(&mut env).now_or_never().unwrap();
362 assert_eq!(result, Continue(()));
363 assert_eq!(env.exit_status, ExitStatus(0));
364 }
365
366 #[test]
367 fn single_command_pipeline_returns_exit_status_intact_without_divert() {
368 let mut env = Env::new_virtual();
369 env.builtins.insert("return", return_builtin());
370 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
371 let result = pipeline.execute(&mut env).now_or_never().unwrap();
372 assert_eq!(result, Continue(()));
373 assert_eq!(env.exit_status, ExitStatus(93));
374 }
375
376 #[test]
377 fn single_command_pipeline_returns_exit_status_intact_with_divert() {
378 let mut env = Env::new_virtual();
379 env.builtins.insert("return", return_builtin());
380 env.exit_status = ExitStatus(17);
381 let pipeline: syntax::Pipeline = "return 37".parse().unwrap();
382 let result = pipeline.execute(&mut env).now_or_never().unwrap();
383 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(37)))));
384 assert_eq!(env.exit_status, ExitStatus(17));
385 }
386
387 #[test]
388 fn multi_command_pipeline_without_pipefail_returns_last_command_exit_status() {
389 in_virtual_system(|mut env, _state| async move {
390 env.builtins.insert("return", return_builtin());
391 env.options.set(PipeFail, Off);
392
393 let pipeline: syntax::Pipeline = "return -n 0 | return -n 0".parse().unwrap();
394 let result = pipeline.execute(&mut env).await;
395 assert_eq!(result, Continue(()));
396 assert_eq!(env.exit_status, ExitStatus(0));
397
398 let pipeline: syntax::Pipeline = "return -n 10 | return -n 20".parse().unwrap();
399 let result = pipeline.execute(&mut env).await;
400 assert_eq!(result, Continue(()));
401 assert_eq!(env.exit_status, ExitStatus(20));
402
403 let pipeline: syntax::Pipeline = "return -n 0 | return -n 20 | return -n 0 |\
404 return -n 30 | return -n 0 | return -n 0"
405 .parse()
406 .unwrap();
407 let result = pipeline.execute(&mut env).await;
408 assert_eq!(result, Continue(()));
409 assert_eq!(env.exit_status, ExitStatus(0));
410 });
411 }
412
413 #[test]
414 fn multi_command_pipeline_with_pipefail_returns_last_failed_command_exit_status() {
415 in_virtual_system(|mut env, _state| async move {
416 env.builtins.insert("return", return_builtin());
417 env.options.set(PipeFail, On);
418
419 let pipeline: syntax::Pipeline = "return -n 0 | return -n 0".parse().unwrap();
420 let result = pipeline.execute(&mut env).await;
421 assert_eq!(result, Continue(()));
422 assert_eq!(env.exit_status, ExitStatus(0));
423
424 let pipeline: syntax::Pipeline = "return -n 10 | return -n 20".parse().unwrap();
425 let result = pipeline.execute(&mut env).await;
426 assert_eq!(result, Continue(()));
427 assert_eq!(env.exit_status, ExitStatus(20));
428
429 let pipeline: syntax::Pipeline = "return -n 0 | return -n 20 | return -n 0 |\
430 return -n 30 | return -n 0 | return -n 0"
431 .parse()
432 .unwrap();
433 let result = pipeline.execute(&mut env).await;
434 assert_eq!(result, Continue(()));
435 assert_eq!(env.exit_status, ExitStatus(30));
436 });
437 }
438
439 #[test]
440 fn multi_command_pipeline_waits_for_all_child_commands() {
441 in_virtual_system(|mut env, state| async move {
442 env.builtins.insert("return", return_builtin());
443 let pipeline: syntax::Pipeline =
444 "return -n 1 | return -n 2 | return -n 3".parse().unwrap();
445 _ = pipeline.execute(&mut env).await;
446
447 for (pid, process) in &state.borrow().processes {
449 if *pid == env.main_pid {
450 assert_eq!(process.state(), ProcessState::Running);
451 } else {
452 assert_matches!(
453 process.state(),
454 ProcessState::Halted(ProcessResult::Exited(_))
455 );
456 }
457 }
458 });
459 }
460
461 #[test]
462 fn multi_command_pipeline_does_not_wait_for_unrelated_child() {
463 in_virtual_system(|mut env, state| async move {
464 env.builtins.insert("return", return_builtin());
465
466 let list: syntax::List = "return -n 7&".parse().unwrap();
467 _ = list.execute(&mut env).await;
468 let async_pid = {
469 let state = state.borrow();
470 let mut iter = state.processes.keys();
471 assert_eq!(iter.next(), Some(&env.main_pid));
472 let async_pid = *iter.next().unwrap();
473 assert_eq!(iter.next(), None);
474 async_pid
475 };
476
477 let pipeline: syntax::Pipeline =
478 "return -n 1 | return -n 2 | return -n 3".parse().unwrap();
479 _ = pipeline.execute(&mut env).await;
480
481 let state = state.borrow();
482 let process = &state.processes[&async_pid];
483 assert_eq!(process.state(), ProcessState::exited(7));
484 assert!(process.state_has_changed());
485 });
486 }
487
488 #[test]
489 fn pipe_connects_commands_in_pipeline() {
490 in_virtual_system(|mut env, state| async move {
491 {
492 let file = state.borrow().file_system.get("/dev/stdin").unwrap();
493 let mut file = file.borrow_mut();
494 file.body = FileBody::new(*b"ok\n");
495 }
496
497 env.builtins.insert("cat", cat_builtin());
498
499 let pipeline: syntax::Pipeline = "cat | cat | cat".parse().unwrap();
500 let result = pipeline.execute(&mut env).await;
501 assert_eq!(result, Continue(()));
502 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
503 assert_stdout(&state, |stdout| assert_eq!(stdout, "ok\n"));
504 });
505 }
506
507 #[test]
508 fn pipeline_leaves_no_pipe_fds_leftover() {
509 in_virtual_system(|mut env, state| async move {
510 env.builtins.insert("cat", cat_builtin());
511 let pipeline: syntax::Pipeline = "cat | cat".parse().unwrap();
512 let _ = pipeline.execute(&mut env).await;
513
514 let state = state.borrow();
515 let fds = state.processes[&env.main_pid].fds();
516 for fd in 3..10 {
517 assert!(!fds.contains_key(&Fd(fd)), "fd={fd}");
518 }
519 });
520 }
521
522 #[test]
523 fn inverting_exit_status_to_0_without_divert() {
524 let mut env = Env::new_virtual();
525 env.builtins.insert("return", return_builtin());
526 let pipeline: syntax::Pipeline = "! return -n 42".parse().unwrap();
527 let result = pipeline.execute(&mut env).now_or_never().unwrap();
528 assert_eq!(result, Continue(()));
529 assert_eq!(env.exit_status, ExitStatus(0));
530 }
531
532 #[test]
533 fn inverting_exit_status_to_1_without_divert() {
534 let mut env = Env::new_virtual();
535 env.builtins.insert("return", return_builtin());
536 let pipeline: syntax::Pipeline = "! return -n 0".parse().unwrap();
537 let result = pipeline.execute(&mut env).now_or_never().unwrap();
538 assert_eq!(result, Continue(()));
539 assert_eq!(env.exit_status, ExitStatus(1));
540 }
541
542 #[test]
543 fn not_inverting_exit_status_with_divert() {
544 let mut env = Env::new_virtual();
545 env.builtins.insert("return", return_builtin());
546 env.exit_status = ExitStatus(3);
547 let pipeline: syntax::Pipeline = "! return 15".parse().unwrap();
548 let result = pipeline.execute(&mut env).now_or_never().unwrap();
549 assert_eq!(result, Break(Divert::Return(Some(ExitStatus(15)))));
550 assert_eq!(env.exit_status, ExitStatus(3));
551 }
552
553 #[test]
554 fn noexec_option() {
555 let mut env = Env::new_virtual();
556 env.builtins.insert("return", return_builtin());
557 env.options.set(Exec, Off);
558 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
559 let result = pipeline.execute(&mut env).now_or_never().unwrap();
560 assert_eq!(result, Continue(()));
561 assert_eq!(env.exit_status, ExitStatus::SUCCESS);
562 }
563
564 #[test]
565 fn noexec_option_interactive() {
566 let mut env = Env::new_virtual();
567 env.builtins.insert("return", return_builtin());
568 env.options.set(Exec, Off);
569 env.options.set(Interactive, On);
570 let pipeline: syntax::Pipeline = "return -n 93".parse().unwrap();
571 let result = pipeline.execute(&mut env).now_or_never().unwrap();
572 assert_eq!(result, Continue(()));
573 assert_eq!(env.exit_status, ExitStatus(93));
574 }
575
576 #[test]
577 fn errexit_option() {
578 in_virtual_system(|mut env, _state| async move {
579 env.builtins.insert("return", return_builtin());
580 env.options.set(ErrExit, On);
581
582 let pipeline: syntax::Pipeline = "return -n 0 | return -n 93".parse().unwrap();
583 let result = pipeline.execute(&mut env).await;
584
585 assert_eq!(result, Break(Divert::Exit(None)));
586 assert_eq!(env.exit_status, ExitStatus(93));
587 });
588 }
589
590 #[test]
591 fn stack_without_inversion() {
592 fn stub_builtin(
593 env: &mut Env<VirtualSystem>,
594 _args: Vec<Field>,
595 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
596 Box::pin(async move {
597 assert!(!env.stack.contains(&Frame::Condition), "{:?}", env.stack);
598 Default::default()
599 })
600 }
601
602 let mut env = Env::new_virtual();
603 env.builtins
604 .insert("foo", Builtin::new(Special, stub_builtin));
605 let pipeline: syntax::Pipeline = "foo".parse().unwrap();
606 let result = pipeline.execute(&mut env).now_or_never().unwrap();
607 assert_eq!(result, Continue(()));
608 }
609
610 #[test]
611 fn stack_with_inversion() {
612 fn stub_builtin(
613 env: &mut Env<VirtualSystem>,
614 _args: Vec<Field>,
615 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
616 Box::pin(async move {
617 assert_matches!(
618 env.stack.as_slice(),
619 [Frame::Condition, Frame::Builtin { .. }]
620 );
621 Default::default()
622 })
623 }
624
625 let mut env = Env::new_virtual();
626 env.builtins
627 .insert("foo", Builtin::new(Special, stub_builtin));
628 let pipeline: syntax::Pipeline = "! foo".parse().unwrap();
629 let result = pipeline.execute(&mut env).now_or_never().unwrap();
630 assert_eq!(result, Continue(()));
631 }
632
633 #[test]
634 fn process_group_id_of_job_controlled_pipeline() {
635 fn stub_builtin(
636 env: &mut Env<VirtualSystem>,
637 _args: Vec<Field>,
638 ) -> Pin<Box<dyn Future<Output = yash_env::builtin::Result> + '_>> {
639 let pgid = env.system.getpgrp().0 as _;
640 Box::pin(async move { yash_env::builtin::Result::new(ExitStatus(pgid)) })
641 }
642
643 in_virtual_system(|mut env, state| async move {
644 env.builtins
645 .insert("foo", Builtin::new(Special, stub_builtin));
646 env.options.set(Monitor, On);
647 stub_tty(&state);
648
649 let pipeline: syntax::Pipeline = "foo | foo".parse().unwrap();
651 let result = pipeline.execute(&mut env).await;
652 assert_eq!(result, Continue(()));
653 assert_ne!(env.exit_status, ExitStatus(env.main_pgid.0 as _));
654
655 assert_eq!(state.borrow().foreground, Some(env.main_pgid));
657 })
658 }
659
660 #[test]
661 fn job_controlled_suspended_pipeline_in_job_list() {
662 in_virtual_system(|mut env, state| async move {
663 env.builtins.insert("return", return_builtin());
664 env.builtins.insert("suspend", suspend_builtin());
665 env.options.set(Monitor, On);
666 stub_tty(&state);
667
668 let pipeline: syntax::Pipeline = "return -n 0 | suspend x".parse().unwrap();
669 let result = pipeline.execute(&mut env).await;
670 assert_eq!(result, Continue(()));
671 assert_eq!(env.exit_status, ExitStatus::from(SIGSTOP));
672
673 assert_eq!(env.jobs.len(), 1);
674 let job = env.jobs.iter().next().unwrap().1;
675 assert!(job.job_controlled);
676 assert_eq!(job.state, ProcessState::stopped(SIGSTOP));
677 assert!(job.state_changed);
678 assert_eq!(job.name, "return -n 0 | suspend x");
679 })
680 }
681
682 #[test]
683 fn pipe_set_shift_to_first_command() {
684 let system = VirtualSystem::new();
685 let process_id = system.process_id;
686 let state = Rc::clone(&system.state);
687 let mut env = Env::with_system(system);
688 let mut pipes = PipeSet::new();
689
690 let result = pipes.shift(&mut env, true);
691 assert_eq!(result, Ok(()));
692 assert_eq!(pipes.read_previous, None);
693 assert_eq!(pipes.next, Some((Fd(3), Fd(4))));
694 let state = state.borrow();
695 let process = &state.processes[&process_id];
696 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
697 assert_eq!(process.fds().get(&Fd(4)).unwrap().flags, EnumSet::empty());
698 }
699
700 #[test]
701 fn pipe_set_shift_to_middle_command() {
702 let system = VirtualSystem::new();
703 let process_id = system.process_id;
704 let state = Rc::clone(&system.state);
705 let mut env = Env::with_system(system);
706 let mut pipes = PipeSet::new();
707
708 let _ = pipes.shift(&mut env, true);
709 let result = pipes.shift(&mut env, true);
710 assert_eq!(result, Ok(()));
711 assert_eq!(pipes.read_previous, Some(Fd(3)));
712 assert_eq!(pipes.next, Some((Fd(4), Fd(5))));
713 let state = state.borrow();
714 let process = &state.processes[&process_id];
715 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
716 assert_eq!(process.fds().get(&Fd(4)).unwrap().flags, EnumSet::empty());
717 assert_eq!(process.fds().get(&Fd(5)).unwrap().flags, EnumSet::empty());
718 }
719
720 #[test]
721 fn pipe_set_shift_to_last_command() {
722 let system = VirtualSystem::new();
723 let process_id = system.process_id;
724 let state = Rc::clone(&system.state);
725 let mut env = Env::with_system(system);
726 let mut pipes = PipeSet::new();
727
728 let _ = pipes.shift(&mut env, true);
729 let result = pipes.shift(&mut env, false);
730 assert_eq!(result, Ok(()));
731 assert_eq!(pipes.read_previous, Some(Fd(3)));
732 assert_eq!(pipes.next, None);
733 let state = state.borrow();
734 let process = &state.processes[&process_id];
735 assert_eq!(process.fds().get(&Fd(3)).unwrap().flags, EnumSet::empty());
736 }
737
738 }