this repo has no description
1// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
2#include "under-io-module.h"
3
4#include "builtins.h"
5#include "bytes-builtins.h"
6#include "byteslike.h"
7#include "file.h"
8#include "frame.h"
9#include "globals.h"
10#include "int-builtins.h"
11#include "modules.h"
12#include "object-builtins.h"
13#include "objects.h"
14#include "os.h"
15#include "runtime.h"
16#include "str-builtins.h"
17#include "thread.h"
18#include "type-builtins.h"
19#include "unicode.h"
20
21namespace py {
22
23RawObject FUNC(_io, _BytesIO_guard)(Thread* thread, Arguments args) {
24 HandleScope scope(thread);
25 Object self_obj(&scope, args.get(0));
26 if (!thread->runtime()->isInstanceOfBytesIO(*self_obj)) {
27 return thread->raiseRequiresType(self_obj, ID(BytesIO));
28 }
29 return NoneType::object();
30}
31
32RawObject FUNC(_io, _BytesIO_closed_guard)(Thread* thread, Arguments args) {
33 HandleScope scope(thread);
34 Object self_obj(&scope, args.get(0));
35 if (!thread->runtime()->isInstanceOfBytesIO(*self_obj)) {
36 return thread->raiseRequiresType(self_obj, ID(BytesIO));
37 }
38 BytesIO self(&scope, *self_obj);
39 if (self.closed()) {
40 return thread->raiseWithFmt(LayoutId::kValueError,
41 "I/O operation on closed file.");
42 }
43 return NoneType::object();
44}
45
46RawObject FUNC(_io, _BytesIO_seek)(Thread* thread, Arguments args) {
47 HandleScope scope(thread);
48
49 Runtime* runtime = thread->runtime();
50 Object offset_obj(&scope, args.get(1));
51 if (!runtime->isInstanceOfInt(*offset_obj)) {
52 return Unbound::object();
53 }
54
55 Object whence_obj(&scope, args.get(2));
56 if (!runtime->isInstanceOfInt(*whence_obj)) {
57 return Unbound::object();
58 }
59
60 Object self_obj(&scope, args.get(0));
61 if (!runtime->isInstanceOfBytesIO(*self_obj)) {
62 return thread->raiseRequiresType(self_obj, ID(BytesIO));
63 }
64 BytesIO self(&scope, *self_obj);
65 if (self.closed()) {
66 return thread->raiseWithFmt(LayoutId::kValueError,
67 "I/O operation on closed file.");
68 }
69
70 Int offset_int(&scope, intUnderlying(*offset_obj));
71 word offset = offset_int.asWordSaturated();
72 if (!SmallInt::isValid(offset)) {
73 return thread->raiseWithFmt(
74 LayoutId::kOverflowError,
75 "cannot fit offset into an index-sized integer");
76 }
77 Int whence_int(&scope, intUnderlying(*whence_obj));
78 word whence = whence_int.asWordSaturated();
79 word result;
80 switch (whence) {
81 case 0:
82 if (offset < 0) {
83 return thread->raiseWithFmt(LayoutId::kValueError,
84 "Negative seek value %d", offset);
85 }
86 self.setPos(offset);
87 return SmallInt::fromWord(offset);
88 case 1:
89 result = Utils::maximum(word{0}, self.pos() + offset);
90 self.setPos(result);
91 return SmallInt::fromWord(result);
92 case 2:
93 result = Utils::maximum(
94 word{0}, MutableBytes::cast(self.buffer()).length() + offset);
95 self.setPos(result);
96 return SmallInt::fromWord(result);
97 default:
98 if (SmallInt::isValid(whence)) {
99 return thread->raiseWithFmt(LayoutId::kValueError,
100 "Invalid whence (%w, should be 0, 1 or 2)",
101 whence);
102 }
103 return thread->raiseWithFmt(LayoutId::kOverflowError,
104 "Python int too large to convert to C long");
105 }
106}
107
108RawObject FUNC(_io, _BytesIO_truncate)(Thread* thread, Arguments args) {
109 HandleScope scope(thread);
110 Object self(&scope, args.get(0));
111 Runtime* runtime = thread->runtime();
112 if (!runtime->isInstanceOfBytesIO(*self)) {
113 return thread->raiseRequiresType(self, ID(BytesIO));
114 }
115 BytesIO bytes_io(&scope, *self);
116 if (bytes_io.closed()) {
117 return thread->raiseWithFmt(LayoutId::kValueError,
118 "I/O operation on closed file.");
119 }
120 Object size_obj(&scope, args.get(1));
121 word size;
122 if (size_obj.isNoneType()) {
123 size = bytes_io.pos();
124 } else {
125 if (size_obj.isError()) return *size_obj;
126 Int size_int(&scope, intUnderlying(*size_obj));
127 // Allow SmallInt, Bool, and subclasses of Int containing SmallInt or Bool
128 if (!size_int.isSmallInt() && !size_int.isBool()) {
129 return thread->raiseWithFmt(LayoutId::kOverflowError,
130 "cannot fit '%T' into an index-sized integer",
131 &size_int);
132 }
133 size = size_int.asWord();
134 if (size < 0) {
135 return thread->raiseWithFmt(LayoutId::kValueError,
136 "negative size value %d", size);
137 }
138 }
139 if (size < bytes_io.numItems()) {
140 bytes_io.setNumItems(size);
141 bytes_io.setPos(size);
142 }
143 return SmallInt::fromWord(size);
144}
145
146RawObject FUNC(_io, _StringIO_closed_guard)(Thread* thread, Arguments args) {
147 HandleScope scope(thread);
148 Runtime* runtime = thread->runtime();
149 Object self_obj(&scope, args.get(0));
150 if (!runtime->isInstanceOfStringIO(*self_obj)) {
151 return thread->raiseRequiresType(self_obj, ID(StringIO));
152 }
153 StringIO self(&scope, *self_obj);
154 if (self.closed()) {
155 return thread->raiseWithFmt(LayoutId::kValueError,
156 "I/O operation on closed file.");
157 }
158 return NoneType::object();
159}
160
161RawObject FUNC(_io, _StringIO_seek)(Thread* thread, Arguments args) {
162 HandleScope scope(thread);
163 Object offset_obj(&scope, args.get(1));
164 Object whence_obj(&scope, args.get(2));
165 Runtime* runtime = thread->runtime();
166 if (!runtime->isInstanceOfInt(*offset_obj) ||
167 !runtime->isInstanceOfInt(*whence_obj)) {
168 return Unbound::object();
169 }
170 Object self_obj(&scope, args.get(0));
171 if (!runtime->isInstanceOfStringIO(*self_obj)) {
172 return thread->raiseRequiresType(self_obj, ID(StringIO));
173 }
174 StringIO self(&scope, *self_obj);
175 if (self.closed()) {
176 return thread->raiseWithFmt(LayoutId::kValueError,
177 "I/O operation on closed file.");
178 }
179 word offset = intUnderlying(*offset_obj).asWordSaturated();
180 if (!SmallInt::isValid(offset)) {
181 return thread->raiseWithFmt(
182 LayoutId::kOverflowError,
183 "cannot fit offset into an index-sized integer");
184 }
185 if (!runtime->isInstanceOfInt(*whence_obj)) {
186 return thread->raiseWithFmt(LayoutId::kTypeError,
187 "Invalid whence (should be 0, 1 or 2)");
188 }
189 word whence = intUnderlying(*whence_obj).asWordSaturated();
190 switch (whence) {
191 case 0:
192 if (offset < 0) {
193 return thread->raiseWithFmt(LayoutId::kValueError,
194 "Negative seek position %d", offset);
195 }
196 self.setPos(offset);
197 return *offset_obj;
198 case 1:
199 if (offset != 0) {
200 return thread->raiseWithFmt(LayoutId::kOSError,
201 "Can't do nonzero cur-relative seeks");
202 }
203 return SmallInt::fromWord(self.pos());
204 case 2: {
205 if (offset != 0) {
206 return thread->raiseWithFmt(LayoutId::kOSError,
207 "Can't do nonzero end-relative seeks");
208 }
209 word new_pos = MutableBytes::cast(self.buffer()).length();
210 self.setPos(new_pos);
211 return SmallInt::fromWord(new_pos);
212 }
213 default:
214 if (SmallInt::isValid(whence)) {
215 return thread->raiseWithFmt(LayoutId::kValueError,
216 "Invalid whence (%w, should be 0, 1 or 2)",
217 whence);
218 } else {
219 return thread->raiseWithFmt(
220 LayoutId::kOverflowError,
221 "Python int too large to convert to C long");
222 }
223 }
224}
225
226static RawObject initReadBuf(Thread* thread,
227 const BufferedReader& buffered_reader) {
228 HandleScope scope(thread);
229 word buffer_size = buffered_reader.bufferSize();
230 MutableBytes read_buf(
231 &scope, thread->runtime()->newMutableBytesUninitialized(buffer_size));
232 buffered_reader.setReadBuf(*read_buf);
233 buffered_reader.setReadPos(0);
234 buffered_reader.setBufferNumBytes(0);
235 return *read_buf;
236}
237
238// If there is no buffer allocated yet, allocate one. If there are remaining
239// bytes in the buffer, move them to position 0; Set buffer read position to 0.
240static RawObject rewindOrInitReadBuf(Thread* thread,
241 const BufferedReader& buffered_reader) {
242 HandleScope scope(thread);
243 Object read_buf_obj(&scope, buffered_reader.readBuf());
244 word read_pos = buffered_reader.readPos();
245 if (read_pos > 0) {
246 MutableBytes read_buf(&scope, *read_buf_obj);
247 word buffer_num_bytes = buffered_reader.bufferNumBytes();
248 read_buf.replaceFromWithStartAt(0, *read_buf, buffer_num_bytes - read_pos,
249 read_pos);
250 buffered_reader.setBufferNumBytes(buffer_num_bytes - read_pos);
251 buffered_reader.setReadPos(0);
252 return *read_buf;
253 }
254 if (read_buf_obj.isNoneType()) {
255 return initReadBuf(thread, buffered_reader);
256 }
257 return *read_buf_obj;
258}
259
260// Perform one read operation to re-fill the buffer.
261static RawObject fillBuffer(Thread* thread, const Object& raw_file,
262 const MutableBytes& buffer,
263 word* buffer_num_bytes) {
264 HandleScope scope(thread);
265 word buffer_size = buffer.length();
266 word wanted = buffer_size - *buffer_num_bytes;
267 Object wanted_int(&scope, SmallInt::fromWord(wanted));
268 Object result_obj(&scope,
269 thread->invokeMethod2(raw_file, ID(read), wanted_int));
270 if (result_obj.isError()) {
271 if (result_obj.isErrorException()) return *result_obj;
272 if (result_obj.isErrorNotFound()) {
273 if (raw_file.isNoneType()) {
274 return thread->raiseWithFmt(LayoutId::kValueError,
275 "raw stream has been detached");
276 }
277 Object name(&scope, thread->runtime()->symbols()->at(ID(read)));
278 return objectRaiseAttributeError(thread, raw_file, name);
279 }
280 }
281 if (result_obj.isNoneType()) return NoneType::object();
282
283 Runtime* runtime = thread->runtime();
284 Bytes bytes(&scope, Bytes::empty());
285 word length;
286 if (runtime->isInstanceOfBytes(*result_obj)) {
287 bytes = bytesUnderlying(*result_obj);
288 length = bytes.length();
289 } else if (runtime->isInstanceOfBytearray(*result_obj)) {
290 Bytearray byte_array(&scope, *result_obj);
291 bytes = byte_array.items();
292 length = byte_array.numItems();
293 } else if (runtime->isByteslike(*result_obj)) {
294 UNIMPLEMENTED("byteslike");
295 } else {
296 return thread->raiseWithFmt(LayoutId::kTypeError,
297 "read() should return bytes");
298 }
299 if (length == 0) return Bytes::empty();
300 if (length > wanted && wanted != -1) {
301 UNIMPLEMENTED("read() returned too many bytes");
302 }
303 buffer.replaceFromWithBytes(*buffer_num_bytes, *bytes, length);
304 *buffer_num_bytes += length;
305 return Unbound::object();
306}
307
308// Helper function for read requests that are bigger (or close to) than the size
309// of the buffer.
310static RawObject readBig(Thread* thread, const BufferedReader& buffered_reader,
311 word num_bytes) {
312 HandleScope scope(thread);
313 Runtime* runtime = thread->runtime();
314 word available = buffered_reader.bufferNumBytes() - buffered_reader.readPos();
315 DCHECK(num_bytes == kMaxWord || num_bytes > available,
316 "num_bytes should be big");
317
318 // TODO(T59000373): We could specialize this to avoid the intermediate
319 // allocations when the size of the result is known and `readinto` is
320 // available.
321
322 word length = available;
323 Object chunks(&scope, NoneType::object());
324 Object chunk(&scope, NoneType::object());
325 Object raw_file(&scope, buffered_reader.underlying());
326 Bytes bytes(&scope, Bytes::empty());
327 for (;;) {
328 word wanted = (num_bytes == kMaxWord) ? 32 * kKiB : num_bytes - available;
329 Object wanted_int(&scope, SmallInt::fromWord(wanted));
330 Object result_obj(&scope,
331 thread->invokeMethod2(raw_file, ID(read), wanted_int));
332 if (result_obj.isError()) {
333 if (result_obj.isErrorException()) return *result_obj;
334 if (result_obj.isErrorNotFound()) {
335 if (raw_file.isNoneType()) {
336 return thread->raiseWithFmt(LayoutId::kValueError,
337 "raw stream has been detached");
338 }
339 Object name(&scope, runtime->symbols()->at(ID(read)));
340 return objectRaiseAttributeError(thread, raw_file, name);
341 }
342 }
343 if (result_obj.isNoneType()) {
344 if (length == 0) return NoneType::object();
345 break;
346 }
347
348 word chunk_length;
349 if (runtime->isInstanceOfBytes(*result_obj)) {
350 bytes = bytesUnderlying(*result_obj);
351 chunk = *bytes;
352 chunk_length = bytes.length();
353 } else if (runtime->isInstanceOfBytearray(*result_obj)) {
354 Bytearray byte_array(&scope, *result_obj);
355 bytes = byte_array.items();
356 chunk = *byte_array;
357 chunk_length = byte_array.numItems();
358 } else if (runtime->isByteslike(*result_obj)) {
359 UNIMPLEMENTED("byteslike");
360 } else {
361 return thread->raiseWithFmt(LayoutId::kTypeError,
362 "read() should return bytes");
363 }
364
365 if (chunk_length == 0) {
366 if (length == 0) return *chunk;
367 break;
368 }
369 if (chunk_length > wanted) {
370 UNIMPLEMENTED("read() returned too many bytes");
371 }
372
373 if (chunks.isNoneType()) {
374 chunks = runtime->newList();
375 }
376 List list(&scope, *chunks);
377 runtime->listAdd(thread, list, chunk);
378
379 length += chunk_length;
380 if (num_bytes != kMaxWord) {
381 num_bytes -= chunk_length;
382 if (num_bytes <= 0) break;
383 }
384 }
385
386 MutableBytes result(&scope, runtime->newMutableBytesUninitialized(length));
387 word idx = 0;
388 if (available > 0) {
389 result.replaceFromWithStartAt(idx,
390 MutableBytes::cast(buffered_reader.readBuf()),
391 available, buffered_reader.readPos());
392 idx += available;
393 buffered_reader.setReadPos(0);
394 buffered_reader.setBufferNumBytes(0);
395 }
396 if (!chunks.isNoneType()) {
397 List list(&scope, *chunks);
398 for (word i = 0, num_items = list.numItems(); i < num_items; i++) {
399 chunk = list.at(i);
400 word chunk_length;
401 if (chunk.isBytes()) {
402 bytes = *chunk;
403 chunk_length = bytes.length();
404 } else {
405 Bytearray byte_array(&scope, *chunk);
406 bytes = byte_array.items();
407 chunk_length = byte_array.numItems();
408 }
409 result.replaceFromWithBytes(idx, *bytes, chunk_length);
410 idx += chunk_length;
411 }
412 }
413 DCHECK(idx == length, "mismatched length");
414 return result.becomeImmutable();
415}
416
417RawObject FUNC(_io, _buffered_reader_clear_buffer)(Thread* thread,
418 Arguments args) {
419 HandleScope scope(thread);
420 Runtime* runtime = thread->runtime();
421 Object self_obj(&scope, args.get(0));
422 if (!runtime->isInstanceOfBufferedReader(*self_obj)) {
423 return thread->raiseRequiresType(self_obj, ID(BufferedReader));
424 }
425 BufferedReader self(&scope, *self_obj);
426 self.setReadPos(0);
427 self.setBufferNumBytes(0);
428 return NoneType::object();
429}
430
431RawObject FUNC(_io, _buffered_reader_init)(Thread* thread, Arguments args) {
432 HandleScope scope(thread);
433 Runtime* runtime = thread->runtime();
434 Object self_obj(&scope, args.get(0));
435 if (!runtime->isInstanceOfBufferedReader(*self_obj)) {
436 return thread->raiseRequiresType(self_obj, ID(BufferedReader));
437 }
438 BufferedReader self(&scope, *self_obj);
439
440 Int buffer_size_obj(&scope, intUnderlying(args.get(1)));
441 if (!buffer_size_obj.isSmallInt() && !buffer_size_obj.isBool()) {
442 return thread->raiseWithFmt(LayoutId::kOverflowError,
443 "cannot fit value into an index-sized integer");
444 }
445 word buffer_size = buffer_size_obj.asWord();
446 DCHECK(buffer_size > 0, "invalid buffer size");
447
448 self.setBufferSize(buffer_size);
449 self.setReadPos(0);
450 self.setBufferNumBytes(0);
451 // readBuf() starts out as `None` and is initialized lazily so patterns like
452 // just doing a single `read()` on the whole buffered reader will not even
453 // bother allocating the read buffer. There may however be already a
454 // `_read_buf` allocated previously when `_init` is used to clear the buffer
455 // as part of `seek`.
456 if (!self.readBuf().isNoneType() &&
457 MutableBytes::cast(self.readBuf()).length() != buffer_size) {
458 return thread->raiseWithFmt(LayoutId::kValueError, "length mismatch");
459 }
460 return NoneType::object();
461}
462
463RawObject FUNC(_io, _buffered_reader_peek)(Thread* thread, Arguments args) {
464 // TODO(T58490915): Investigate what thread safety guarantees python has,
465 // and add locking code as necessary.
466 HandleScope scope(thread);
467 Runtime* runtime = thread->runtime();
468 Object self_obj(&scope, args.get(0));
469 if (!runtime->isInstanceOfBufferedReader(*self_obj)) {
470 return thread->raiseRequiresType(self_obj, ID(BufferedReader));
471 }
472 BufferedReader self(&scope, *self_obj);
473
474 Object num_bytes_obj(&scope, args.get(1));
475 // TODO(T59004416) Is there a way to push intFromIndex() towards managed?
476 Object num_bytes_int_obj(&scope, intFromIndex(thread, num_bytes_obj));
477 if (num_bytes_int_obj.isErrorException()) return *num_bytes_int_obj;
478 Int num_bytes_int(&scope, intUnderlying(*num_bytes_int_obj));
479 if (!num_bytes_int.isSmallInt() && !num_bytes_int.isBool()) {
480 return thread->raiseWithFmt(LayoutId::kOverflowError,
481 "cannot fit value into an index-sized integer");
482 }
483 word num_bytes = num_bytes_int.asWord();
484
485 word buffer_num_bytes = self.bufferNumBytes();
486 word read_pos = self.readPos();
487 Object read_buf_obj(&scope, self.readBuf());
488 word available = buffer_num_bytes - read_pos;
489 if (num_bytes <= 0 || num_bytes > available) {
490 // Perform a lightweight "reset" of the read buffer that does not move data
491 // around.
492 if (read_buf_obj.isNoneType()) {
493 read_buf_obj = initReadBuf(thread, self);
494 } else if (available == 0) {
495 buffer_num_bytes = 0;
496 read_pos = 0;
497 self.setReadPos(0);
498 self.setBufferNumBytes(0);
499 }
500 // Attempt a single read to fill the buffer.
501 MutableBytes read_buf(&scope, *read_buf_obj);
502 Object raw_file(&scope, self.underlying());
503 Object fill_result(
504 &scope, fillBuffer(thread, raw_file, read_buf, &buffer_num_bytes));
505 if (fill_result.isErrorException()) return *fill_result;
506 self.setBufferNumBytes(buffer_num_bytes);
507 available = buffer_num_bytes - read_pos;
508 }
509
510 Bytes read_buf(&scope, *read_buf_obj);
511 return bytesSubseq(thread, read_buf, read_pos, available);
512}
513
514RawObject FUNC(_io, _buffered_reader_read)(Thread* thread, Arguments args) {
515 // TODO(T58490915): Investigate what thread safety guarantees python has,
516 // and add locking code as necessary.
517 HandleScope scope(thread);
518 Runtime* runtime = thread->runtime();
519 Object self_obj(&scope, args.get(0));
520 if (!runtime->isInstanceOfBufferedReader(*self_obj)) {
521 return thread->raiseRequiresType(self_obj, ID(BufferedReader));
522 }
523 BufferedReader self(&scope, *self_obj);
524
525 Object num_bytes_obj(&scope, args.get(1));
526 word num_bytes;
527 if (num_bytes_obj.isNoneType()) {
528 num_bytes = kMaxWord;
529 } else {
530 // TODO(T59004416) Is there a way to push intFromIndex() towards managed?
531 Object num_bytes_int_obj(&scope, intFromIndex(thread, num_bytes_obj));
532 if (num_bytes_int_obj.isErrorException()) return *num_bytes_int_obj;
533 Int num_bytes_int(&scope, intUnderlying(*num_bytes_int_obj));
534 if (!num_bytes_int.isSmallInt() && !num_bytes_int.isBool()) {
535 return thread->raiseWithFmt(
536 LayoutId::kOverflowError,
537 "cannot fit value into an index-sized integer");
538 }
539 num_bytes = num_bytes_int.asWord();
540 if (num_bytes == -1) {
541 num_bytes = kMaxWord;
542 } else if (num_bytes < 0) {
543 return thread->raiseWithFmt(LayoutId::kValueError,
544 "read length must be non-negative or -1");
545 }
546 }
547
548 word buffer_num_bytes = self.bufferNumBytes();
549 word read_pos = self.readPos();
550
551 word available = buffer_num_bytes - read_pos;
552 DCHECK(available >= 0, "invalid state");
553 if (num_bytes <= available) {
554 word new_read_pos = read_pos + num_bytes;
555 self.setReadPos(new_read_pos);
556 Bytes read_buf(&scope, self.readBuf());
557 return bytesSubseq(thread, read_buf, read_pos, num_bytes);
558 }
559
560 Object raw_file(&scope, self.underlying());
561 if (num_bytes == kMaxWord) {
562 Object readall_result(&scope, thread->invokeMethod1(raw_file, ID(readall)));
563 if (readall_result.isErrorException()) return *readall_result;
564 if (!readall_result.isErrorNotFound()) {
565 Bytes bytes(&scope, Bytes::empty());
566 word bytes_length;
567 if (readall_result.isNoneType()) {
568 if (available == 0) return NoneType::object();
569 bytes_length = 0;
570 } else if (runtime->isInstanceOfBytes(*readall_result)) {
571 bytes = bytesUnderlying(*readall_result);
572 bytes_length = bytes.length();
573 } else if (runtime->isInstanceOfBytearray(*readall_result)) {
574 Bytearray byte_array(&scope, *readall_result);
575 bytes = byte_array.items();
576 bytes_length = byte_array.numItems();
577 } else if (runtime->isByteslike(*readall_result)) {
578 UNIMPLEMENTED("byteslike");
579 } else {
580 return thread->raiseWithFmt(LayoutId::kTypeError,
581 "readall() should return bytes");
582 }
583 word length = bytes_length + available;
584 if (length == 0) return Bytes::empty();
585 MutableBytes result(&scope,
586 runtime->newMutableBytesUninitialized(length));
587 word idx = 0;
588 if (available > 0) {
589 result.replaceFromWithStartAt(idx, MutableBytes::cast(self.readBuf()),
590 available, read_pos);
591 idx += available;
592 self.setReadPos(0);
593 self.setBufferNumBytes(0);
594 }
595 if (bytes_length > 0) {
596 result.replaceFromWithBytes(idx, *bytes, bytes_length);
597 idx += bytes_length;
598 }
599 DCHECK(idx == length, "length mismatch");
600 return result.becomeImmutable();
601 }
602 }
603
604 // Use alternate reading code for big requests where buffering would not help.
605 // (This is also used for the num_bytes==kMaxWord (aka "readall") case when
606 // the file object does not provide a "readall" method.
607 word buffer_size = self.bufferSize();
608 if (num_bytes > (buffer_size / 2)) {
609 return readBig(thread, self, num_bytes);
610 }
611
612 // Fill buffer until we have enough bytes available.
613 MutableBytes read_buf(&scope, rewindOrInitReadBuf(thread, self));
614 buffer_num_bytes = self.bufferNumBytes();
615 Object fill_result(&scope, NoneType::object());
616 do {
617 fill_result = fillBuffer(thread, raw_file, read_buf, &buffer_num_bytes);
618 if (fill_result.isErrorException()) return *fill_result;
619 if (!fill_result.isUnbound()) {
620 if (buffer_num_bytes == 0) return *fill_result;
621 break;
622 }
623 } while (buffer_num_bytes < num_bytes);
624
625 word length = Utils::minimum(buffer_num_bytes, num_bytes);
626 self.setBufferNumBytes(buffer_num_bytes);
627 self.setReadPos(length);
628 Bytes read_buf_bytes(&scope, *read_buf);
629 return bytesSubseq(thread, read_buf_bytes, 0, length);
630}
631
632RawObject FUNC(_io, _buffered_reader_readline)(Thread* thread, Arguments args) {
633 // TODO(T58490915): Investigate what thread safety guarantees Python has,
634 // and add locking code as necessary.
635 HandleScope scope(thread);
636 Runtime* runtime = thread->runtime();
637 Object self_obj(&scope, args.get(0));
638 if (!runtime->isInstanceOfBufferedReader(*self_obj)) {
639 return thread->raiseRequiresType(self_obj, ID(BufferedReader));
640 }
641 BufferedReader self(&scope, *self_obj);
642
643 Object max_line_bytes_obj(&scope, args.get(1));
644 word max_line_bytes = kMaxWord;
645 if (!max_line_bytes_obj.isNoneType()) {
646 // TODO(T59004416) Is there a way to push intFromIndex() towards managed?
647 Object max_line_bytes_int_obj(&scope,
648 intFromIndex(thread, max_line_bytes_obj));
649 if (max_line_bytes_int_obj.isErrorException()) {
650 return *max_line_bytes_int_obj;
651 }
652 Int max_line_bytes_int(&scope, intUnderlying(*max_line_bytes_int_obj));
653 if (!max_line_bytes_int.isSmallInt() && !max_line_bytes_int.isBool()) {
654 return thread->raiseWithFmt(
655 LayoutId::kOverflowError,
656 "cannot fit value into an index-sized integer");
657 }
658 max_line_bytes = max_line_bytes_int.asWord();
659 if (max_line_bytes == -1) {
660 max_line_bytes = kMaxWord;
661 } else if (max_line_bytes < 0) {
662 return thread->raiseWithFmt(LayoutId::kValueError,
663 "read length must be non-negative or -1");
664 }
665 }
666
667 word buffer_num_bytes = self.bufferNumBytes();
668 word read_pos = self.readPos();
669 word available = buffer_num_bytes - read_pos;
670 if (available > 0) {
671 MutableBytes read_buf(&scope, self.readBuf());
672 word line_end = -1;
673 word scan_length = available;
674 if (available >= max_line_bytes) {
675 scan_length = max_line_bytes;
676 line_end = read_pos + max_line_bytes;
677 } else {
678 max_line_bytes -= available;
679 }
680 word newline_index = read_buf.findByte('\n', read_pos, scan_length);
681 if (newline_index >= 0) {
682 line_end = newline_index + 1;
683 }
684 if (line_end >= 0) {
685 self.setReadPos(line_end);
686 Bytes read_buf_bytes(&scope, *read_buf);
687 return bytesSubseq(thread, read_buf_bytes, read_pos, line_end - read_pos);
688 }
689 }
690
691 MutableBytes read_buf(&scope, rewindOrInitReadBuf(thread, self));
692 buffer_num_bytes = self.bufferNumBytes();
693 word buffer_size = self.bufferSize();
694
695 Object raw_file(&scope, self.underlying());
696 Object fill_result(&scope, NoneType::object());
697 Object chunks(&scope, NoneType::object());
698 word line_end = -1;
699 // Outer loop in case for case where a line is longer than a single buffer. In
700 // that case we will collect the pieces in the `chunks` list.
701 for (;;) {
702 // Fill buffer until we find a newline character or filled up the whole
703 // buffer.
704 do {
705 word old_buffer_num_bytes = buffer_num_bytes;
706 fill_result = fillBuffer(thread, raw_file, read_buf, &buffer_num_bytes);
707 if (fill_result.isErrorException()) return *fill_result;
708 if (!fill_result.isUnbound()) {
709 if (buffer_num_bytes == 0 && chunks.isNoneType()) return *fill_result;
710 line_end = buffer_num_bytes;
711 break;
712 }
713
714 word scan_start = old_buffer_num_bytes;
715 word scan_length = buffer_num_bytes - old_buffer_num_bytes;
716 if (scan_length >= max_line_bytes) {
717 scan_length = max_line_bytes;
718 line_end = scan_start + max_line_bytes;
719 } else {
720 max_line_bytes -= buffer_num_bytes - old_buffer_num_bytes;
721 }
722 word newline_index = read_buf.findByte('\n', scan_start, scan_length);
723 if (newline_index >= 0) {
724 line_end = newline_index + 1;
725 break;
726 }
727 } while (line_end < 0 && buffer_num_bytes < buffer_size);
728
729 if (line_end < 0) {
730 // The line is longer than the buffer: Add the current buffer to the
731 // chunks list, create a fresh one and repeat scan loop.
732 if (chunks.isNoneType()) {
733 chunks = runtime->newList();
734 }
735 List list(&scope, *chunks);
736 runtime->listAdd(thread, list, read_buf);
737
738 // Create a fresh buffer and retry.
739 read_buf = initReadBuf(thread, self);
740 buffer_num_bytes = 0;
741 continue;
742 }
743 break;
744 }
745
746 word length = line_end;
747 if (!chunks.isNoneType()) {
748 List list(&scope, *chunks);
749 for (word i = 0, num_items = list.numItems(); i < num_items; i++) {
750 length += MutableBytes::cast(list.at(i)).length();
751 }
752 }
753 MutableBytes result(&scope, runtime->newMutableBytesUninitialized(length));
754 word idx = 0;
755 if (!chunks.isNoneType()) {
756 List list(&scope, *chunks);
757 Bytes chunk(&scope, Bytes::empty());
758 for (word i = 0, num_items = list.numItems(); i < num_items; i++) {
759 chunk = list.at(i);
760 word chunk_length = chunk.length();
761 result.replaceFromWithBytes(idx, *chunk, chunk_length);
762 idx += chunk_length;
763 }
764 }
765 result.replaceFromWith(idx, *read_buf, line_end);
766 DCHECK(idx + line_end == length, "length mismatch");
767 self.setReadPos(line_end);
768 self.setBufferNumBytes(buffer_num_bytes);
769 return result.becomeImmutable();
770}
771
772RawObject FUNC(_io, _TextIOWrapper_attached_guard)(Thread* thread,
773 Arguments args) {
774 HandleScope scope(thread);
775 Runtime* runtime = thread->runtime();
776 Object self_obj(&scope, args.get(0));
777 if (!runtime->isInstanceOfTextIOWrapper(*self_obj)) {
778 return thread->raiseRequiresType(self_obj, ID(TextIOWrapper));
779 }
780 TextIOWrapper self(&scope, *self_obj);
781 if (self.detached()) {
782 return thread->raiseWithFmt(LayoutId::kValueError,
783 "underlying buffer has been detached");
784 }
785 return NoneType::object();
786}
787
788RawObject FUNC(_io, _TextIOWrapper_attached_closed_guard)(Thread* thread,
789 Arguments args) {
790 HandleScope scope(thread);
791 Object self_obj(&scope, args.get(0));
792 Runtime* runtime = thread->runtime();
793 if (!runtime->isInstanceOfTextIOWrapper(*self_obj)) {
794 return thread->raiseRequiresType(self_obj, ID(TextIOWrapper));
795 }
796 TextIOWrapper self(&scope, *self_obj);
797 if (self.detached()) {
798 return thread->raiseWithFmt(LayoutId::kValueError,
799 "underlying buffer has been detached");
800 }
801 Object buffer_obj(&scope, self.buffer());
802 if (runtime->isInstanceOfBufferedReader(*buffer_obj)) {
803 BufferedReader buffer(&scope, *buffer_obj);
804 if (buffer.closed()) {
805 return thread->raiseWithFmt(LayoutId::kValueError,
806 "I/O operation on closed file.");
807 }
808 return NoneType::object();
809 }
810
811 if (runtime->isInstanceOfBufferedWriter(*buffer_obj)) {
812 BufferedWriter buffer(&scope, *buffer_obj);
813 if (!buffer.closed()) {
814 return NoneType::object();
815 }
816 return thread->raiseWithFmt(LayoutId::kValueError,
817 "I/O operation on closed file.");
818 }
819 // TODO(T61927696): Add closed check support for other types of buffer
820 return Unbound::object();
821}
822
823RawObject FUNC(_io,
824 _TextIOWrapper_attached_closed_seekable_guard)(Thread* thread,
825 Arguments args) {
826 HandleScope scope(thread);
827 Object self_obj(&scope, args.get(0));
828 Runtime* runtime = thread->runtime();
829 if (!runtime->isInstanceOfTextIOWrapper(*self_obj)) {
830 return thread->raiseRequiresType(self_obj, ID(TextIOWrapper));
831 }
832 TextIOWrapper self(&scope, *self_obj);
833 if (self.detached()) {
834 return thread->raiseWithFmt(LayoutId::kValueError,
835 "underlying buffer has been detached");
836 }
837 Object buffer_obj(&scope, self.buffer());
838 if (runtime->isInstanceOfBufferedReader(*buffer_obj)) {
839 BufferedReader buffer(&scope, *buffer_obj);
840 if (buffer.closed()) {
841 return thread->raiseWithFmt(LayoutId::kValueError,
842 "I/O operation on closed file.");
843 }
844 // TODO(T61927696): change this when TextIOWrapper.seekable() returns bool
845 Object seekable_obj(&scope, self.seekable());
846 if (seekable_obj.isNoneType() || seekable_obj == Bool::falseObj()) {
847 return thread->raiseWithFmt(LayoutId::kValueError,
848 "underlying stream is not seekable");
849 }
850 return NoneType::object();
851 }
852
853 if (runtime->isInstanceOfBufferedWriter(*buffer_obj)) {
854 BufferedWriter buffer(&scope, *buffer_obj);
855 if (!buffer.closed()) {
856 // TODO(T61927696): change this when TextIOWrapper.seekable() returns bool
857 Object seekable_obj(&scope, self.seekable());
858 if (seekable_obj.isNoneType() || seekable_obj == Bool::falseObj()) {
859 return thread->raiseWithFmt(LayoutId::kValueError,
860 "underlying stream is not seekable");
861 }
862 return NoneType::object();
863 }
864 return thread->raiseWithFmt(LayoutId::kValueError,
865 "I/O operation on closed file.");
866 }
867
868 // TODO(T61927696): Add closed check support for other types of buffer
869 return Unbound::object();
870}
871
872// Copy the bytes of a UTF-8 encoded string with no surrogates to the write
873// buffer (a Bytearray) of underlying Bufferedwriter of TextIOWrapper
874// If the length of write buffer will be larger than
875// BufferedWriter.bufferSize(), return Unbound to escape to managed code and
876// call BufferedWriter.write()
877// If the newline is "\r\n", return Unbound to use managed code
878// If text_io.lineBuffering() or haslf or "\r" in text, return Unbound to
879// managed code to use flush()
880// TODO(T61927696): Implement native version of BufferedWriter._flush_unlocked()
881// with FileIO as BufferedWriter.raw. With that function, we can do flush in
882// here.
883RawObject FUNC(_io, _TextIOWrapper_write_UTF8)(Thread* thread, Arguments args) {
884 HandleScope scope(thread);
885
886 Object text_obj(&scope, args.get(1));
887 Runtime* runtime = thread->runtime();
888 if (!runtime->isInstanceOfStr(*text_obj)) {
889 return thread->raiseWithFmt(LayoutId::kTypeError,
890 "write() argument must be str, not %T",
891 &text_obj);
892 }
893
894 Object self_obj(&scope, args.get(0));
895 if (!runtime->isInstanceOfTextIOWrapper(*self_obj)) {
896 return thread->raiseRequiresType(self_obj, ID(TextIOWrapper));
897 }
898 TextIOWrapper text_io(&scope, *self_obj);
899 if (text_io.detached()) {
900 return thread->raiseWithFmt(LayoutId::kValueError,
901 "underlying buffer has been detached");
902 }
903
904 Object buffer_obj(&scope, text_io.buffer());
905 if (!buffer_obj.isBufferedWriter()) {
906 return Unbound::object();
907 }
908 BufferedWriter buffer(&scope, text_io.buffer());
909 if (buffer.closed()) {
910 return thread->raiseWithFmt(LayoutId::kTypeError,
911 "I/O operation on closed file.");
912 }
913
914 if (text_io.encoding() != SmallStr::fromCStr("utf-8") &&
915 text_io.encoding() != SmallStr::fromCStr("UTF-8")) {
916 return Unbound::object();
917 }
918 Str writenl(&scope, text_io.writenl());
919
920 // Only allow writenl to be cr or lf in this short cut
921 if (!text_io.writetranslate() || writenl == SmallStr::fromCStr("\r\n")) {
922 return Unbound::object();
923 }
924
925 Str text(&scope, strUnderlying(*text_obj));
926 word text_len = text.length();
927
928 Bytearray write_buffer(&scope, buffer.writeBuf());
929 word old_len = write_buffer.numItems();
930 word new_len = old_len + text_len;
931 runtime->bytearrayEnsureCapacity(thread, write_buffer, new_len);
932 MutableBytes write_buffer_bytes(&scope, write_buffer.items());
933 write_buffer_bytes.replaceFromWithStr(old_len, *text, text_len);
934 write_buffer.setNumItems(new_len);
935
936 int32_t codepoint;
937 word num_bytes;
938 bool hasnl = false;
939
940 if (writenl == SmallStr::fromCStr("\n")) {
941 for (word offset = 0; offset < text_len;) {
942 codepoint = text.codePointAt(offset, &num_bytes);
943 if (Unicode::isSurrogate(codepoint)) {
944 write_buffer.downsize(old_len);
945 return Unbound::object();
946 }
947 if (num_bytes == 1) {
948 if (text.byteAt(offset) == '\n' || text.byteAt(offset) == '\r') {
949 hasnl = true;
950 }
951 }
952 offset += num_bytes;
953 }
954 } else {
955 for (word offset = 0; offset < text_len;) {
956 codepoint = text.codePointAt(offset, &num_bytes);
957 if (Unicode::isSurrogate(codepoint)) {
958 write_buffer.downsize(old_len);
959 return Unbound::object();
960 }
961 if (num_bytes == 1) {
962 if (text.byteAt(offset) == '\n') {
963 hasnl = true;
964 write_buffer_bytes.byteAtPut(offset + old_len, byte{'\r'});
965 offset += num_bytes;
966 continue;
967 }
968 if (text.byteAt(offset) == '\r') {
969 hasnl = true;
970 offset += num_bytes;
971 continue;
972 }
973 }
974 offset += num_bytes;
975 }
976 }
977
978 if (text_io.lineBuffering() && hasnl) {
979 // TODO(T61927696): Implement native support for
980 // BufferedWriter._flush_unlocked to do flush here
981 Object flush_result(&scope, thread->invokeMethod1(buffer, ID(flush)));
982 if (flush_result.isErrorException()) return *flush_result;
983 text_io.setTelling(text_io.seekable());
984 }
985
986 text_io.setDecodedChars(Str::empty());
987 text_io.setSnapshot(NoneType::object());
988 Object decoder_obj(&scope, text_io.decoder());
989 if (!decoder_obj.isNoneType()) {
990 Object reset_result(&scope, thread->invokeMethod1(decoder_obj, ID(reset)));
991 if (reset_result.isErrorException()) return *reset_result;
992 }
993
994 return SmallInt::fromWord(text_len);
995}
996
997static const BuiltinAttribute kUnderIOBaseAttributes[] = {
998 {ID(_closed), RawUnderIOBase::kClosedOffset},
999};
1000
1001static const BuiltinAttribute kIncrementalNewlineDecoderAttributes[] = {
1002 {ID(_errors), RawIncrementalNewlineDecoder::kErrorsOffset},
1003 {ID(_translate), RawIncrementalNewlineDecoder::kTranslateOffset},
1004 {ID(_decoder), RawIncrementalNewlineDecoder::kDecoderOffset},
1005 {ID(_seennl), RawIncrementalNewlineDecoder::kSeennlOffset},
1006 {ID(_pendingcr), RawIncrementalNewlineDecoder::kPendingcrOffset},
1007};
1008
1009static const BuiltinAttribute kUnderBufferedIOMixinAttributes[] = {
1010 {ID(_raw), RawUnderBufferedIOMixin::kUnderlyingOffset},
1011};
1012
1013static const BuiltinAttribute kBufferedRandomAttributes[] = {
1014 {ID(buffer_size), RawBufferedRandom::kBufferSizeOffset},
1015 {ID(_reader), RawBufferedRandom::kReaderOffset},
1016 {ID(_write_buf), RawBufferedRandom::kWriteBufOffset},
1017 {ID(_write_lock), RawBufferedRandom::kWriteLockOffset},
1018};
1019
1020static const BuiltinAttribute kBufferedReaderAttributes[] = {
1021 {ID(_buffer_size), RawBufferedReader::kBufferSizeOffset,
1022 AttributeFlags::kReadOnly},
1023 {ID(_buffered_reader__read_buf), RawBufferedReader::kReadBufOffset,
1024 AttributeFlags::kHidden},
1025 {ID(_read_pos), RawBufferedReader::kReadPosOffset,
1026 AttributeFlags::kReadOnly},
1027 {ID(_buffer_num_bytes), RawBufferedReader::kBufferNumBytesOffset,
1028 AttributeFlags::kReadOnly},
1029};
1030
1031static const BuiltinAttribute kBufferedWriterAttributes[] = {
1032 {ID(buffer_size), RawBufferedWriter::kBufferSizeOffset},
1033 {ID(_write_buf), RawBufferedWriter::kWriteBufOffset},
1034 {ID(_write_lock), RawBufferedWriter::kWriteLockOffset},
1035};
1036
1037static const BuiltinAttribute kBytesIOAttributes[] = {
1038 {ID(_buffer), RawBytesIO::kBufferOffset},
1039 {ID(_BytesIO__num_items), RawBytesIO::kNumItemsOffset,
1040 AttributeFlags::kReadOnly},
1041 {ID(_pos), RawBytesIO::kPosOffset},
1042};
1043
1044static void bytesIOEnsureCapacity(Thread* thread, const BytesIO& bytes_io,
1045 word min_capacity) {
1046 DCHECK_BOUND(min_capacity, SmallInt::kMaxValue);
1047 HandleScope scope(thread);
1048 MutableBytes curr_buffer(&scope, bytes_io.buffer());
1049 word curr_capacity = curr_buffer.length();
1050 if (min_capacity <= curr_capacity) return;
1051 word new_capacity = Runtime::newCapacity(curr_capacity, min_capacity);
1052 MutableBytes new_buffer(
1053 &scope, thread->runtime()->newMutableBytesUninitialized(new_capacity));
1054 new_buffer.replaceFromWith(0, *curr_buffer, curr_capacity);
1055 new_buffer.replaceFromWithByte(curr_capacity, 0,
1056 new_capacity - curr_capacity);
1057 bytes_io.setBuffer(*new_buffer);
1058}
1059
1060RawObject METH(BytesIO, __init__)(Thread* thread, Arguments args) {
1061 HandleScope scope(thread);
1062 Object self(&scope, args.get(0));
1063 Runtime* runtime = thread->runtime();
1064 if (!runtime->isInstanceOfBytesIO(*self)) {
1065 return thread->raiseRequiresType(self, ID(BytesIO));
1066 }
1067 BytesIO bytes_io(&scope, *self);
1068 Object initial_bytes(&scope, args.get(1));
1069 if (initial_bytes.isNoneType() || initial_bytes == Bytes::empty()) {
1070 bytes_io.setBuffer(runtime->emptyMutableBytes());
1071 bytes_io.setNumItems(0);
1072 bytes_io.setPos(0);
1073 bytes_io.setClosed(false);
1074 return NoneType::object();
1075 }
1076
1077 Byteslike byteslike(&scope, thread, *initial_bytes);
1078 if (!byteslike.isValid()) {
1079 return thread->raiseWithFmt(LayoutId::kTypeError,
1080 "a bytes-like object is required, not '%T'",
1081 &initial_bytes);
1082 }
1083 word byteslike_length = byteslike.length();
1084 MutableBytes buffer(&scope,
1085 runtime->newMutableBytesUninitialized(byteslike_length));
1086 buffer.replaceFromWithByteslike(0, byteslike, byteslike_length);
1087 bytes_io.setBuffer(*buffer);
1088 bytes_io.setClosed(false);
1089 bytes_io.setNumItems(byteslike_length);
1090 bytes_io.setPos(0);
1091 return NoneType::object();
1092}
1093
1094RawObject METH(BytesIO, getvalue)(Thread* thread, Arguments args) {
1095 HandleScope scope(thread);
1096 Runtime* runtime = thread->runtime();
1097 Object self(&scope, args.get(0));
1098 if (!runtime->isInstanceOfBytesIO(*self)) {
1099 return thread->raiseRequiresType(self, ID(BytesIO));
1100 }
1101 BytesIO bytes_io(&scope, *self);
1102 if (bytes_io.closed()) {
1103 return thread->raiseWithFmt(LayoutId::kValueError,
1104 "I/O operation on closed file.");
1105 }
1106 Bytes buffer(&scope, bytes_io.buffer());
1107 word num_items = bytes_io.numItems();
1108 return runtime->bytesCopyWithSize(thread, buffer, num_items);
1109}
1110
1111RawObject METH(BytesIO, read)(Thread* thread, Arguments args) {
1112 HandleScope scope(thread);
1113 Runtime* runtime = thread->runtime();
1114 Object self(&scope, args.get(0));
1115 if (!runtime->isInstanceOfBytesIO(*self)) {
1116 return thread->raiseRequiresType(self, ID(BytesIO));
1117 }
1118 BytesIO bytes_io(&scope, *self);
1119 if (bytes_io.closed()) {
1120 return thread->raiseWithFmt(LayoutId::kValueError,
1121 "I/O operation on closed file.");
1122 }
1123
1124 Object size_obj(&scope, args.get(1));
1125 MutableBytes buffer(&scope, bytes_io.buffer());
1126
1127 word size;
1128 word buffer_len = bytes_io.numItems();
1129 word pos = bytes_io.pos();
1130 if (size_obj.isNoneType()) {
1131 size = buffer_len;
1132 } else {
1133 size_obj = intFromIndex(thread, size_obj);
1134 if (size_obj.isError()) return *size_obj;
1135 // Allow SmallInt, Bool, and subclasses of Int containing SmallInt or Bool
1136 Int size_int(&scope, intUnderlying(*size_obj));
1137 if (size_obj.isLargeInt()) {
1138 return thread->raiseWithFmt(LayoutId::kOverflowError,
1139 "cannot fit '%T' into an index-sized integer",
1140 &size_int);
1141 }
1142 if (size_int.asWord() < 0) {
1143 size = buffer_len;
1144 } else {
1145 size = size_int.asWord();
1146 }
1147 }
1148 if (buffer_len <= pos) {
1149 return Bytes::empty();
1150 }
1151 word new_pos = Utils::minimum(buffer_len, pos + size);
1152 bytes_io.setPos(new_pos);
1153 Bytes result(&scope, *buffer);
1154 return bytesSubseq(thread, result, pos, new_pos - pos);
1155}
1156
1157RawObject METH(BytesIO, write)(Thread* thread, Arguments args) {
1158 HandleScope scope(thread);
1159 Runtime* runtime = thread->runtime();
1160 Object self(&scope, args.get(0));
1161 if (!runtime->isInstanceOfBytesIO(*self)) {
1162 return thread->raiseRequiresType(self, ID(BytesIO));
1163 }
1164 BytesIO bytes_io(&scope, *self);
1165 if (bytes_io.closed()) {
1166 return thread->raiseWithFmt(LayoutId::kValueError,
1167 "I/O operation on closed file.");
1168 }
1169
1170 Object value_obj(&scope, args.get(1));
1171 Byteslike value(&scope, thread, *value_obj);
1172 if (!value.isValid()) {
1173 return thread->raiseWithFmt(LayoutId::kTypeError,
1174 "a bytes-like object is required, not '%T'",
1175 &value_obj);
1176 }
1177
1178 word pos = bytes_io.pos();
1179 word value_length = value.length();
1180 word new_pos = pos + value_length;
1181 bytesIOEnsureCapacity(thread, bytes_io, new_pos);
1182
1183 MutableBytes::cast(bytes_io.buffer())
1184 .replaceFromWithByteslike(pos, value, value_length);
1185 if (new_pos > bytes_io.numItems()) {
1186 bytes_io.setNumItems(new_pos);
1187 }
1188 bytes_io.setPos(new_pos);
1189 return SmallInt::fromWord(value_length);
1190}
1191
1192static const BuiltinAttribute kFileIOAttributes[] = {
1193 {ID(_fd), RawFileIO::kFdOffset},
1194 {ID(name), RawFileIO::kNameOffset},
1195 {ID(_created), RawFileIO::kCreatedOffset},
1196 {ID(_readable), RawFileIO::kReadableOffset},
1197 {ID(_writable), RawFileIO::kWritableOffset},
1198 {ID(_appending), RawFileIO::kAppendingOffset},
1199 {ID(_seekable), RawFileIO::kSeekableOffset},
1200 {ID(_closefd), RawFileIO::kCloseFdOffset},
1201};
1202
1203static const word kDefaultBufferSize = 1 * kKiB; // bytes
1204
1205RawObject METH(FileIO, readall)(Thread* thread, Arguments args) {
1206 HandleScope scope(thread);
1207 Runtime* runtime = thread->runtime();
1208 Object self(&scope, args.get(0));
1209 if (!runtime->isInstanceOfFileIO(*self)) {
1210 return thread->raiseRequiresType(self, ID(FileIO));
1211 }
1212 FileIO file_io(&scope, *self);
1213 if (file_io.closed()) {
1214 return thread->raiseWithFmt(LayoutId::kValueError,
1215 "I/O operation on closed file.");
1216 }
1217 Object fd_obj(&scope, file_io.fd());
1218 DCHECK(fd_obj.isSmallInt(), "fd must be small int");
1219 int fd = SmallInt::cast(*fd_obj).value();
1220 // If there is OSError from File::seek or File::size, error will not be
1221 // thrown. This case is handled by the for loop below.
1222 word pos = File::seek(fd, 0, 1);
1223 word end = File::size(fd);
1224 word buffer_size = kDefaultBufferSize;
1225
1226 if (end > 0 && pos >= 0 && end >= pos) {
1227 buffer_size = end - pos + 1;
1228 }
1229 // OSError from getting File::seek or File::size, or end < pos
1230 // read buffer by buffer
1231 Bytearray result_array(&scope, runtime->newBytearray());
1232 word read_size;
1233 word total_len = 0;
1234 for (;;) {
1235 read_size = buffer_size;
1236 runtime->bytearrayEnsureCapacity(thread, result_array,
1237 total_len + buffer_size);
1238 byte* dst = reinterpret_cast<byte*>(
1239 MutableBytes::cast(result_array.items()).address());
1240 word result_len = File::read(fd, dst + total_len, read_size);
1241 if (result_len < 0) {
1242 return thread->raiseOSErrorFromErrno(-result_len);
1243 }
1244 total_len += result_len;
1245 // From the glibc manual: "If read returns at least one character, there
1246 // is no way you can tell whether end-of-file was reached. But if you did
1247 // reach the end, the next read will return zero."
1248 // Therefore, we can't stop when the result_len is less than read_len, as
1249 // we still don't know if there's more input that we're blocked on.
1250 if (result_len == 0) {
1251 if (total_len == 0) {
1252 return Bytes::empty();
1253 }
1254 // TODO(T70612758): Find a way to shorten the MutableBytes object without
1255 // extra allocation
1256 Bytes result_bytes(
1257 &scope, MutableBytes::cast(result_array.items()).becomeImmutable());
1258 MutableBytes result(&scope,
1259 runtime->newMutableBytesUninitialized(total_len));
1260 dst = reinterpret_cast<byte*>(result.address());
1261 result_bytes.copyTo(dst, total_len);
1262 return result.becomeImmutable();
1263 }
1264 result_array.setNumItems(total_len);
1265 if (total_len == buffer_size) {
1266 buffer_size *= 2;
1267 }
1268 }
1269}
1270
1271static RawObject readintoBytesAddress(Thread* thread, const int fd, byte* dst,
1272 const word dst_len) {
1273 if (dst_len == 0) {
1274 return SmallInt::fromWord(0);
1275 }
1276 word result = File::read(fd, dst, dst_len);
1277 if (result < 0) return thread->raiseOSErrorFromErrno(-result);
1278 return SmallInt::fromWord(result);
1279}
1280
1281RawObject METH(FileIO, readinto)(Thread* thread, Arguments args) {
1282 HandleScope scope(thread);
1283 Object self(&scope, args.get(0));
1284 Runtime* runtime = thread->runtime();
1285 if (!runtime->isInstanceOfFileIO(*self)) {
1286 return thread->raiseRequiresType(self, ID(FileIO));
1287 }
1288 FileIO file_io(&scope, *self);
1289 if (file_io.closed()) {
1290 return thread->raiseWithFmt(LayoutId::kValueError,
1291 "I/O operation on closed file.");
1292 }
1293 Object dst_obj(&scope, args.get(1));
1294 if (!runtime->isByteslike(*dst_obj) && !runtime->isInstanceOfMmap(*dst_obj)) {
1295 return thread->raiseWithFmt(LayoutId::kTypeError,
1296 "Expected bytes-like object, not %T", &dst_obj);
1297 }
1298
1299 int fd = SmallInt::cast(file_io.fd()).value();
1300 if (runtime->isInstanceOfBytearray(*dst_obj)) {
1301 Bytearray dst_array(&scope, *dst_obj);
1302 return readintoBytesAddress(
1303 thread, fd,
1304 reinterpret_cast<byte*>(
1305 MutableBytes::cast(dst_array.items()).address()),
1306 dst_array.numItems());
1307 }
1308 if (dst_obj.isArray()) {
1309 Array array(&scope, *dst_obj);
1310 return readintoBytesAddress(
1311 thread, fd,
1312 reinterpret_cast<byte*>(MutableBytes::cast(array.buffer()).address()),
1313 array.length());
1314 }
1315 if (dst_obj.isMemoryView()) {
1316 MemoryView dst_memoryview(&scope, *dst_obj);
1317 dst_obj = dst_memoryview.buffer();
1318 if (runtime->isInstanceOfBytes(*dst_obj)) {
1319 return thread->raiseWithFmt(
1320 LayoutId::kTypeError, "Expected read-write bytes-like object, not %T",
1321 &dst_memoryview);
1322 }
1323 Pointer dst_ptr(&scope, *dst_obj);
1324 return readintoBytesAddress(
1325 thread, fd, reinterpret_cast<byte*>(dst_ptr.cptr()), dst_ptr.length());
1326 }
1327 if (dst_obj.isMmap()) {
1328 Mmap dst_mmap(&scope, *dst_obj);
1329 if (!dst_mmap.isWritable()) {
1330 return thread->raiseWithFmt(
1331 LayoutId::kTypeError, "Expected read-write bytes-like object, not %T",
1332 &dst_mmap);
1333 }
1334 Pointer dst_ptr(&scope, dst_mmap.data());
1335 return readintoBytesAddress(
1336 thread, fd, reinterpret_cast<byte*>(dst_ptr.cptr()), dst_ptr.length());
1337 }
1338 // Bytes, not valid arguments for readinto
1339 return thread->raiseWithFmt(LayoutId::kTypeError,
1340 "Expected read-write bytes-like object, not %T",
1341 &dst_obj);
1342}
1343
1344static const BuiltinAttribute kStringIOAttributes[] = {
1345 {ID(_buffer), RawStringIO::kBufferOffset},
1346 {ID(_pos), RawStringIO::kPosOffset},
1347 {ID(_readnl), RawStringIO::kReadnlOffset},
1348 {ID(_readtranslate), RawStringIO::kReadtranslateOffset},
1349 {ID(_readuniversal), RawStringIO::kReaduniversalOffset},
1350 {ID(_seennl), RawStringIO::kSeennlOffset},
1351 {ID(_writenl), RawStringIO::kWritenlOffset},
1352 {ID(_writetranslate), RawStringIO::kWritetranslateOffset},
1353};
1354
1355enum NewlineFound { kLF = 0x1, kCR = 0x2, kCRLF = 0x4 };
1356
1357static RawObject stringIOWrite(Thread* thread, const StringIO& string_io,
1358 const Str& value) {
1359 HandleScope scope(thread);
1360 Runtime* runtime = thread->runtime();
1361 if (*value == Str::empty()) {
1362 return SmallInt::fromWord(0);
1363 }
1364
1365 Str writenl(&scope, string_io.writenl());
1366 bool long_writenl = writenl.length() == 2;
1367 byte first_writenl_char = writenl.byteAt(0);
1368 bool has_write_translate =
1369 string_io.hasWritetranslate() && first_writenl_char != '\n';
1370 word original_val_len = value.length();
1371 word val_len = original_val_len;
1372
1373 // If write_translate is true, read_translate is false
1374 // Contrapositively, if read_translate is true, write_translate is false
1375 // Therefore we don't have to worry about their interactions with each other
1376 if (has_write_translate && long_writenl) {
1377 val_len += value.occurrencesOf(SmallStr::fromCStr("\n"));
1378 }
1379
1380 word start = string_io.pos();
1381 word new_len = start + val_len;
1382 bool has_read_translate = string_io.hasReadtranslate();
1383 if (has_read_translate) {
1384 new_len -= value.occurrencesOf(SmallStr::fromCStr("\r\n"));
1385 }
1386
1387 MutableBytes buffer(&scope, string_io.buffer());
1388 word old_len = buffer.length();
1389 if (old_len < new_len) {
1390 MutableBytes new_buffer(&scope,
1391 runtime->newMutableBytesUninitialized(new_len));
1392 new_buffer.replaceFromWith(0, *buffer, old_len);
1393 new_buffer.replaceFromWithByte(old_len, 0, new_len - old_len);
1394 string_io.setBuffer(*new_buffer);
1395 buffer = *new_buffer;
1396 }
1397
1398 if (has_read_translate) {
1399 word new_seen_nl = Int::cast(string_io.seennl()).asWord();
1400 for (word str_i = 0, byte_i = start; str_i < val_len; ++str_i, ++byte_i) {
1401 byte ch = value.byteAt(str_i);
1402 if (ch == '\r') {
1403 if (val_len > str_i + 1 && value.byteAt(str_i + 1) == '\n') {
1404 new_seen_nl |= NewlineFound::kCRLF;
1405 buffer.byteAtPut(byte_i, '\n');
1406 str_i++;
1407 continue;
1408 }
1409 new_seen_nl |= NewlineFound::kCR;
1410 buffer.byteAtPut(byte_i, '\n');
1411 continue;
1412 }
1413 if (ch == '\n') {
1414 new_seen_nl |= NewlineFound::kLF;
1415 }
1416 buffer.byteAtPut(byte_i, ch);
1417 }
1418 string_io.setSeennl(SmallInt::fromWord(new_seen_nl));
1419 } else if (has_write_translate) {
1420 for (word str_i = 0, byte_i = start; str_i < original_val_len;
1421 ++str_i, ++byte_i) {
1422 byte ch = value.byteAt(str_i);
1423 if (ch == '\n') {
1424 buffer.byteAtPut(byte_i, first_writenl_char);
1425 if (long_writenl) {
1426 buffer.byteAtPut(++byte_i, writenl.byteAt(1));
1427 }
1428 continue;
1429 }
1430 buffer.byteAtPut(byte_i, ch);
1431 }
1432 } else {
1433 buffer.replaceFromWithStr(start, *value, val_len);
1434 }
1435 string_io.setPos(new_len);
1436 return SmallInt::fromWord(original_val_len);
1437}
1438
1439static bool isValidStringIONewline(const Object& newline) {
1440 if (newline == SmallStr::empty()) return true;
1441 if (newline == SmallStr::fromCodePoint('\n')) return true;
1442 if (newline == SmallStr::fromCodePoint('\r')) return true;
1443 return newline == SmallStr::fromCStr("\r\n");
1444}
1445
1446RawObject METH(StringIO, __init__)(Thread* thread, Arguments args) {
1447 HandleScope scope(thread);
1448 Runtime* runtime = thread->runtime();
1449 Object self(&scope, args.get(0));
1450 if (!runtime->isInstanceOfStringIO(*self)) {
1451 return thread->raiseRequiresType(self, ID(StringIO));
1452 }
1453 Object newline(&scope, args.get(2));
1454 if (newline != NoneType::object()) {
1455 if (!runtime->isInstanceOfStr(*newline)) {
1456 return thread->raiseWithFmt(LayoutId::kTypeError,
1457 "newline must be str or None, not %T",
1458 &newline);
1459 }
1460 newline = strUnderlying(*newline);
1461 if (!isValidStringIONewline(newline)) {
1462 return thread->raiseWithFmt(LayoutId::kValueError,
1463 "illegal newline value: %S", &newline);
1464 }
1465 }
1466 StringIO string_io(&scope, *self);
1467 string_io.setBuffer(runtime->emptyMutableBytes());
1468 string_io.setClosed(false);
1469 string_io.setPos(0);
1470 string_io.setReadnl(*newline);
1471 string_io.setSeennl(SmallInt::fromWord(0));
1472 if (newline == NoneType::object()) {
1473 string_io.setReadtranslate(true);
1474 string_io.setReaduniversal(true);
1475 string_io.setWritetranslate(false);
1476 string_io.setWritenl(SmallStr::fromCodePoint('\n'));
1477 } else if (newline == Str::empty()) {
1478 string_io.setReadtranslate(false);
1479 string_io.setReaduniversal(true);
1480 string_io.setWritetranslate(false);
1481 string_io.setWritenl(SmallStr::fromCodePoint('\n'));
1482 } else {
1483 string_io.setReadtranslate(false);
1484 string_io.setReaduniversal(false);
1485 string_io.setWritetranslate(true);
1486 string_io.setWritenl(*newline);
1487 }
1488
1489 Object initial_value_obj(&scope, args.get(1));
1490 if (initial_value_obj != NoneType::object()) {
1491 if (!runtime->isInstanceOfStr(*initial_value_obj)) {
1492 return thread->raiseWithFmt(LayoutId::kTypeError,
1493 "initial_value must be str or None, not %T",
1494 &initial_value_obj);
1495 }
1496 Str initial_value(&scope, strUnderlying(*initial_value_obj));
1497 stringIOWrite(thread, string_io, initial_value);
1498 string_io.setPos(0);
1499 }
1500 return NoneType::object();
1501}
1502
1503static word stringIOReadline(Thread* thread, const StringIO& string_io,
1504 word size) {
1505 HandleScope scope(thread);
1506 MutableBytes buffer(&scope, string_io.buffer());
1507 word buf_len = buffer.length();
1508 word start = string_io.pos();
1509 if (start >= buf_len) {
1510 return -1;
1511 }
1512 bool has_read_universal = string_io.hasReaduniversal();
1513 bool has_read_translate = string_io.hasReadtranslate();
1514 Object newline_obj(&scope, string_io.readnl());
1515 if (has_read_translate) {
1516 newline_obj = SmallStr::fromCodePoint('\n');
1517 }
1518 Str newline(&scope, *newline_obj);
1519 if (size < 0 || (size + start) > buf_len) {
1520 size = buf_len - start;
1521 }
1522 word i = start;
1523
1524 if (has_read_universal) {
1525 const byte crlf[] = {'\r', '\n'};
1526 i = buffer.indexOfAny(crlf, start);
1527 // when this condition is met, either '\r' or '\n' is found
1528 if (buf_len > i) {
1529 // ch is the '\n' or '\r'
1530 byte ch = buffer.byteAt(i++);
1531 if (ch == '\r') {
1532 if (buf_len > i && buffer.byteAt(i) == '\n') {
1533 i++;
1534 }
1535 }
1536 }
1537 } else {
1538 byte first_nl_byte = newline.byteAt(0);
1539 while (i < start + size) {
1540 word index = buffer.findByte(first_nl_byte, i, (size + start - i));
1541 if (index == -1) {
1542 i += (size + start - i);
1543 break;
1544 }
1545 i = index + 1;
1546 if (buf_len >= (i + newline.length() - 1)) {
1547 bool match = true;
1548 for (int j = 1; j < newline.length(); j++) {
1549 if (buffer.byteAt(i + j - 1) != newline.byteAt(j)) {
1550 match = false;
1551 }
1552 }
1553 if (match) {
1554 i += (newline.length() - 1);
1555 break;
1556 }
1557 }
1558 }
1559 }
1560 string_io.setPos(i);
1561 return i;
1562}
1563
1564RawObject METH(StringIO, __next__)(Thread* thread, Arguments args) {
1565 HandleScope scope(thread);
1566 Object self(&scope, args.get(0));
1567 if (!thread->runtime()->isInstanceOfStringIO(*self)) {
1568 return thread->raiseRequiresType(self, ID(StringIO));
1569 }
1570 StringIO string_io(&scope, *self);
1571 if (string_io.closed()) {
1572 return thread->raiseWithFmt(LayoutId::kValueError,
1573 "I/O operation on closed file.");
1574 }
1575 word start = string_io.pos();
1576 word end = stringIOReadline(thread, string_io, -1);
1577 if (end == -1) {
1578 return thread->raise(LayoutId::kStopIteration, NoneType::object());
1579 }
1580 Bytes result(&scope, string_io.buffer());
1581 result = bytesSubseq(thread, result, start, end - start);
1582 return result.becomeStr();
1583}
1584
1585RawObject METH(StringIO, close)(Thread* thread, Arguments args) {
1586 HandleScope scope(thread);
1587 Object self_obj(&scope, args.get(0));
1588 if (!thread->runtime()->isInstanceOfStringIO(*self_obj)) {
1589 return thread->raiseRequiresType(self_obj, ID(StringIO));
1590 }
1591 StringIO self(&scope, *self_obj);
1592 self.setClosed(true);
1593 return NoneType::object();
1594}
1595
1596RawObject METH(StringIO, getvalue)(Thread* thread, Arguments args) {
1597 HandleScope scope(thread);
1598 Runtime* runtime = thread->runtime();
1599 Object self(&scope, args.get(0));
1600 if (!runtime->isInstanceOfStringIO(*self)) {
1601 return thread->raiseRequiresType(self, ID(StringIO));
1602 }
1603 StringIO string_io(&scope, *self);
1604 if (string_io.closed()) {
1605 return thread->raiseWithFmt(LayoutId::kValueError,
1606 "I/O operation on closed file.");
1607 }
1608 Bytes buffer(&scope, string_io.buffer());
1609 buffer = runtime->bytesCopy(thread, buffer);
1610 return buffer.becomeStr();
1611}
1612
1613RawObject METH(StringIO, read)(Thread* thread, Arguments args) {
1614 HandleScope scope(thread);
1615 Object self(&scope, args.get(0));
1616 if (!thread->runtime()->isInstanceOfStringIO(*self)) {
1617 return thread->raiseRequiresType(self, ID(StringIO));
1618 }
1619 StringIO string_io(&scope, *self);
1620 if (string_io.closed()) {
1621 return thread->raiseWithFmt(LayoutId::kValueError,
1622 "I/O operation on closed file.");
1623 }
1624 Object size_obj(&scope, args.get(1));
1625 word size;
1626 if (size_obj.isNoneType()) {
1627 size = -1;
1628 } else {
1629 size_obj = intFromIndex(thread, size_obj);
1630 if (size_obj.isError()) return *size_obj;
1631 // TODO(T55084422): have a better abstraction for int to word conversion
1632 if (!size_obj.isSmallInt() && !size_obj.isBool()) {
1633 return thread->raiseWithFmt(
1634 LayoutId::kOverflowError,
1635 "cannot fit value into an index-sized integer");
1636 }
1637 size = Int::cast(*size_obj).asWord();
1638 }
1639 Bytes result(&scope, string_io.buffer());
1640 word start = string_io.pos();
1641 word end = result.length();
1642 if (start > end) {
1643 return Str::empty();
1644 }
1645 if (size < 0) {
1646 string_io.setPos(end);
1647 result = bytesSubseq(thread, result, start, end - start);
1648 return result.becomeStr();
1649 }
1650 word new_pos = Utils::minimum(end, start + size);
1651 string_io.setPos(new_pos);
1652 result = bytesSubseq(thread, result, start, new_pos - start);
1653 return result.becomeStr();
1654}
1655
1656RawObject METH(StringIO, readline)(Thread* thread, Arguments args) {
1657 HandleScope scope(thread);
1658 Object self(&scope, args.get(0));
1659 if (!thread->runtime()->isInstanceOfStringIO(*self)) {
1660 return thread->raiseRequiresType(self, ID(StringIO));
1661 }
1662 StringIO string_io(&scope, *self);
1663 if (string_io.closed()) {
1664 return thread->raiseWithFmt(LayoutId::kValueError,
1665 "I/O operation on closed file.");
1666 }
1667 Object size_obj(&scope, args.get(1));
1668 word size;
1669 if (size_obj.isNoneType()) {
1670 size = -1;
1671 } else {
1672 size_obj = intFromIndex(thread, size_obj);
1673 if (size_obj.isError()) return *size_obj;
1674 // TODO(T55084422): have a better abstraction for int to word conversion
1675 if (!size_obj.isSmallInt() && !size_obj.isBool()) {
1676 return thread->raiseWithFmt(
1677 LayoutId::kOverflowError,
1678 "cannot fit value into an index-sized integer");
1679 }
1680 size = Int::cast(*size_obj).asWord();
1681 }
1682 word start = string_io.pos();
1683 word end = stringIOReadline(thread, string_io, size);
1684 if (end == -1) {
1685 return Str::empty();
1686 }
1687 Bytes result(&scope, string_io.buffer());
1688 result = bytesSubseq(thread, result, start, end - start);
1689 return result.becomeStr();
1690}
1691
1692RawObject METH(StringIO, truncate)(Thread* thread, Arguments args) {
1693 HandleScope scope(thread);
1694 Object self(&scope, args.get(0));
1695 Runtime* runtime = thread->runtime();
1696 if (!runtime->isInstanceOfStringIO(*self)) {
1697 return thread->raiseRequiresType(self, ID(StringIO));
1698 }
1699 StringIO string_io(&scope, *self);
1700 if (string_io.closed()) {
1701 return thread->raiseWithFmt(LayoutId::kValueError,
1702 "I/O operation on closed file.");
1703 }
1704 Object size_obj(&scope, args.get(1));
1705 word size;
1706 if (size_obj.isNoneType()) {
1707 size = string_io.pos();
1708 } else {
1709 size_obj = intFromIndex(thread, size_obj);
1710 if (size_obj.isError()) return *size_obj;
1711 // TODO(T55084422): have a better abstraction for int to word conversion
1712 if (!size_obj.isSmallInt() && !size_obj.isBool()) {
1713 return thread->raiseWithFmt(
1714 LayoutId::kOverflowError,
1715 "cannot fit value into an index-sized integer");
1716 }
1717 size = Int::cast(*size_obj).asWord();
1718 if (size < 0) {
1719 return thread->raiseWithFmt(LayoutId::kValueError,
1720 "Negative size value %d", size);
1721 }
1722 }
1723 MutableBytes buffer(&scope, string_io.buffer());
1724 if (size < buffer.length()) {
1725 MutableBytes new_buffer(&scope,
1726 runtime->newMutableBytesUninitialized(size));
1727 new_buffer.replaceFromWith(0, *buffer, size);
1728 string_io.setBuffer(*new_buffer);
1729 }
1730 return SmallInt::fromWord(size);
1731}
1732
1733RawObject METH(StringIO, write)(Thread* thread, Arguments args) {
1734 HandleScope scope(thread);
1735 Runtime* runtime = thread->runtime();
1736 Object self(&scope, args.get(0));
1737 if (!runtime->isInstanceOfStringIO(*self)) {
1738 return thread->raiseRequiresType(self, ID(StringIO));
1739 }
1740 StringIO string_io(&scope, *self);
1741 if (string_io.closed()) {
1742 return thread->raiseWithFmt(LayoutId::kValueError,
1743 "I/O operation on closed file.");
1744 }
1745 Object value(&scope, args.get(1));
1746 if (!runtime->isInstanceOfStr(*value)) {
1747 return thread->raiseRequiresType(value, ID(str));
1748 }
1749 Str str(&scope, strUnderlying(*value));
1750 return stringIOWrite(thread, string_io, str);
1751}
1752
1753static const BuiltinAttribute kTextIOWrapperAttributes[] = {
1754 {ID(_buffer), RawTextIOWrapper::kBufferOffset},
1755 {ID(_line_buffering), RawTextIOWrapper::kLineBufferingOffset},
1756 {ID(_encoding), RawTextIOWrapper::kEncodingOffset},
1757 {ID(_errors), RawTextIOWrapper::kErrorsOffset},
1758 {ID(_readuniversal), RawTextIOWrapper::kReaduniversalOffset},
1759 {ID(_readtranslate), RawTextIOWrapper::kReadtranslateOffset},
1760 {ID(_readnl), RawTextIOWrapper::kReadnlOffset},
1761 {ID(_writetranslate), RawTextIOWrapper::kWritetranslateOffset},
1762 {ID(_writenl), RawTextIOWrapper::kWritenlOffset},
1763 {ID(_encoder), RawTextIOWrapper::kEncoderOffset},
1764 {ID(_decoder), RawTextIOWrapper::kDecoderOffset},
1765 {ID(_decoded_chars), RawTextIOWrapper::kDecodedCharsOffset},
1766 {ID(_decoded_chars_used), RawTextIOWrapper::kDecodedCharsUsedOffset},
1767 {ID(_snapshot), RawTextIOWrapper::kSnapshotOffset},
1768 {ID(_seekable), RawTextIOWrapper::kSeekableOffset},
1769 {ID(_has_read1), RawTextIOWrapper::kHasRead1Offset},
1770 {ID(_b2cratio), RawTextIOWrapper::kB2cratioOffset},
1771 {ID(_telling), RawTextIOWrapper::kTellingOffset},
1772 {ID(mode), RawTextIOWrapper::kModeOffset}, // TODO(T54575279): remove
1773};
1774
1775void initializeUnderIOTypes(Thread* thread) {
1776 HandleScope scope(thread);
1777 Type type(&scope, addBuiltinType(thread, ID(_IOBase), LayoutId::kUnderIOBase,
1778 /*superclass_id=*/LayoutId::kObject,
1779 kUnderIOBaseAttributes, UnderIOBase::kSize,
1780 /*basetype=*/true));
1781 builtinTypeEnableTupleOverflow(thread, type);
1782
1783 addBuiltinType(thread, ID(IncrementalNewlineDecoder),
1784 LayoutId::kIncrementalNewlineDecoder,
1785 /*superclass_id=*/LayoutId::kObject,
1786 kIncrementalNewlineDecoderAttributes,
1787 IncrementalNewlineDecoder::kSize, /*basetype=*/true);
1788
1789 addBuiltinType(thread, ID(_RawIOBase), LayoutId::kUnderRawIOBase,
1790 /*superclass_id=*/LayoutId::kUnderIOBase, kNoAttributes,
1791 UnderRawIOBase::kSize, /*basetype=*/true);
1792
1793 addBuiltinType(thread, ID(_BufferedIOBase), LayoutId::kUnderBufferedIOBase,
1794 /*superclass_id=*/LayoutId::kUnderIOBase, kNoAttributes,
1795 UnderBufferedIOBase::kSize, /*basetype=*/true);
1796
1797 type = addBuiltinType(thread, ID(BytesIO), LayoutId::kBytesIO,
1798 /*superclass_id=*/LayoutId::kUnderBufferedIOBase,
1799 kBytesIOAttributes, BytesIO::kSize, /*basetype=*/true);
1800 builtinTypeEnableTupleOverflow(thread, type);
1801
1802 addBuiltinType(thread, ID(_BufferedIOMixin), LayoutId::kUnderBufferedIOMixin,
1803 /*superclass_id=*/LayoutId::kUnderBufferedIOBase,
1804 kUnderBufferedIOMixinAttributes, UnderBufferedIOMixin::kSize,
1805 /*basetype=*/true);
1806
1807 type = addBuiltinType(thread, ID(BufferedRandom), LayoutId::kBufferedRandom,
1808 /*superclass_id=*/LayoutId::kUnderBufferedIOMixin,
1809 kBufferedRandomAttributes, BufferedRandom::kSize,
1810 /*basetype=*/true);
1811 builtinTypeEnableTupleOverflow(thread, type);
1812
1813 type = addBuiltinType(thread, ID(BufferedReader), LayoutId::kBufferedReader,
1814 /*superclass_id=*/LayoutId::kUnderBufferedIOMixin,
1815 kBufferedReaderAttributes, BufferedReader::kSize,
1816 /*basetype=*/true);
1817 builtinTypeEnableTupleOverflow(thread, type);
1818
1819 type = addBuiltinType(thread, ID(BufferedWriter), LayoutId::kBufferedWriter,
1820 /*superclass_id=*/LayoutId::kUnderBufferedIOMixin,
1821 kBufferedWriterAttributes, BufferedWriter::kSize,
1822 /*basetype=*/true);
1823 builtinTypeEnableTupleOverflow(thread, type);
1824
1825 type = addBuiltinType(thread, ID(FileIO), LayoutId::kFileIO,
1826 /*superclass_id=*/LayoutId::kUnderRawIOBase,
1827 kFileIOAttributes, FileIO::kSize, /*basetype=*/true);
1828 builtinTypeEnableTupleOverflow(thread, type);
1829
1830 addBuiltinType(thread, ID(_TextIOBase), LayoutId::kUnderTextIOBase,
1831 /*superclass_id=*/LayoutId::kUnderIOBase, kNoAttributes,
1832 RawUnderTextIOBase::kSize, /*basetype=*/true);
1833
1834 type = addBuiltinType(thread, ID(TextIOWrapper), LayoutId::kTextIOWrapper,
1835 /*superclass_id=*/LayoutId::kUnderTextIOBase,
1836 kTextIOWrapperAttributes, TextIOWrapper::kSize,
1837 /*basetype=*/true);
1838 builtinTypeEnableTupleOverflow(thread, type);
1839
1840 type =
1841 addBuiltinType(thread, ID(StringIO), LayoutId::kStringIO,
1842 /*superclass_id=*/LayoutId::kUnderTextIOBase,
1843 kStringIOAttributes, StringIO::kSize, /*basetype=*/true);
1844 builtinTypeEnableTupleOverflow(thread, type);
1845}
1846
1847} // namespace py