1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-12-22 15:24:46 -05:00

fix(cli): panic when stdio is null on windows (#6528)

Fixes: #6409
This commit is contained in:
Maayan Hanin 2020-07-09 22:06:51 +03:00 committed by GitHub
parent 202e7fa6ad
commit edb7a0eead
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 289 additions and 33 deletions

View file

@ -37,36 +37,48 @@ lazy_static! {
/// resource table is dropped storing reference to that handle, the handle /// resource table is dropped storing reference to that handle, the handle
/// itself won't be closed (so Deno.core.print) will still work. /// itself won't be closed (so Deno.core.print) will still work.
// TODO(ry) It should be possible to close stdout. // TODO(ry) It should be possible to close stdout.
static ref STDIN_HANDLE: std::fs::File = { static ref STDIN_HANDLE: Option<std::fs::File> = {
#[cfg(not(windows))] #[cfg(not(windows))]
let stdin = unsafe { std::fs::File::from_raw_fd(0) }; let stdin = unsafe { Some(std::fs::File::from_raw_fd(0)) };
#[cfg(windows)] #[cfg(windows)]
let stdin = unsafe { let stdin = unsafe {
std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle( let handle = winapi::um::processenv::GetStdHandle(
winapi::um::winbase::STD_INPUT_HANDLE, winapi::um::winbase::STD_INPUT_HANDLE,
)) );
if handle.is_null() {
return None;
}
Some(std::fs::File::from_raw_handle(handle))
}; };
stdin stdin
}; };
static ref STDOUT_HANDLE: std::fs::File = { static ref STDOUT_HANDLE: Option<std::fs::File> = {
#[cfg(not(windows))] #[cfg(not(windows))]
let stdout = unsafe { std::fs::File::from_raw_fd(1) }; let stdout = unsafe { Some(std::fs::File::from_raw_fd(1)) };
#[cfg(windows)] #[cfg(windows)]
let stdout = unsafe { let stdout = unsafe {
std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle( let handle = winapi::um::processenv::GetStdHandle(
winapi::um::winbase::STD_OUTPUT_HANDLE, winapi::um::winbase::STD_OUTPUT_HANDLE,
)) );
if handle.is_null() {
return None;
}
Some(std::fs::File::from_raw_handle(handle))
}; };
stdout stdout
}; };
static ref STDERR_HANDLE: std::fs::File = { static ref STDERR_HANDLE: Option<std::fs::File> = {
#[cfg(not(windows))] #[cfg(not(windows))]
let stderr = unsafe { std::fs::File::from_raw_fd(2) }; let stderr = unsafe { Some(std::fs::File::from_raw_fd(2)) };
#[cfg(windows)] #[cfg(windows)]
let stderr = unsafe { let stderr = unsafe {
std::fs::File::from_raw_handle(winapi::um::processenv::GetStdHandle( let handle = winapi::um::processenv::GetStdHandle(
winapi::um::winbase::STD_ERROR_HANDLE, winapi::um::winbase::STD_ERROR_HANDLE,
)) );
if handle.is_null() {
return None;
}
Some(std::fs::File::from_raw_handle(handle))
}; };
stderr stderr
}; };
@ -78,26 +90,31 @@ pub fn init(i: &mut CoreIsolate, s: &State) {
} }
pub fn get_stdio() -> ( pub fn get_stdio() -> (
StreamResourceHolder, Option<StreamResourceHolder>,
StreamResourceHolder, Option<StreamResourceHolder>,
StreamResourceHolder, Option<StreamResourceHolder>,
) { ) {
let stdin = StreamResourceHolder::new(StreamResource::FsFile(Some({ let stdin = get_stdio_stream(&STDIN_HANDLE);
let stdin = STDIN_HANDLE.try_clone().unwrap(); let stdout = get_stdio_stream(&STDOUT_HANDLE);
(tokio::fs::File::from_std(stdin), FileMetadata::default()) let stderr = get_stdio_stream(&STDERR_HANDLE);
})));
let stdout = StreamResourceHolder::new(StreamResource::FsFile(Some({
let stdout = STDOUT_HANDLE.try_clone().unwrap();
(tokio::fs::File::from_std(stdout), FileMetadata::default())
})));
let stderr = StreamResourceHolder::new(StreamResource::FsFile(Some({
let stderr = STDERR_HANDLE.try_clone().unwrap();
(tokio::fs::File::from_std(stderr), FileMetadata::default())
})));
(stdin, stdout, stderr) (stdin, stdout, stderr)
} }
fn get_stdio_stream(
handle: &Option<std::fs::File>,
) -> Option<StreamResourceHolder> {
match handle {
None => None,
Some(file_handle) => match file_handle.try_clone() {
Ok(clone) => Some(StreamResourceHolder::new(StreamResource::FsFile(
Some((tokio::fs::File::from_std(clone), FileMetadata::default())),
))),
Err(_e) => None,
},
}
}
fn no_buffer_specified() -> OpError { fn no_buffer_specified() -> OpError {
OpError::type_error("no buffer specified".to_string()) OpError::type_error("no buffer specified".to_string())
} }

