Skip to content

Commit 1ceafe5

Browse files
author
Siva Chandra Reddy
committed
[libc] Add implementation of ungetc.
A bug in the file read logic has also been fixed along the way. Parts of the ungetc tests will fail without that bug fixed. Reviewed By: michaelrj Differential Revision: https://reviews.llvm.org/D137286
1 parent f970b00 commit 1ceafe5

File tree

9 files changed

+184
-1
lines changed

9 files changed

+184
-1
lines changed

libc/config/linux/x86_64/entrypoints.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ if(LLVM_LIBC_FULL_BUILD)
396396
libc.src.stdio.stderr
397397
libc.src.stdio.stdin
398398
libc.src.stdio.stdout
399+
libc.src.stdio.ungetc
399400

400401
# stdlib.h entrypoints
401402
libc.src.stdlib._Exit

libc/spec/stdc.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,11 @@ def StdC : StandardSpec<"stdc"> {
641641
ArgSpec<ConstCharRestrictedPtr>,
642642
ArgSpec<VarArgType>]
643643
>,
644+
FunctionSpec<
645+
"ungetc",
646+
RetValSpec<IntType>,
647+
[ArgSpec<IntType>, ArgSpec<FILEPtr>]
648+
>,
644649
],
645650
[
646651
ObjectSpec<

libc/src/__support/File/file.cpp

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,14 @@ size_t File::read_unlocked(void *data, size_t len) {
203203
for (size_t i = 0; i < available_data; ++i)
204204
dataref[i] = bufref[i + pos];
205205
read_limit = pos = 0; // Reset the pointers.
206+
// Update the dataref to reflect that fact that we have already
207+
// copied |available_data| into |data|.
208+
dataref = cpp::span<uint8_t>(dataref.data() + available_data,
209+
dataref.size() - available_data);
206210

207211
size_t to_fetch = len - available_data;
208212
if (to_fetch > bufsize) {
209-
size_t fetched_size = platform_read(this, data, to_fetch);
213+
size_t fetched_size = platform_read(this, dataref.data(), to_fetch);
210214
if (fetched_size < to_fetch) {
211215
if (errno == 0)
212216
eof = true;
@@ -233,6 +237,44 @@ size_t File::read_unlocked(void *data, size_t len) {
233237
return transfer_size + available_data;
234238
}
235239

240+
int File::ungetc_unlocked(int c) {
241+
// There is no meaning to unget if:
242+
// 1. You are trying to push back EOF.
243+
// 2. Read operations are not allowed on this file.
244+
// 3. The previous operation was a write operation.
245+
if (c == EOF || !read_allowed() || (prev_op == FileOp::WRITE))
246+
return EOF;
247+
248+
cpp::span<uint8_t> bufref(static_cast<uint8_t *>(buf), bufsize);
249+
if (read_limit == 0) {
250+
// If |read_limit| is zero, it can mean three things:
251+
// a. This file was just created.
252+
// b. The previous operation was a seek operation.
253+
// c. The previous operation was a read operation which emptied
254+
// the buffer.
255+
// For all the above cases, we simply write |c| at the beginning
256+
// of the buffer and bump |read_limit|. Note that |pos| will also
257+
// be zero in this case, so we don't need to adjust it.
258+
bufref[0] = static_cast<unsigned char>(c);
259+
++read_limit;
260+
} else {
261+
// If |read_limit| is non-zero, it means that there is data in the buffer
262+
// from a previous read operation. Which would also mean that |pos| is not
263+
// zero. So, we decrement |pos| and write |c| in to the buffer at the new
264+
// |pos|. If too many ungetc operations are performed without reads, it
265+
// can lead to (pos == 0 but read_limit != 0). We will just error out in
266+
// such a case.
267+
if (pos == 0)
268+
return EOF;
269+
--pos;
270+
bufref[pos] = static_cast<unsigned char>(c);
271+
}
272+
273+
eof = false; // There is atleast one character that can be read now.
274+
err = false; // This operation was a success.
275+
return c;
276+
}
277+
236278
int File::seek(long offset, int whence) {
237279
FileLock lock(this);
238280
if (prev_op == FileOp::WRITE && pos > 0) {

libc/src/__support/File/file.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,14 @@ class File {
187187

188188
int flush_unlocked();
189189

190+
// Returns EOF on error and keeps the file unchanged.
191+
int ungetc_unlocked(int c);
192+
193+
int ungetc(int c) {
194+
FileLock lock(this);
195+
return ungetc_unlocked(c);
196+
}
197+
190198
// Sets the internal buffer to |buffer| with buffering mode |mode|.
191199
// |size| is the size of |buffer|. This new |buffer| is owned by the
192200
// stream only if |owned| is true.

libc/src/stdio/CMakeLists.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,18 @@ add_entrypoint_object(
282282
libc.src.__support.File.platform_file
283283
)
284284

285+
add_entrypoint_object(
286+
ungetc
287+
SRCS
288+
ungetc.cpp
289+
HDRS
290+
ungetc.h
291+
DEPENDS
292+
libc.include.stdio
293+
libc.src.__support.File.file
294+
libc.src.__support.File.platform_file
295+
)
296+
285297
add_entrypoint_object(
286298
fopencookie
287299
SRCS

libc/src/stdio/ungetc.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===-- Implementation of ungetc ------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/stdio/ungetc.h"
10+
#include "src/__support/File/file.h"
11+
12+
#include <stdio.h>
13+
14+
namespace __llvm_libc {
15+
16+
LLVM_LIBC_FUNCTION(int, ungetc, (int c, ::FILE *stream)) {
17+
return reinterpret_cast<__llvm_libc::File *>(stream)->ungetc(c);
18+
}
19+
20+
} // namespace __llvm_libc

libc/src/stdio/ungetc.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//===-- Implementation header of ungetc -------------------------*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#ifndef LLVM_LIBC_SRC_STDIO_UNGETC_H
10+
#define LLVM_LIBC_SRC_STDIO_UNGETC_H
11+
12+
#include <stdio.h>
13+
14+
namespace __llvm_libc {
15+
16+
int ungetc(int c, ::FILE *stream);
17+
18+
} // namespace __llvm_libc
19+
20+
#endif // LLVM_LIBC_SRC_STDIO_UNGETC_H

libc/test/src/stdio/CMakeLists.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,22 @@ add_libc_unittest(
2121
libc.src.stdio.fwrite
2222
)
2323

24+
add_libc_unittest(
25+
ungetc_test
26+
SUITE
27+
libc_stdio_unittests
28+
SRCS
29+
ungetc_test.cpp
30+
DEPENDS
31+
libc.include.stdio
32+
libc.src.stdio.fclose
33+
libc.src.stdio.fopen
34+
libc.src.stdio.fread
35+
libc.src.stdio.fseek
36+
libc.src.stdio.fwrite
37+
libc.src.stdio.ungetc
38+
)
39+
2440
add_libc_unittest(
2541
unlocked_fileop_test
2642
SUITE

libc/test/src/stdio/ungetc_test.cpp

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//===-- Unittests for ungetc ----------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "src/stdio/fclose.h"
10+
#include "src/stdio/fopen.h"
11+
#include "src/stdio/fread.h"
12+
#include "src/stdio/fseek.h"
13+
#include "src/stdio/fwrite.h"
14+
#include "src/stdio/ungetc.h"
15+
#include "utils/UnitTest/Test.h"
16+
17+
#include <stdio.h>
18+
19+
TEST(LlvmLibcUngetcTest, UngetAndReadBack) {
20+
constexpr char FILENAME[] = "testdata/ungetc_test.test";
21+
::FILE *file = __llvm_libc::fopen(FILENAME, "w");
22+
ASSERT_FALSE(file == nullptr);
23+
constexpr char CONTENT[] = "abcdef";
24+
constexpr size_t CONTENT_SIZE = sizeof(CONTENT);
25+
ASSERT_EQ(CONTENT_SIZE, __llvm_libc::fwrite(CONTENT, 1, CONTENT_SIZE, file));
26+
// Cannot unget to an un-readable file.
27+
ASSERT_EQ(EOF, __llvm_libc::ungetc('1', file));
28+
ASSERT_EQ(0, __llvm_libc::fclose(file));
29+
30+
file = __llvm_libc::fopen(FILENAME, "r+");
31+
ASSERT_FALSE(file == nullptr);
32+
char c;
33+
ASSERT_EQ(__llvm_libc::fread(&c, 1, 1, file), size_t(1));
34+
ASSERT_EQ(c, CONTENT[0]);
35+
ASSERT_EQ(__llvm_libc::ungetc(int(c), file), int(c));
36+
37+
char data[CONTENT_SIZE];
38+
ASSERT_EQ(CONTENT_SIZE, __llvm_libc::fread(data, 1, CONTENT_SIZE, file));
39+
ASSERT_STREQ(CONTENT, data);
40+
41+
ASSERT_EQ(0, __llvm_libc::fseek(file, 0, SEEK_SET));
42+
// ungetc should not fail after a seek operation.
43+
int unget_char = 'z';
44+
ASSERT_EQ(unget_char, __llvm_libc::ungetc(unget_char, file));
45+
// Another unget should fail.
46+
ASSERT_EQ(EOF, __llvm_libc::ungetc(unget_char, file));
47+
// ungetting a char at the beginning of the file will allow us to fetch
48+
// one additional character.
49+
char new_data[CONTENT_SIZE + 1];
50+
ASSERT_EQ(CONTENT_SIZE + 1,
51+
__llvm_libc::fread(new_data, 1, CONTENT_SIZE + 1, file));
52+
ASSERT_STREQ("zabcdef", new_data);
53+
54+
ASSERT_EQ(size_t(1), __llvm_libc::fwrite("x", 1, 1, file));
55+
// unget should fail after a write operation.
56+
ASSERT_EQ(EOF, __llvm_libc::ungetc('1', file));
57+
58+
ASSERT_EQ(0, __llvm_libc::fclose(file));
59+
}

0 commit comments

Comments
 (0)