From 74d6bb74d35733516c1de1d6b106783dc7c957fc Mon Sep 17 00:00:00 2001 From: mahiro21h Date: Sun, 29 Dec 2024 14:01:54 +0300 Subject: [PATCH 1/2] fix input() causes segfault on EOF Signed-off-by: Raysan Alawami --- stdlib/src/builtin/io.mojo | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/stdlib/src/builtin/io.mojo b/stdlib/src/builtin/io.mojo index a409c35c62..bef5c0c48d 100644 --- a/stdlib/src/builtin/io.mojo +++ b/stdlib/src/builtin/io.mojo @@ -67,7 +67,7 @@ struct _fdopen[mode: StringLiteral = "a"]: """Closes the file handle.""" _ = fclose(self.handle) - fn readline(self) -> String: + fn readline(self) raises -> String: """Reads an entire line from stdin or until EOF. Lines are delimited by a newline character. Returns: @@ -94,7 +94,7 @@ struct _fdopen[mode: StringLiteral = "a"]: """ return self.read_until_delimiter("\n") - fn read_until_delimiter(self, delimiter: String) -> String: + fn read_until_delimiter(self, delimiter: String) raises -> String: """Reads an entire line from a stream, up to the `delimiter`. Does not include the delimiter in the result. @@ -139,6 +139,13 @@ struct _fdopen[mode: StringLiteral = "a"]: ord(delimiter), self.handle, ) + # Per man getdelim(3), getdelim will return -1 if an error occurs + # (or the user sends EOF without providing any input). We must + # raise an error in this case because otherwise, String() will crash mojo + # if the user sends EOF with no input. + # TODO: check errno to ensure we haven't encountered EINVAL or ENOMEM instead + if bytes_read == -1: + raise Error("EOF") # Copy the buffer (excluding the delimiter itself) into a Mojo String. var s = String(StringRef(buffer, bytes_read - 1)) # Explicitly free the buffer using free() instead of the Mojo allocator. @@ -286,7 +293,7 @@ fn print[ # ===----------------------------------------------------------------------=== # -fn input(prompt: String = "") -> String: +fn input(prompt: String = "") raises -> String: """Reads a line of input from the user. Reads a line from standard input, converts it to a string, and returns that string. From df55a1f48ea0c3cbd243422eb66de5fc87ca1f9f Mon Sep 17 00:00:00 2001 From: mahiro21h Date: Sun, 29 Dec 2024 14:03:06 +0300 Subject: [PATCH 2/2] add a test to check if read_until_delimiter() raises EOF Signed-off-by: Raysan Alawami --- stdlib/test/builtin/test_issue_3908.mojo | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 stdlib/test/builtin/test_issue_3908.mojo diff --git a/stdlib/test/builtin/test_issue_3908.mojo b/stdlib/test/builtin/test_issue_3908.mojo new file mode 100644 index 0000000000..3e468a428f --- /dev/null +++ b/stdlib/test/builtin/test_issue_3908.mojo @@ -0,0 +1,28 @@ +# ===----------------------------------------------------------------------=== # +# Copyright (c) 2024, Modular Inc. All rights reserved. +# +# Licensed under the Apache License v2.0 with LLVM Exceptions: +# https://llvm.org/LICENSE.txt +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ===----------------------------------------------------------------------=== # +# RUN: echo -n | %mojo %s + +from builtin.io import _fdopen +from testing import testing + + +fn test_read_until_delimiter_raises_eof() raises: + var stdin = _fdopen["r"](0) + with testing.assert_raises(contains="EOF"): + # Assign to a variable to silence a warning about unused String value + # if an error wasn't raised. + var unused = stdin.read_until_delimiter("\n") + + +fn main() raises: + test_read_until_delimiter_raises_eof()