View file

@ -58,9 +58,15 @@ fn create_web_worker(
let state = state_rc.borrow(); let state = state_rc.borrow();
let mut resource_table = state.resource_table.borrow_mut(); let mut resource_table = state.resource_table.borrow_mut();
let (stdin, stdout, stderr) = get_stdio(); let (stdin, stdout, stderr) = get_stdio();
resource_table.add("stdin", Box::new(stdin)); if let Some(stream) = stdin {
resource_table.add("stdout", Box::new(stdout)); resource_table.add("stdin", Box::new(stream));
resource_table.add("stderr", Box::new(stderr)); }
if let Some(stream) = stdout {
resource_table.add("stdout", Box::new(stream));
}
if let Some(stream) = stderr {
resource_table.add("stderr", Box::new(stream));
}
} }
// Instead of using name for log we use `worker-${id}` because // Instead of using name for log we use `worker-${id}` because

127
cli/tests/DenoWinRunner.cs Normal file
View file

@ -0,0 +1,127 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
[Flags]
public enum DenoConstraints : int
{
None = 0,
NoStdin = 1,
NoStdout = 2,
NoStderr = 4
}
public class DenoWinRunner
{
private const int STD_INPUT_HANDLE = -10;
private const int STD_OUTPUT_HANDLE = -11;
private const int STD_ERROR_HANDLE = -12;
private const int FILE_NOT_FOUND = 2;
private const int WAIT_TIMEOUT = 258;
[DllImport("kernel32.dll")]
private static extern void SetStdHandle(int nStdHandle, IntPtr handle);
/// <summary>
/// Runs Deno.exe under the specified constraints
/// </summary>
/// <param name="pathToDenoExe">Path to the Deno.exe file. Can be absolute or relative</param>
/// <param name="pathToTestScript">Path to the script file Deno should run.</param>
/// <param name="constraints">The contrainsts to apply to the Deno process</param>
/// <param name="timeoutMilliseconds">How long to wait for the Deno process to exit</param>
/// <returns>The deno.exe exit code, or an exit code provided by the test runner</returns>
public static int RunDenoScript(string pathToDenoExe, string pathToTestScript, DenoConstraints constraints, uint timeoutMilliseconds = 1000)
{
try
{
if (!File.Exists(pathToDenoExe))
{
Console.Error.WriteLine("Cannot find Deno.exe at " + pathToDenoExe);
return FILE_NOT_FOUND;
}
if (!File.Exists(pathToTestScript))
{
Console.Error.WriteLine("Cannot find test script at " + pathToTestScript);
return FILE_NOT_FOUND;
}
ProcessStartInfo startInfo = new ProcessStartInfo(pathToDenoExe)
{
ErrorDialog = false,
UseShellExecute = false,
Arguments = @"run -A " + pathToTestScript,
RedirectStandardInput = !constraints.HasFlag(DenoConstraints.NoStdin),
RedirectStandardOutput = !constraints.HasFlag(DenoConstraints.NoStdout),
RedirectStandardError = !constraints.HasFlag(DenoConstraints.NoStderr)
};
startInfo.Environment.Add("RUST_BACKTRACE", "1");
if (constraints.HasFlag(DenoConstraints.NoStdin))
{
SetStdHandle(STD_INPUT_HANDLE, (IntPtr)null);
}
if (constraints.HasFlag(DenoConstraints.NoStdout))
{
SetStdHandle(STD_OUTPUT_HANDLE, (IntPtr)null);
}
if (constraints.HasFlag(DenoConstraints.NoStderr))
{
SetStdHandle(STD_ERROR_HANDLE, (IntPtr)null);
}
Process process = new Process { StartInfo = startInfo };
process.Start();
Task<string> stdErrTask = startInfo.RedirectStandardError ?
process.StandardError.ReadToEndAsync() : Task.FromResult<string>(null);
Task<string> stdOutTask = startInfo.RedirectStandardOutput ?
process.StandardOutput.ReadToEndAsync() : Task.FromResult<string>(null);
if (!process.WaitForExit((int)timeoutMilliseconds))
{
Console.Error.WriteLine("Timed out waiting for Deno process to exit");
try
{
process.Kill();
}
catch
{
// Kill might fail, either because the process already exited or due to some other error
Console.Error.WriteLine("Failure killing the Deno process - possible Zombie Deno.exe process");
}
return WAIT_TIMEOUT;
}
// If the Deno process wrote to STDERR - append it to our STDERR
if (!constraints.HasFlag(DenoConstraints.NoStderr))
{
string error = stdErrTask.Result;
if (!string.IsNullOrWhiteSpace(error))
{
Console.Error.WriteLine(error);
}
}
return process.ExitCode;
}
catch (Win32Exception ex)
{
Console.Error.WriteLine("Win32Exception: code = " + ex.ErrorCode + ", message: " + ex.Message);
return ex.NativeErrorCode;
}
catch (Exception ex)
{
Console.Error.WriteLine("Exception: message: " + ex.Message);
return -1;
}
}
}

