diff --git a/graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java b/graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java index dcc72a50c3..b082c7b78b 100644 --- a/graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java +++ b/graalpython/com.oracle.graal.python.shell/src/com/oracle/graal/python/shell/GraalPythonMain.java @@ -157,6 +157,7 @@ private static void appendDecodedChars(StringBuilder builder, CharBuffer out) { private boolean noUserSite = false; private boolean noSite = false; private boolean unbufferedIO = false; + private boolean legacyWindowsStdio = false; private boolean multiContext = false; private int repeatedRuns = 1; private boolean snaptshotStartup = false; @@ -794,6 +795,7 @@ protected void launch(Builder contextBuilder) { safePath = safePath || getBoolEnv("PYTHONSAFEPATH"); verboseFlag = verboseFlag || getBoolEnv("PYTHONVERBOSE"); unbufferedIO = unbufferedIO || getBoolEnv("PYTHONUNBUFFERED"); + legacyWindowsStdio = getBoolEnv("PYTHONLEGACYWINDOWSSTDIO"); dontWriteBytecode = dontWriteBytecode || getBoolEnv("PYTHONDONTWRITEBYTECODE"); String maxStrDigitsEnv = getEnv("PYTHONINTMAXSTRDIGITS"); if (intMaxStrDigits < 0 && maxStrDigitsEnv != null) { @@ -877,6 +879,9 @@ protected void launch(Builder contextBuilder) { } contextBuilder.option("python.IgnoreEnvironmentFlag", Boolean.toString(ignoreEnv)); contextBuilder.option("python.UnbufferedIO", Boolean.toString(unbufferedIO)); + if (IS_WINDOWS) { + contextBuilder.option("python.LegacyWindowsStdio", Boolean.toString(legacyWindowsStdio)); + } ConsoleHandler consoleHandler = createConsoleHandler(System.in, System.out); contextBuilder.arguments(getLanguageId(), programArgs.toArray(new String[programArgs.size()])); @@ -1288,6 +1293,7 @@ protected void printHelp(OptionCategory maxCategory) { "PYTHON_CPU_COUNT: override the return value of os.cpu_count() (-X cpu_count).\n" + "PYTHONCASEOK : ignore case in 'import' statements (Windows).\n" + "PYTHONIOENCODING: Encoding[:errors] used for stdin/stdout/stderr.\n" + + "PYTHONLEGACYWINDOWSSTDIO: use legacy Windows console I/O.\n" + "PYTHONHASHSEED: if this variable is set to 'random', the effect is the same\n" + " as specifying the -R option: a random value is used to seed the hashes of\n" + " str, bytes and datetime objects. It can also be set to an integer\n" + diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java index a9ff7666d0..703a6de553 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/Python3Core.java @@ -178,6 +178,7 @@ import com.oracle.graal.python.builtins.modules.io.BufferedWriterBuiltins; import com.oracle.graal.python.builtins.modules.io.BufferedWriterMixinBuiltins; import com.oracle.graal.python.builtins.modules.io.BytesIOBuiltins; +import com.oracle.graal.python.builtins.modules.io.CommonFileIOBuiltins; import com.oracle.graal.python.builtins.modules.io.FileIOBuiltins; import com.oracle.graal.python.builtins.modules.io.IOBaseBuiltins; import com.oracle.graal.python.builtins.modules.io.IOBaseDictBuiltins; @@ -187,6 +188,7 @@ import com.oracle.graal.python.builtins.modules.io.StringIOBuiltins; import com.oracle.graal.python.builtins.modules.io.TextIOBaseBuiltins; import com.oracle.graal.python.builtins.modules.io.TextIOWrapperBuiltins; +import com.oracle.graal.python.builtins.modules.io.WindowsConsoleIOBuiltins; import com.oracle.graal.python.builtins.modules.json.JSONEncoderBuiltins; import com.oracle.graal.python.builtins.modules.json.JSONModuleBuiltins; import com.oracle.graal.python.builtins.modules.json.JSONScannerBuiltins; @@ -620,7 +622,9 @@ private static PythonBuiltins[] initializeBuiltins(TruffleLanguage.Env env) { new BufferedReaderMixinBuiltins(), new BufferedWriterMixinBuiltins(), new BufferedIOMixinBuiltins(), + new CommonFileIOBuiltins(), new FileIOBuiltins(), + new WindowsConsoleIOBuiltins(), new TextIOWrapperBuiltins(), new IncrementalNewlineDecoderBuiltins(), new BufferedRWPairBuiltins(), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java index 416b1ccd48..a96555806d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/PythonBuiltinClassType.java @@ -120,6 +120,7 @@ import com.oracle.graal.python.builtins.modules.io.IncrementalNewlineDecoderBuiltins; import com.oracle.graal.python.builtins.modules.io.StringIOBuiltins; import com.oracle.graal.python.builtins.modules.io.TextIOWrapperBuiltins; +import com.oracle.graal.python.builtins.modules.io.WindowsConsoleIOBuiltins; import com.oracle.graal.python.builtins.modules.json.JSONEncoderBuiltins; import com.oracle.graal.python.builtins.modules.json.JSONScannerBuiltins; import com.oracle.graal.python.builtins.modules.lsprof.ProfilerBuiltins; @@ -921,7 +922,7 @@ It can be called either on the class (e.g. C.f()) or on an instance "BufferedRandom", PBufferedIOBase, newBuilder().publishInModule("_io").basetype().addDict().slots(BufferedReaderMixinBuiltins.SLOTS, BufferedIOMixinBuiltins.SLOTS, BufferedRandomBuiltins.SLOTS)), - PWindowsConsoleIO("_WindowsConsoleIO", PRawIOBase, newBuilder().moduleName("_io").basetype()), + PWindowsConsoleIO("_WindowsConsoleIO", PRawIOBase, newBuilder().moduleName("_io").basetype().addDict().slots(WindowsConsoleIOBuiltins.SLOTS)), PFileIO("FileIO", PRawIOBase, newBuilder().publishInModule("_io").basetype().addDict().slots(FileIOBuiltins.SLOTS)), PTextIOWrapper("TextIOWrapper", PTextIOBase, newBuilder().publishInModule("_io").basetype().addDict().slots(TextIOWrapperBuiltins.SLOTS)), PIncrementalNewlineDecoder("IncrementalNewlineDecoder", PythonObject, newBuilder().publishInModule("_io").basetype().slots(IncrementalNewlineDecoderBuiltins.SLOTS)), diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java index 6d1622df5b..3e9ffe6f36 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/SysModuleBuiltins.java @@ -50,6 +50,7 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.AttributeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.DeprecationWarning; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ImportError; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PWindowsConsoleIO; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RuntimeError; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RuntimeWarning; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.TypeError; @@ -174,6 +175,7 @@ import com.oracle.graal.python.builtins.modules.io.PFileIO; import com.oracle.graal.python.builtins.modules.io.PTextIO; import com.oracle.graal.python.builtins.modules.io.TextIOWrapperNodesFactory.TextIOWrapperInitNodeGen; +import com.oracle.graal.python.builtins.modules.io.WindowsConsoleIOBuiltins; import com.oracle.graal.python.builtins.objects.PNone; import com.oracle.graal.python.builtins.objects.PythonAbstractObject; import com.oracle.graal.python.builtins.objects.cext.capi.CExtNodes.EnsurePythonObjectNode; @@ -786,15 +788,33 @@ static void initStd(Python3Core core) { BufferedReaderBuiltins.BufferedReaderInit.internalInit(stdinBuffer, stdinFileIO, BufferedReaderBuiltins.DEFAULT_BUFFER_SIZE, language, posixSupport, posixLib); setWrapper(T_STDIN, T___STDIN__, T_R, stdioEncoding, stdioError, PNone.NONE, stdinBuffer, sysModule, language, true); - PFileIO stdoutFileIO = PFactory.createFileIO(language); - FileIOBuiltins.FileIOInit.internalInit(stdoutFileIO, toTruffleStringUncached(""), 1, IOMode.WB); + PFileIO stdoutFileIO = createStdoutRaw(context, language, posixSupport, posixLib, toTruffleStringUncached(""), 1); Object stdoutBuffer = createBufferedIO(buffering, language, stdoutFileIO, posixSupport, posixLib); - setWrapper(T_STDOUT, T___STDOUT__, T_W, stdioEncoding, stdioError, PNone.NONE, stdoutBuffer, sysModule, language, buffering); + TruffleString stdoutEncoding = isWindowsConsoleIO(stdoutFileIO) ? StringLiterals.T_UTF8 : stdioEncoding; + setWrapper(T_STDOUT, T___STDOUT__, T_W, stdoutEncoding, stdioError, PNone.NONE, stdoutBuffer, sysModule, language, buffering); - PFileIO stderr = PFactory.createFileIO(language); - FileIOBuiltins.FileIOInit.internalInit(stderr, toTruffleStringUncached(""), 2, IOMode.WB); + PFileIO stderr = createStdoutRaw(context, language, posixSupport, posixLib, toTruffleStringUncached(""), 2); Object stderrBuffer = createBufferedIO(buffering, language, stderr, posixSupport, posixLib); - setWrapper(T_STDERR, T___STDERR__, T_W, stdioEncoding, T_BACKSLASHREPLACE, PNone.NONE, stderrBuffer, sysModule, language, buffering); + TruffleString stderrEncoding = isWindowsConsoleIO(stderr) ? StringLiterals.T_UTF8 : stdioEncoding; + setWrapper(T_STDERR, T___STDERR__, T_W, stderrEncoding, T_BACKSLASHREPLACE, PNone.NONE, stderrBuffer, sysModule, language, buffering); + } + + private static PFileIO createStdoutRaw(PythonContext context, PythonLanguage language, Object posixSupport, PosixSupportLibrary posixLib, TruffleString name, int fd) { + if (getPythonOS() == PLATFORM_WIN32 && !context.getOption(PythonOptions.LegacyWindowsStdio)) { + int consoleType = posixLib.getWindowsConsoleType(posixSupport, fd); + if (consoleType != 0) { + PFileIO consoleIO = WindowsConsoleIOBuiltins.create(language); + WindowsConsoleIOBuiltins.internalInit(consoleIO, name, fd, IOMode.WB, consoleType); + return consoleIO; + } + } + PFileIO fileIO = PFactory.createFileIO(language); + FileIOBuiltins.FileIOInit.internalInit(fileIO, name, fd, IOMode.WB); + return fileIO; + } + + private static boolean isWindowsConsoleIO(PFileIO fileIO) { + return getPythonOS() == PLATFORM_WIN32 && fileIO.getPythonClass() == PWindowsConsoleIO; } private static Object createBufferedIO(boolean buffering, PythonLanguage language, PFileIO fileIo, Object posixSupport, PosixSupportLibrary posixLib) { @@ -802,7 +822,11 @@ private static Object createBufferedIO(boolean buffering, PythonLanguage languag return fileIo; } PBuffered writer = PFactory.createBufferedWriter(language); - BufferedWriterBuiltins.BufferedWriterInit.internalInit(writer, fileIo, BufferedReaderBuiltins.DEFAULT_BUFFER_SIZE, language, posixSupport, posixLib); + if (isWindowsConsoleIO(fileIo)) { + BufferedWriterBuiltins.BufferedWriterInit.internalInitConsole(writer, fileIo, BufferedReaderBuiltins.DEFAULT_BUFFER_SIZE, language); + } else { + BufferedWriterBuiltins.BufferedWriterInit.internalInit(writer, fileIo, BufferedReaderBuiltins.DEFAULT_BUFFER_SIZE, language, posixSupport, posixLib); + } return writer; } diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/AbstractBufferedIOBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/AbstractBufferedIOBuiltins.java index 63de30bfea..c32199d450 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/AbstractBufferedIOBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/AbstractBufferedIOBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -104,7 +104,7 @@ static void bufferSizeError(PBuffered self, int bufferSize, throw PRaiseNode.raiseStatic(inliningTarget, ValueError, BUF_SIZE_POS); } - private static void init(PBuffered self, int bufferSize, PythonLanguage language) { + static void init(PBuffered self, int bufferSize, PythonLanguage language) { self.initBuffer(bufferSize); self.setLock(PFactory.createLock(language)); self.setOwner(0); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/BufferedWriterBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/BufferedWriterBuiltins.java index 5fd24560bf..82339302c7 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/BufferedWriterBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/BufferedWriterBuiltins.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -125,6 +125,15 @@ public static void internalInit(PBuffered self, PFileIO raw, int bufferSize, Pyt self.setOK(true); } + + public static void internalInitConsole(PBuffered self, PFileIO raw, int bufferSize, PythonLanguage language) { + self.setDetached(false); + self.setRaw(raw, false); + BufferedInitNode.init(self, bufferSize, language); + self.resetWrite(); + self.setPos(0); + self.setOK(true); + } } // BufferedWriter(raw[, buffer_size=DEFAULT_BUFFER_SIZE]) diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/CommonFileIOBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/CommonFileIOBuiltins.java new file mode 100644 index 0000000000..c2128c6f1f --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/CommonFileIOBuiltins.java @@ -0,0 +1,247 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.builtins.modules.io; + +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PFileIO; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PRawIOBase; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PWindowsConsoleIO; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSE; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSED; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSEFD; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_FILENO; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_MODE; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_READABLE; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_WRITABLE; +import static com.oracle.graal.python.builtins.modules.io.IONodes.T_CLOSE; +import static com.oracle.graal.python.nodes.ErrorMessages.IO_CLOSED; +import static com.oracle.graal.python.runtime.exception.PythonErrorType.ValueError; +import static com.oracle.graal.python.util.PythonUtils.tsLiteral; + +import java.util.List; + +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.modules.PosixModuleBuiltins; +import com.oracle.graal.python.builtins.modules.WarningsModuleBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.lib.PyErrChainExceptions; +import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.exception.PException; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.Cached.Exclusive; +import com.oracle.truffle.api.dsl.Cached.Shared; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.nodes.Node; +import com.oracle.truffle.api.strings.TruffleString; + +@CoreFunctions(extendClasses = {PFileIO, PWindowsConsoleIO}) +public final class CommonFileIOBuiltins extends PythonBuiltins { + @Override + protected List> getNodeFactories() { + return CommonFileIOBuiltinsFactory.getFactories(); + } + + @Builtin(name = J_CLOSE, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class CloseNode extends PythonUnaryBuiltinNode { + @Specialization(guards = "!self.isCloseFD()") + static Object simple(VirtualFrame frame, PFileIO self, + @Bind Node inliningTarget, + @Exclusive @Cached PyObjectCallMethodObjArgs callClose) { + try { + callClose.execute(frame, inliningTarget, PythonContext.get(inliningTarget).lookupType(PRawIOBase), T_CLOSE, self); + } catch (PException e) { + self.setClosed(); + throw e; + } + self.setClosed(); + return PNone.NONE; + } + + @Specialization(guards = {"self.isCloseFD()", "!self.isFinalizing()"}) + static Object common(VirtualFrame frame, PFileIO self, + @Bind Node inliningTarget, + @Shared("c") @Cached PosixModuleBuiltins.CloseNode posixClose, + @Shared("l") @Cached PyObjectCallMethodObjArgs callSuperClose, + @Shared @Cached PyErrChainExceptions chainExceptions) { + try { + callSuperClose.execute(frame, inliningTarget, PythonContext.get(inliningTarget).lookupType(PRawIOBase), T_CLOSE, self); + } catch (PException e) { + try { + FileIOBuiltins.internalClose(frame, self, posixClose); + } catch (PException ee) { + throw chainExceptions.execute(inliningTarget, ee, e); + } + throw e; + } + FileIOBuiltins.internalClose(frame, self, posixClose); + return PNone.NONE; + } + + @Specialization(guards = {"self.isCloseFD()", "self.isFinalizing()"}) + static Object slow(VirtualFrame frame, PFileIO self, + @Bind Node inliningTarget, + @Shared("c") @Cached PosixModuleBuiltins.CloseNode posixClose, + @Cached WarningsModuleBuiltins.WarnNode warnNode, + @Shared("l") @Cached PyObjectCallMethodObjArgs callSuperClose, + @Shared @Cached PyErrChainExceptions chainExceptions) { + PException rawIOException = null; + PythonContext context = PythonContext.get(inliningTarget); + try { + callSuperClose.execute(frame, inliningTarget, context.lookupType(PRawIOBase), T_CLOSE, self); + } catch (PException e) { + rawIOException = e; + } + FileIOBuiltins.deallocWarn(frame, self, warnNode); + try { + FileIOBuiltins.internalClose(frame, self, posixClose); + } catch (PException ee) { + if (rawIOException != null) { + throw chainExceptions.execute(inliningTarget, ee, rawIOException); + } else { + throw ee; + } + } + if (rawIOException != null) { + throw rawIOException; + } + return PNone.NONE; + } + } + + @Builtin(name = J_READABLE, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class ReadableNode extends PythonUnaryBuiltinNode { + @Specialization(guards = "!self.isClosed()") + static Object readable(PFileIO self) { + return self.isReadable(); + } + + @Specialization(guards = "self.isClosed()") + static Object closedError(@SuppressWarnings("unused") PFileIO self, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); + } + } + + @Builtin(name = J_WRITABLE, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class WritableNode extends PythonUnaryBuiltinNode { + @Specialization(guards = "!self.isClosed()") + static Object writable(PFileIO self) { + return self.isWritable(); + } + + @Specialization(guards = "self.isClosed()") + static Object closedError(@SuppressWarnings("unused") PFileIO self, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); + } + } + + @Builtin(name = J_FILENO, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class FilenoNode extends PythonBuiltinNode { + @Specialization(guards = "!self.isClosed()") + static Object fileno(PFileIO self) { + return self.getFD(); + } + + @Specialization(guards = "self.isClosed()") + static Object closedError(@SuppressWarnings("unused") PFileIO self, + @Bind Node inliningTarget) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); + } + } + + @Builtin(name = J_CLOSED, minNumOfPositionalArgs = 1, isGetter = true) + @GenerateNodeFactory + abstract static class ClosedNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doit(PFileIO self) { + return self.getFD() < 0; + } + } + + @Builtin(name = J_CLOSEFD, minNumOfPositionalArgs = 1, isGetter = true) + @GenerateNodeFactory + abstract static class CloseFDNode extends PythonUnaryBuiltinNode { + @Specialization + static Object doit(PFileIO self) { + return self.isCloseFD(); + } + } + + @Builtin(name = J_MODE, minNumOfPositionalArgs = 1, isGetter = true) + @GenerateNodeFactory + abstract static class ModeNode extends PythonUnaryBuiltinNode { + private static final TruffleString T_XB = tsLiteral("xb"); + private static final TruffleString T_XBP = tsLiteral("xb+"); + private static final TruffleString T_AB = tsLiteral("ab"); + private static final TruffleString T_ABP = tsLiteral("ab+"); + private static final TruffleString T_RB = tsLiteral("rb"); + private static final TruffleString T_RBP = tsLiteral("rb+"); + private static final TruffleString T_WB = tsLiteral("wb"); + + @Specialization + static TruffleString mode(PFileIO self) { + if (self.isCreated()) { + return self.isReadable() ? T_XBP : T_XB; + } + if (self.isAppending()) { + return self.isReadable() ? T_ABP : T_AB; + } else if (self.isReadable()) { + return self.isWritable() ? T_RBP : T_RB; + } + return T_WB; + } + } +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/FileIOBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/FileIOBuiltins.java index b308aaf531..c462d056a4 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/FileIOBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/FileIOBuiltins.java @@ -42,33 +42,24 @@ import static com.oracle.graal.python.builtins.PythonBuiltinClassType.IOUnsupportedOperation; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.OverflowError; -import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PRawIOBase; import static com.oracle.graal.python.builtins.PythonBuiltinClassType.RuntimeError; import static com.oracle.graal.python.builtins.modules.PosixModuleBuiltins.mapPythonSeekWhenceToPosix; import static com.oracle.graal.python.builtins.modules.io.BufferedIOUtil.SEEK_CUR; import static com.oracle.graal.python.builtins.modules.io.BufferedIOUtil.SEEK_END; import static com.oracle.graal.python.builtins.modules.io.IOBaseBuiltins.BUFSIZ; import static com.oracle.graal.python.builtins.modules.io.IOModuleBuiltins.DEFAULT_BUFFER_SIZE; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSE; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSED; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_CLOSEFD; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_FILENO; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_ISATTY; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_MODE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_READ; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_READABLE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_READALL; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_READINTO; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_SEEK; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_SEEKABLE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_TELL; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_TRUNCATE; -import static com.oracle.graal.python.builtins.modules.io.IONodes.J_WRITABLE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J_WRITE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J__BLKSIZE; import static com.oracle.graal.python.builtins.modules.io.IONodes.J__DEALLOC_WARN; import static com.oracle.graal.python.builtins.modules.io.IONodes.J__FINALIZING; -import static com.oracle.graal.python.builtins.modules.io.IONodes.T_CLOSE; import static com.oracle.graal.python.builtins.modules.io.IONodes.T_NAME; import static com.oracle.graal.python.builtins.objects.bytes.BytesUtils.append; import static com.oracle.graal.python.builtins.objects.bytes.BytesUtils.createOutputStream; @@ -127,10 +118,8 @@ import com.oracle.graal.python.builtins.objects.str.StringUtils.SimpleTruffleStringFormatNode; import com.oracle.graal.python.builtins.objects.type.TpSlots; import com.oracle.graal.python.builtins.objects.type.TypeNodes; -import com.oracle.graal.python.lib.PyErrChainExceptions; import com.oracle.graal.python.lib.PyIndexCheckNode; import com.oracle.graal.python.lib.PyNumberAsSizeNode; -import com.oracle.graal.python.lib.PyObjectCallMethodObjArgs; import com.oracle.graal.python.lib.PyObjectIsTrueNode; import com.oracle.graal.python.lib.PyObjectLookupAttr; import com.oracle.graal.python.lib.PyObjectReprAsTruffleStringNode; @@ -159,7 +148,6 @@ import com.oracle.truffle.api.ThreadLocalAction.Access; import com.oracle.truffle.api.dsl.Bind; import com.oracle.truffle.api.dsl.Cached; -import com.oracle.truffle.api.dsl.Cached.Exclusive; import com.oracle.truffle.api.dsl.Cached.Shared; import com.oracle.truffle.api.dsl.GenerateCached; import com.oracle.truffle.api.dsl.GenerateInline; @@ -877,74 +865,6 @@ static Object closedError(PFileIO self, Object posobj, } } - @Builtin(name = J_CLOSE, minNumOfPositionalArgs = 1) - @GenerateNodeFactory - abstract static class CloseNode extends PythonUnaryBuiltinNode { - @Specialization(guards = "!self.isCloseFD()") - static Object simple(VirtualFrame frame, PFileIO self, - @Bind Node inliningTarget, - @Exclusive @Cached PyObjectCallMethodObjArgs callClose) { - try { - callClose.execute(frame, inliningTarget, PythonContext.get(inliningTarget).lookupType(PRawIOBase), T_CLOSE, self); - } catch (PException e) { - self.setClosed(); - throw e; - } - self.setClosed(); - return PNone.NONE; - } - - @Specialization(guards = {"self.isCloseFD()", "!self.isFinalizing()"}) - static Object common(VirtualFrame frame, PFileIO self, - @Bind Node inliningTarget, - @Shared("c") @Cached PosixModuleBuiltins.CloseNode posixClose, - @Shared("l") @Cached PyObjectCallMethodObjArgs callSuperClose, - @Shared @Cached PyErrChainExceptions chainExceptions) { - try { - callSuperClose.execute(frame, inliningTarget, PythonContext.get(inliningTarget).lookupType(PRawIOBase), T_CLOSE, self); - } catch (PException e) { - try { - internalClose(frame, self, posixClose); - } catch (PException ee) { - throw chainExceptions.execute(inliningTarget, ee, e); - } - throw e; - } - internalClose(frame, self, posixClose); - return PNone.NONE; - } - - @Specialization(guards = {"self.isCloseFD()", "self.isFinalizing()"}) - static Object slow(VirtualFrame frame, PFileIO self, - @Bind Node inliningTarget, - @Shared("c") @Cached PosixModuleBuiltins.CloseNode posixClose, - @Cached WarningsModuleBuiltins.WarnNode warnNode, - @Shared("l") @Cached PyObjectCallMethodObjArgs callSuperClose, - @Shared @Cached PyErrChainExceptions chainExceptions) { - PException rawIOException = null; - PythonContext context = PythonContext.get(inliningTarget); - try { - callSuperClose.execute(frame, inliningTarget, context.lookupType(PRawIOBase), T_CLOSE, self); - } catch (PException e) { - rawIOException = e; - } - deallocWarn(frame, self, warnNode); - try { - internalClose(frame, self, posixClose); - } catch (PException ee) { - if (rawIOException != null) { - throw chainExceptions.execute(inliningTarget, ee, rawIOException); - } else { - throw ee; - } - } - if (rawIOException != null) { - throw rawIOException; - } - return PNone.NONE; - } - } - @Builtin(name = J_SEEKABLE, minNumOfPositionalArgs = 1) @GenerateNodeFactory abstract static class SeekableNode extends PythonUnaryBuiltinNode { @@ -978,51 +898,6 @@ static Object closedError(@SuppressWarnings("unused") PFileIO self, } } - @Builtin(name = J_READABLE, minNumOfPositionalArgs = 1) - @GenerateNodeFactory - abstract static class ReadableNode extends PythonUnaryBuiltinNode { - @Specialization(guards = "!self.isClosed()") - static Object readable(PFileIO self) { - return self.isReadable(); - } - - @Specialization(guards = "self.isClosed()") - static Object closedError(@SuppressWarnings("unused") PFileIO self, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); - } - } - - @Builtin(name = J_WRITABLE, minNumOfPositionalArgs = 1) - @GenerateNodeFactory - abstract static class WritableNode extends PythonUnaryBuiltinNode { - @Specialization(guards = "!self.isClosed()") - static Object writable(PFileIO self) { - return self.isWritable(); - } - - @Specialization(guards = "self.isClosed()") - static Object closedError(@SuppressWarnings("unused") PFileIO self, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); - } - } - - @Builtin(name = J_FILENO, minNumOfPositionalArgs = 1) - @GenerateNodeFactory - abstract static class FilenoNode extends PythonBuiltinNode { - @Specialization(guards = "!self.isClosed()") - static Object fileno(PFileIO self) { - return self.getFD(); - } - - @Specialization(guards = "self.isClosed()") - static Object closedError(@SuppressWarnings("unused") PFileIO self, - @Bind Node inliningTarget) { - throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); - } - } - @Builtin(name = J_ISATTY, minNumOfPositionalArgs = 1) @GenerateNodeFactory abstract static class IsattyNode extends PythonUnaryBuiltinNode { @@ -1056,54 +931,6 @@ static Object deallocWarn(VirtualFrame frame, PFileIO self, } } - @Builtin(name = J_CLOSED, minNumOfPositionalArgs = 1, isGetter = true) - @GenerateNodeFactory - abstract static class ClosedNode extends PythonUnaryBuiltinNode { - @Specialization - static Object doit(PFileIO self) { - return self.getFD() < 0; - } - } - - @Builtin(name = J_CLOSEFD, minNumOfPositionalArgs = 1, isGetter = true) - @GenerateNodeFactory - abstract static class CloseFDNode extends PythonUnaryBuiltinNode { - @Specialization - static Object doit(PFileIO self) { - return self.isCloseFD(); - } - } - - @Builtin(name = J_MODE, minNumOfPositionalArgs = 1, isGetter = true) - @GenerateNodeFactory - abstract static class ModeNode extends PythonUnaryBuiltinNode { - - public static final TruffleString T_XB = tsLiteral("xb"); - public static final TruffleString T_XBP = tsLiteral("xb+"); - public static final TruffleString T_AB = tsLiteral("ab"); - public static final TruffleString T_ABP = tsLiteral("ab+"); - public static final TruffleString T_RB = tsLiteral("rb"); - public static final TruffleString T_RBP = tsLiteral("rb+"); - public static final TruffleString T_WB = tsLiteral("wb"); - - static TruffleString modeString(PFileIO self) { - if (self.isCreated()) { - return self.isReadable() ? T_XBP : T_XB; - } - if (self.isAppending()) { - return self.isReadable() ? T_ABP : T_AB; - } else if (self.isReadable()) { - return self.isWritable() ? T_RBP : T_RB; - } - return T_WB; - } - - @Specialization - static TruffleString doit(PFileIO self) { - return modeString(self); - } - } - @Builtin(name = J__BLKSIZE, minNumOfPositionalArgs = 1, maxNumOfPositionalArgs = 2, isGetter = true, isSetter = true) @GenerateNodeFactory abstract static class BlksizeNode extends PythonBinaryBuiltinNode { @@ -1155,7 +982,7 @@ static TruffleString doit(VirtualFrame frame, PFileIO self, @Cached PyObjectReprAsTruffleStringNode repr, @Cached SimpleTruffleStringFormatNode simpleTruffleStringFormatNode, @Cached PRaiseNode raiseNode) { - TruffleString mode = ModeNode.modeString(self); + TruffleString mode = CommonFileIOBuiltins.ModeNode.mode(self); TruffleString closefd = self.isCloseFD() ? T_TRUE : T_FALSE; Object nameobj = lookupName.execute(frame, inliningTarget, self, T_NAME); if (nameobj instanceof PNone) { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/PFileIO.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/PFileIO.java index f2ff239b55..ca9c15aa0d 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/PFileIO.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/PFileIO.java @@ -80,7 +80,6 @@ public int getFD() { } public void setFD(int fd, PythonContext context) { - assert this.fd == null : "'fd' has not been closed!"; if (closefd) { this.fd = new FD(fd, context); } else { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/WindowsConsoleIOBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/WindowsConsoleIOBuiltins.java new file mode 100644 index 0000000000..be004a262d --- /dev/null +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/io/WindowsConsoleIOBuiltins.java @@ -0,0 +1,224 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The Universal Permissive License (UPL), Version 1.0 + * + * Subject to the condition set forth below, permission is hereby granted to any + * person obtaining a copy of this software, associated documentation and/or + * data (collectively the "Software"), free of charge and under any and all + * copyright rights in the Software, and any and all patent rights owned or + * freely licensable by each licensor hereunder covering either (i) the + * unmodified Software as contributed to or provided by such licensor, or (ii) + * the Larger Works (as defined below), to deal in both + * + * (a) the Software, and + * + * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if + * one is included with the Software each a "Larger Work" to which the Software + * is contributed by such licensors), + * + * without restriction, including without limitation the rights to copy, create + * derivative works of, display, perform, and distribute the Software and make, + * use, sell, offer for sale, import, export, have made, and have sold the + * Software and the Larger Work(s), and to sublicense the foregoing rights on + * either these or other terms. + * + * This license is subject to the following condition: + * + * The above copyright notice and either this complete permission notice or at a + * minimum a reference to the UPL must be included in all copies or substantial + * portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.oracle.graal.python.builtins.modules.io; + +import static com.oracle.graal.python.annotations.PythonOS.PLATFORM_WIN32; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.IOUnsupportedOperation; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.PWindowsConsoleIO; +import static com.oracle.graal.python.builtins.PythonBuiltinClassType.ValueError; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_ISATTY; +import static com.oracle.graal.python.builtins.modules.io.IONodes.J_WRITE; +import static com.oracle.graal.python.builtins.modules.io.IONodes.T_NAME; +import static com.oracle.graal.python.nodes.ErrorMessages.CANNOT_OPEN_CONSOLE_INPUT_BUFFER_FOR_WRITING; +import static com.oracle.graal.python.nodes.ErrorMessages.CANNOT_OPEN_NON_CONSOLE_FILE; +import static com.oracle.graal.python.nodes.ErrorMessages.CONSOLE_BUFFER_DOES_NOT_SUPPORT_WRITING; +import static com.oracle.graal.python.nodes.ErrorMessages.CONSOLE_INPUT_IS_NOT_SUPPORTED; +import static com.oracle.graal.python.nodes.ErrorMessages.IO_CLOSED; +import static com.oracle.graal.python.nodes.ErrorMessages.MUST_HAVE_EXACTLY_ONE_OF_READ_WRITE_MODE; +import static com.oracle.graal.python.nodes.ErrorMessages.NEG_FILE_DESC; + +import java.util.List; + +import com.oracle.graal.python.PythonLanguage; +import com.oracle.graal.python.annotations.ArgumentClinic; +import com.oracle.graal.python.annotations.Builtin; +import com.oracle.graal.python.annotations.Slot; +import com.oracle.graal.python.annotations.Slot.SlotKind; +import com.oracle.graal.python.annotations.Slot.SlotSignature; +import com.oracle.graal.python.builtins.CoreFunctions; +import com.oracle.graal.python.builtins.PythonBuiltins; +import com.oracle.graal.python.builtins.objects.PNone; +import com.oracle.graal.python.builtins.objects.buffer.PythonBufferAccessLibrary; +import com.oracle.graal.python.builtins.objects.type.TpSlots; +import com.oracle.graal.python.builtins.objects.type.TypeNodes; +import com.oracle.graal.python.nodes.PConstructAndRaiseNode; +import com.oracle.graal.python.nodes.PRaiseNode; +import com.oracle.graal.python.nodes.attributes.WriteAttributeToObjectNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; +import com.oracle.graal.python.nodes.function.PythonBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonClinicBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonUnaryBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; +import com.oracle.graal.python.runtime.GilNode; +import com.oracle.graal.python.runtime.PosixSupportLibrary; +import com.oracle.graal.python.runtime.PosixSupportLibrary.Buffer; +import com.oracle.graal.python.runtime.PosixSupportLibrary.PosixException; +import com.oracle.graal.python.runtime.PythonContext; +import com.oracle.graal.python.runtime.object.PFactory; +import com.oracle.truffle.api.dsl.Bind; +import com.oracle.truffle.api.dsl.Cached; +import com.oracle.truffle.api.dsl.GenerateNodeFactory; +import com.oracle.truffle.api.dsl.NodeFactory; +import com.oracle.truffle.api.dsl.Specialization; +import com.oracle.truffle.api.frame.VirtualFrame; +import com.oracle.truffle.api.library.CachedLibrary; +import com.oracle.truffle.api.nodes.Node; + +@CoreFunctions(extendClasses = PWindowsConsoleIO, os = PLATFORM_WIN32) +public final class WindowsConsoleIOBuiltins extends PythonBuiltins { + private static final int CONSOLE_WRITE = 'w'; + public static final TpSlots SLOTS = WindowsConsoleIOBuiltinsSlotsGen.SLOTS; + + @Override + protected List> getNodeFactories() { + return WindowsConsoleIOBuiltinsFactory.getFactories(); + } + + public static PFileIO create(PythonLanguage language) { + return PFactory.createFileIO(PWindowsConsoleIO, PWindowsConsoleIO.getInstanceShape(language)); + } + + public static void internalInit(PFileIO self, Object name, int fd, IONodes.IOMode mode, int consoleType) { + if (mode.reading == mode.writing || mode.text || mode.universal || mode.isInvalid) { + throw PRaiseNode.raiseStatic(null, ValueError, MUST_HAVE_EXACTLY_ONE_OF_READ_WRITE_MODE); + } + if (mode.reading) { + throw PRaiseNode.raiseStatic(null, ValueError, CONSOLE_INPUT_IS_NOT_SUPPORTED); + } + if (consoleType != CONSOLE_WRITE) { + throw PRaiseNode.raiseStatic(null, ValueError, CANNOT_OPEN_CONSOLE_INPUT_BUFFER_FOR_WRITING); + } + self.setCloseFD(false); + self.setFD(fd, null); + self.setWritable(); + self.setBlksize(IOModuleBuiltins.DEFAULT_BUFFER_SIZE); + WriteAttributeToObjectNode.getUncached().execute(self, T_NAME, name); + } + + @Slot(value = SlotKind.tp_new, isComplex = true) + @SlotSignature(name = "_WindowsConsoleIO", minNumOfPositionalArgs = 1, takesVarArgs = true, takesVarKeywordArgs = true) + @GenerateNodeFactory + abstract static class NewNode extends PythonBuiltinNode { + @Specialization + static PFileIO create(Object cls, @SuppressWarnings("unused") Object file, + @Cached TypeNodes.GetInstanceShape getInstanceShape) { + return PFactory.createFileIO(cls, getInstanceShape.execute(cls)); + } + } + + @Slot(value = SlotKind.tp_init, isComplex = true) + @SlotSignature(name = "_WindowsConsoleIO", minNumOfPositionalArgs = 2, parameterNames = {"$self", "file", "mode", "closefd", "opener"}) + @ArgumentClinic(name = "file", conversion = ArgumentClinic.ClinicConversion.Index) + @ArgumentClinic(name = "mode", conversionClass = IONodes.CreateIOModeNode.class, args = "false") + @ArgumentClinic(name = "closefd", conversion = ArgumentClinic.ClinicConversion.Boolean, defaultValue = "true", useDefaultForNone = true) + @GenerateNodeFactory + public abstract static class InitNode extends PythonClinicBuiltinNode { + @Override + protected ArgumentClinicProvider getArgumentClinic() { + return WindowsConsoleIOBuiltinsClinicProviders.InitNodeClinicProviderGen.INSTANCE; + } + + @Specialization + static PNone init(PFileIO self, int file, IONodes.IOMode mode, @SuppressWarnings("unused") boolean closefd, @SuppressWarnings("unused") Object opener, + @Bind Node inliningTarget, + @Bind PythonContext context, + @CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib) { + if (file < 0) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, NEG_FILE_DESC); + } + // We don't release the GIL because CPython doesn't do it + int consoleType = posixLib.getWindowsConsoleType(context.getPosixSupport(), file); + if (consoleType == 0) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, CANNOT_OPEN_NON_CONSOLE_FILE); + } + internalInit(self, file, file, mode, consoleType); + return PNone.NONE; + } + } + + @Builtin(name = J_WRITE, minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 2, parameterNames = {"$self", "b"}) + @ArgumentClinic(name = "b", conversion = ArgumentClinic.ClinicConversion.ReadableBuffer) + @GenerateNodeFactory + abstract static class WriteNode extends PythonBinaryClinicBuiltinNode { + @Override + protected ArgumentClinicProvider getArgumentClinic() { + return WindowsConsoleIOBuiltinsClinicProviders.WriteNodeClinicProviderGen.INSTANCE; + } + + @Specialization(limit = "3") + static Object write(VirtualFrame frame, PFileIO self, Object buffer, + @Bind Node inliningTarget, + @Bind PythonContext context, + @CachedLibrary("buffer") PythonBufferAccessLibrary bufferLib, + @CachedLibrary("context.getPosixSupport()") PosixSupportLibrary posixLib, + @Cached GilNode gil, + @Cached PConstructAndRaiseNode.Lazy constructAndRaiseNode) { + try { + if (self.isClosed()) { + throw PRaiseNode.raiseStatic(inliningTarget, ValueError, IO_CLOSED); + } + if (!self.isWritable()) { + throw PRaiseNode.raiseStatic(inliningTarget, IOUnsupportedOperation, CONSOLE_BUFFER_DOES_NOT_SUPPORT_WRITING); + } + byte[] bytes = bufferLib.getInternalOrCopiedByteArray(buffer); + int length = bufferLib.getBufferLength(buffer); + try { + gil.release(true); + try { + return posixLib.writeWindowsConsole(context.getPosixSupport(), self.getFD(), new Buffer(bytes, length)); + } finally { + gil.acquire(); + } + } catch (PosixException e) { + throw constructAndRaiseNode.get(inliningTarget).raiseOSErrorFromPosixException(frame, e); + } + } finally { + bufferLib.release(buffer); + } + } + } + + @Builtin(name = J_ISATTY, minNumOfPositionalArgs = 1) + @GenerateNodeFactory + abstract static class IsattyNode extends PythonUnaryBuiltinNode { + @Specialization + static boolean isatty(PFileIO self, + @Bind Node inliningTarget, + @Cached PRaiseNode raise) { + if (self.isClosed()) { + throw raise.raise(inliningTarget, ValueError, IO_CLOSED); + } + return true; + } + } + +} diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index e8c7d2ad1d..faba298067 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -1155,6 +1155,11 @@ public abstract class ErrorMessages { public static final TruffleString DETACHED_BUFFER = tsLiteral("underlying buffer has been detached"); public static final TruffleString UNSUPPORTED_WHENCE = tsLiteral("whence value %d unsupported"); public static final TruffleString IO_CLOSED = tsLiteral("I/O operation on closed file."); + public static final TruffleString CANNOT_OPEN_CONSOLE_INPUT_BUFFER_FOR_WRITING = tsLiteral("Cannot open console input buffer for writing"); + public static final TruffleString CANNOT_OPEN_NON_CONSOLE_FILE = tsLiteral("Cannot open non-console file"); + public static final TruffleString CONSOLE_BUFFER_DOES_NOT_SUPPORT_WRITING = tsLiteral("Console buffer does not support writing"); + public static final TruffleString CONSOLE_INPUT_IS_NOT_SUPPORTED = tsLiteral("Console input is not supported"); + public static final TruffleString MUST_HAVE_EXACTLY_ONE_OF_READ_WRITE_MODE = tsLiteral("Must have exactly one of read or write mode"); public static final TruffleString MUST_BE_NON_NEG_OR_NEG_1 = tsLiteral("read length must be non-negative or -1"); public static final TruffleString BUF_SIZE_POS = tsLiteral("buffer size must be strictly positive"); public static final TruffleString S_SHOULD_RETURN_BYTES = tsLiteral("%s should return bytes"); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java index 92241c2cf8..43c6d963b6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/EmulatedPosixSupport.java @@ -514,6 +514,20 @@ public long write(int fd, Buffer data, } } + @ExportMessage + @SuppressWarnings("unused") + public int getWindowsConsoleType(int fd) { + return 0; + } + + @ExportMessage + public long writeWindowsConsole(int fd, Buffer data, + @Bind Node inliningTarget, + @Shared("errorBranch") @Cached InlinedBranchProfile errorBranch, + @Shared("eq") @Cached TruffleString.EqualNode eqNode) throws PosixException { + return write(fd, data, inliningTarget, errorBranch, eqNode); + } + @TruffleBoundary(allowInlining = true) private static int doWriteOp(ByteBuffer data, WritableByteChannel channel) throws IOException { return channel.write(data); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java index f6407c5abc..7313ce5349 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/LoggingPosixSupport.java @@ -198,6 +198,24 @@ final long write(int fd, Buffer data, } } + @ExportMessage + final int getWindowsConsoleType(int fd, + @CachedLibrary("this.delegate") PosixSupportLibrary lib) { + logEnter("getWindowsConsoleType", "%d", fd); + return logExit("getWindowsConsoleType", "%d", lib.getWindowsConsoleType(delegate, fd)); + } + + @ExportMessage + final long writeWindowsConsole(int fd, Buffer data, + @CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException { + logEnter("writeWindowsConsole", "%d, %d", fd, data.length); + try { + return logExit("writeWindowsConsole", "%d", lib.writeWindowsConsole(delegate, fd, data)); + } catch (PosixException e) { + throw logException("writeWindowsConsole", e); + } + } + @ExportMessage final int dup(int fd, @CachedLibrary("this.delegate") PosixSupportLibrary lib) throws PosixException { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java index 0653be98fe..3445b018d6 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/NativePosixSupport.java @@ -231,6 +231,12 @@ abstract static class PosixNativeFunctionInvoker { @DowncallSignature(returnType = SINT64, argumentTypes = {SINT32, POINTER, SINT64}) abstract long call_write(int fd, long buf, long count); + @DowncallSignature(returnType = SINT32, argumentTypes = {SINT32}) + abstract int call_get_windows_console_type(int fd); + + @DowncallSignature(returnType = SINT64, argumentTypes = {SINT32, POINTER, SINT64}) + abstract long call_write_windows_console(int fd, long buf, long count); + @DowncallSignature(returnType = SINT32, argumentTypes = {SINT32}) abstract int call_dup(int fd); @@ -795,6 +801,26 @@ public long write(int fd, Buffer data) throws PosixException { } } + @ExportMessage + public int getWindowsConsoleType(int fd) { + return posixNativeFunctionInvoker.call_get_windows_console_type(fd); + } + + @ExportMessage + public long writeWindowsConsole(int fd, Buffer data) throws PosixException { + long nativeBuffer = NativeMemory.mallocByteArrayOrNull(data.length); + try { + NativeMemory.writeByteArrayElements(nativeBuffer, 0, data.data, 0, (int) data.length); + long n = posixNativeFunctionInvoker.call_write_windows_console(fd, nativeBuffer, data.length); + if (n < 0) { + throw getErrnoAndThrowPosixException(); + } + return n; + } finally { + NativeMemory.free(nativeBuffer); + } + } + @ExportMessage public int dup(int fd) throws PosixException { int newFd = posixNativeFunctionInvoker.call_dup(fd); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java index 1d4ddb47be..3a7b18cc1f 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PosixSupportLibrary.java @@ -96,6 +96,11 @@ public abstract class PosixSupportLibrary extends Library { public abstract long write(Object receiver, int fd, Buffer data) throws PosixException; + /** Returns {@code 'r'}, {@code 'w'}, or zero when {@code fd} is not a Windows console. */ + public abstract int getWindowsConsoleType(Object receiver, int fd); + + public abstract long writeWindowsConsole(Object receiver, int fd, Buffer data) throws PosixException; + public abstract int dup(Object receiver, int fd) throws PosixException; public abstract int dup2(Object receiver, int fd, int fd2, boolean inheritable) throws PosixException; diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java index eff964a8bd..81d70eafda 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PreInitPosixSupport.java @@ -212,6 +212,22 @@ final long write(int fd, Buffer data, return nativeLib.write(nativePosixSupport, fd, data); } + @ExportMessage + final int getWindowsConsoleType(int fd, + @CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) { + if (inPreInitialization) { + return 0; + } + return nativeLib.getWindowsConsoleType(nativePosixSupport, fd); + } + + @ExportMessage + final long writeWindowsConsole(int fd, Buffer data, + @CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException { + checkNotInPreInitialization(); + return nativeLib.writeWindowsConsole(nativePosixSupport, fd, data); + } + @ExportMessage final int dup(int fd, @CachedLibrary("this.nativePosixSupport") PosixSupportLibrary nativeLib) throws PosixException { diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java index 962f9298e7..dc51eceb29 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/runtime/PythonOptions.java @@ -143,6 +143,9 @@ private PythonOptions() { @Option(category = OptionCategory.USER, help = "Equivalent to the Python -u flag. Force stdout and stderr to be unbuffered.", usageSyntax = "true|false", stability = OptionStability.STABLE) // public static final OptionKey UnbufferedIO = new OptionKey<>(false); + @Option(category = OptionCategory.USER, help = "Equivalent to setting the PYTHONLEGACYWINDOWSSTDIO environment variable for the standard launcher.", usageSyntax = "true|false", stability = OptionStability.STABLE) // + public static final OptionKey LegacyWindowsStdio = new OptionKey<>(false); + @Option(category = OptionCategory.USER, help = "Equivalent to the Python -I flag. Isolate from the users environment by not adding the cwd to the path", usageSyntax = "true|false", stability = OptionStability.STABLE) // public static final OptionKey IsolateFlag = new OptionKey<>(false); diff --git a/graalpython/python-libposix/src/posix.c b/graalpython/python-libposix/src/posix.c index df8d2bb45b..ae205e8e31 100644 --- a/graalpython/python-libposix/src/posix.c +++ b/graalpython/python-libposix/src/posix.c @@ -1161,6 +1161,123 @@ GP_EXPORT int64_t call_write(int32_t fd, void *buf, uint64_t count) { return write_noraise(fd, buf, count > INT_MAX ? INT_MAX : (unsigned int) count); } +GP_EXPORT int32_t call_get_windows_console_type(int32_t fd) { + intptr_t osfhandle = get_osfhandle_noraise(fd); + if (osfhandle == -1) { + return 0; + } + HANDLE handle = (HANDLE) osfhandle; + DWORD mode; + if (!GetConsoleMode(handle, &mode)) { + return 0; + } + DWORD event_count; + return GetNumberOfConsoleInputEvents(handle, &event_count) ? 'r' : 'w'; +} + +/* Copied from CPython's Modules/_io/winconsoleio.c (_find_last_utf8_boundary). */ +static uint32_t find_last_utf8_boundary(const unsigned char *buf, uint32_t len) { + for (uint32_t count = 1; count < 4 && count <= len; count++) { + unsigned char c = buf[len - count]; + if (c < 0x80) { + return len; + } + if (c >= 0xc0) { + if (c < 0xe0 ? count < 2 : c < 0xf0 ? count < 3 : c < 0xf8 ? count < 4 : 0) { + return len - count; + } + return len; + } + } + return len; +} + +static uint32_t wchar_to_utf8_count(const unsigned char *s, uint32_t len, uint32_t n) { + uint32_t start = 0; + while (1) { + uint32_t mid = 0; + for (uint32_t i = len / 2; i <= len; i++) { + mid = find_last_utf8_boundary(s, i); + if (mid != 0) { + break; + } + } + if (mid == len) { + uint32_t wlen = MultiByteToWideChar(CP_UTF8, 0, (const char *) s, len, NULL, 0); + return wlen <= n ? start + len : start; + } + if (mid == 0) { + mid = len > 1 ? len - 1 : 1; + } + uint32_t wlen = MultiByteToWideChar(CP_UTF8, 0, (const char *) s, mid, NULL, 0); + if (wlen <= n) { + s += mid; + start += mid; + len -= mid; + n -= wlen; + } else { + len = mid; + } + } +} + +GP_EXPORT int64_t call_write_windows_console(int32_t fd, const unsigned char *buf, uint64_t count) { + intptr_t osfhandle = get_osfhandle_noraise(fd); + if (osfhandle == -1) { + capture_errno(); + return -1; + } + if (count == 0) { + return 0; + } + + const uint32_t max_wlen = 32766U / sizeof(wchar_t); + // Like CPython, write at most one bounded chunk and let the caller handle partial writes. + uint32_t len = count > max_wlen * 3U ? max_wlen * 3U : (uint32_t) count; + uint32_t wlen; + while (1) { + uint32_t boundary = find_last_utf8_boundary(buf, len); + // If the whole chunk is incomplete UTF-8, convert it to replacement characters rather + // than returning zero forever when a buffered caller tries to flush it. + if (boundary != 0) { + len = boundary; + } + wlen = MultiByteToWideChar(CP_UTF8, 0, (const char *) buf, len, NULL, 0); + if (wlen == 0) { + set_win_errno(GetLastError()); + return -1; + } + if (wlen <= max_wlen) { + break; + } + len /= 2; + } + + wchar_t *wbuf = (wchar_t *) malloc(wlen * sizeof(wchar_t)); + if (wbuf == NULL) { + set_posix_errno(ENOMEM); + return -1; + } + wlen = MultiByteToWideChar(CP_UTF8, 0, (const char *) buf, len, wbuf, wlen); + DWORD written = 0; + BOOL result = wlen && WriteConsoleW((HANDLE) osfhandle, wbuf, wlen, &written, NULL); + if (!result) { + DWORD error = GetLastError(); + free(wbuf); + set_win_errno(error); + return -1; + } + free(wbuf); + if (written < wlen) { + len = wchar_to_utf8_count(buf, len, written); + } + if (len == 0) { + set_posix_errno(EIO); + return -1; + } + return len; +} + GP_EXPORT int32_t call_dup(int32_t fd) { if (win_socket_entry_index(fd) >= 0) { return win_socket_dup(fd); @@ -2443,6 +2560,20 @@ int64_t call_write(int32_t fd, void *buf, uint64_t count) { CAPTURE_ERRNO_AND_RETURN(-1, write(fd, buf, count)); } +int32_t call_get_windows_console_type(int32_t fd) { + (void) fd; + return 0; +} + +int64_t call_write_windows_console(int32_t fd, const void *buf, uint64_t count) { + (void) fd; + (void) buf; + (void) count; + errno = ENOSYS; + capture_errno(); + return -1; +} + int32_t call_dup(int32_t fd) { CAPTURE_ERRNO_AND_RETURN(-1, fcntl(fd, F_DUPFD_CLOEXEC, 0)); }