View file

@ -0,0 +1,10 @@
$Source = [IO.File]::ReadAllText("$PSScriptRoot\DenoWinRunner.cs")
$denoExePath = $args[0]
$scriptPath = $args[1]
$constraints = $args[2]
$timeout = 5000;
Add-Type -TypeDefinition $Source -Language CSharp
Write-Output("Running Deno script: " + $args[1])
$code = [DenoWinRunner]::RunDenoScript($denoExePath, $scriptPath, $constraints, $timeout)
Write-Output("Deno.exe or the test wrapper has exited with code: $code")
exit $code

View file

@ -3116,3 +3116,62 @@ fn set_raw_should_not_panic_on_no_tty() {
let stderr = std::str::from_utf8(&output.stderr).unwrap().trim(); let stderr = std::str::from_utf8(&output.stderr).unwrap().trim();
assert!(stderr.contains("BadResource")); assert!(stderr.contains("BadResource"));
} }
#[cfg(windows)]
enum WinProcConstraints {
NoStdIn,
NoStdOut,
NoStdErr,
}
#[cfg(windows)]
fn run_deno_script_constrained(
script_path: std::path::PathBuf,
constraints: WinProcConstraints,
) -> Result<(), i64> {
let file_path = "cli/tests/DenoWinRunner.ps1";
let constraints = match constraints {
WinProcConstraints::NoStdIn => "1",
WinProcConstraints::NoStdOut => "2",
WinProcConstraints::NoStdErr => "4",
};
let deno_exe_path = util::deno_exe_path()
.into_os_string()
.into_string()
.unwrap();
let deno_script_path = script_path.into_os_string().into_string().unwrap();
let args = vec![&deno_exe_path[..], &deno_script_path[..], constraints];
util::run_powershell_script_file(file_path, args)
}
#[cfg(windows)]
#[test]
fn should_not_panic_on_no_stdin() {
let output = run_deno_script_constrained(
util::tests_path().join("echo.ts"),
WinProcConstraints::NoStdIn,
);
output.unwrap();
}
#[cfg(windows)]
#[test]
fn should_not_panic_on_no_stdout() {
let output = run_deno_script_constrained(
util::tests_path().join("echo.ts"),
WinProcConstraints::NoStdOut,
);
output.unwrap();
}
#[cfg(windows)]
#[test]
fn should_not_panic_on_no_stderr() {
let output = run_deno_script_constrained(
util::tests_path().join("echo.ts"),
WinProcConstraints::NoStdErr,
);
output.unwrap();
}

View file

@ -300,9 +300,15 @@ impl MainWorker {
let state_rc = CoreIsolate::state(&worker.isolate); let state_rc = CoreIsolate::state(&worker.isolate);
let state = state_rc.borrow(); let state = state_rc.borrow();
let mut t = state.resource_table.borrow_mut(); let mut t = state.resource_table.borrow_mut();
t.add("stdin", Box::new(stdin)); if let Some(stream) = stdin {
t.add("stdout", Box::new(stdout)); t.add("stdin", Box::new(stream));
t.add("stderr", Box::new(stderr)); }
if let Some(stream) = stdout {
t.add("stdout", Box::new(stream));
}
if let Some(stream) = stderr {
t.add("stderr", Box::new(stream));
}
} }
worker.execute("bootstrap.mainRuntime()")?; worker.execute("bootstrap.mainRuntime()")?;
Ok(worker) Ok(worker)

View file

@ -591,6 +591,37 @@ pub fn run_python_script(script: &str) {
} }
} }
pub fn run_powershell_script_file(
script_file_path: &str,
args: Vec<&str>,
) -> Result<(), i64> {
let deno_dir = new_deno_dir();
let mut command = Command::new("powershell.exe");
command
.env("DENO_DIR", deno_dir.path())
.current_dir(root_path())
.arg("-file")
.arg(script_file_path);
for arg in args {
command.arg(arg);
}
let output = command.output().expect("failed to spawn script");
let stdout = String::from_utf8(output.stdout).unwrap();
let stderr = String::from_utf8(output.stderr).unwrap();
println!("{}", stdout);
if !output.status.success() {
panic!(
"{} executed with failing error code\n{}{}",
script_file_path, stdout, stderr
);
}
Ok(())
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct CheckOutputIntegrationTest { pub struct CheckOutputIntegrationTest {
pub args: &'static str, pub args: &'static str,