/home/andy/git/oilshell/oil/mycpp/leaky_mylib.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // clang-format off |
2 | | #include "mycpp/myerror.h" |
3 | | // clang-format on |
4 | | |
5 | | #include <errno.h> |
6 | | #include <stdio.h> |
7 | | #include <unistd.h> // isatty |
8 | | |
9 | | #include "mycpp/runtime.h" |
10 | | |
11 | | namespace mylib { |
12 | | |
13 | | // NOTE: split_once() was in gc_mylib, and is likely not leaky |
14 | 5 | Tuple2<Str*, Str*> split_once(Str* s, Str* delim) { |
15 | 5 | StackRoots _roots({&s, &delim}); |
16 | | |
17 | 5 | assert(len(delim) == 1); |
18 | | |
19 | 0 | const char* start = s->data_; // note: this pointer may move |
20 | 5 | char c = delim->data_[0]; |
21 | 5 | int length = len(s); |
22 | | |
23 | 5 | const char* p = static_cast<const char*>(memchr(start, c, length)); |
24 | | |
25 | 5 | if (p) { |
26 | 3 | int len1 = p - start; |
27 | 3 | int len2 = length - len1 - 1; // -1 for delim |
28 | | |
29 | 3 | Str* s1 = nullptr; |
30 | 3 | Str* s2 = nullptr; |
31 | 3 | StackRoots _roots({&s1, &s2}); |
32 | | // Allocate together to avoid 's' moving in between |
33 | 3 | s1 = AllocStr(len1); |
34 | 3 | s2 = AllocStr(len2); |
35 | | |
36 | 3 | memcpy(s1->data_, s->data_, len1); |
37 | 3 | memcpy(s2->data_, s->data_ + len1 + 1, len2); |
38 | | |
39 | 3 | return Tuple2<Str*, Str*>(s1, s2); |
40 | 3 | } else { |
41 | 2 | return Tuple2<Str*, Str*>(s, nullptr); |
42 | 2 | } |
43 | 5 | } |
44 | | |
45 | 326 | Str* BufWriter::getvalue() { |
46 | 326 | if (data_) { |
47 | 326 | Str* ret = ::StrFromC(data_, len_); |
48 | 326 | reset(); // Invalidate this instance |
49 | 326 | return ret; |
50 | 326 | } else { |
51 | | // log('') translates to this |
52 | | // Strings are immutable so we can do this. |
53 | 0 | return kEmptyString; |
54 | 0 | } |
55 | 326 | } |
56 | | |
57 | | LineReader* gStdin; |
58 | | |
59 | 2 | LineReader* open(Str* path) { |
60 | 2 | StackRoots _roots({&path}); |
61 | | |
62 | | // TODO: Don't use C I/O; use POSIX I/O! |
63 | 2 | FILE* f = fopen(path->data_, "r"); |
64 | | |
65 | 2 | if (f == nullptr) { |
66 | 0 | throw Alloc<IOError>(errno); |
67 | 0 | } |
68 | 2 | return Alloc<CFileLineReader>(f); |
69 | 2 | } |
70 | | |
71 | 300 | Str* CFileLineReader::readline() { |
72 | 300 | char* line = nullptr; |
73 | 300 | size_t allocated_size = 0; // unused |
74 | | |
75 | | // Reset errno because we turn the EOF error into empty string (like Python). |
76 | 300 | errno = 0; |
77 | 300 | ssize_t len = getline(&line, &allocated_size, f_); |
78 | 300 | if (len < 0) { |
79 | 1 | if (errno != 0) { // Unexpected error |
80 | 0 | log("getline() error: %s", strerror(errno)); |
81 | 0 | throw Alloc<IOError>(errno); |
82 | 0 | } |
83 | | // Expected EOF |
84 | 1 | return kEmptyString; |
85 | 1 | } |
86 | | |
87 | | // TODO: Fix the leak here. |
88 | | // Note: getline() NUL terminates the buffer |
89 | 299 | return ::StrFromC(line, len); |
90 | 300 | } |
91 | | |
92 | | // Problem: most Str methods like index() and slice() COPY so they have a |
93 | | // NUL terminator. |
94 | | // log("%s") falls back on sprintf, so it expects a NUL terminator. |
95 | | // It would be easier for us to just share. |
96 | 4 | Str* BufLineReader::readline() { |
97 | 4 | auto self = this; |
98 | 4 | Str* line = nullptr; |
99 | 4 | StackRoots _roots({&self, &line}); |
100 | | |
101 | 4 | int buf_len = len(s_); |
102 | 4 | if (pos_ == buf_len) { |
103 | 1 | return kEmptyString; |
104 | 1 | } |
105 | | |
106 | 3 | int orig_pos = pos_; |
107 | 3 | const char* p = strchr(s_->data_ + pos_, '\n'); |
108 | | // log("pos_ = %s", pos_); |
109 | 3 | int line_len; |
110 | 3 | if (p) { |
111 | 2 | int new_pos = p - self->s_->data_; |
112 | 2 | line_len = new_pos - pos_ + 1; // past newline char |
113 | 2 | pos_ = new_pos + 1; |
114 | 2 | } else { // leftover line |
115 | 1 | line_len = buf_len - pos_; |
116 | 1 | pos_ = buf_len; |
117 | 1 | } |
118 | | |
119 | 3 | line = AllocStr(line_len); |
120 | 3 | memcpy(line->data_, self->s_->data_ + orig_pos, line_len); |
121 | 3 | assert(line->data_[line_len] == '\0'); |
122 | 0 | return line; |
123 | 4 | } |
124 | | |
125 | | Writer* gStdout; |
126 | | Writer* gStderr; |
127 | | |
128 | 985 | void BufWriter::write(Str* s) { |
129 | 985 | int orig_len = len_; |
130 | 985 | int n = len(s); |
131 | 985 | len_ += n; |
132 | | |
133 | | // BUG: This is quadratic! |
134 | | |
135 | | // TODO: |
136 | | // |
137 | | // - add capacity_, and double it? start at 32 bytes -> 64 -> 128 |
138 | | // - only realloc by doublings? |
139 | | // - or change this to append to a list? and then getvalue() does a join() |
140 | | // on it? |
141 | | // - DEALLOCATE. gc_mylib doesn't leak! |
142 | | |
143 | | // data_ is nullptr at first |
144 | 985 | data_ = static_cast<char*>(realloc(data_, len_ + 1)); |
145 | | |
146 | | // Append to the end |
147 | 985 | memcpy(data_ + orig_len, s->data_, n); |
148 | 985 | data_[len_] = '\0'; |
149 | 985 | } |
150 | | |
151 | 434 | void BufWriter::write_const(const char* s, int len) { |
152 | 434 | int orig_len = len_; |
153 | 434 | len_ += len; |
154 | | // data_ is nullptr at first |
155 | 434 | data_ = static_cast<char*>(realloc(data_, len_ + 1)); |
156 | | |
157 | | // Append to the end |
158 | 434 | memcpy(data_ + orig_len, s, len); |
159 | 434 | data_[len_] = '\0'; |
160 | 434 | } |
161 | | |
162 | 250 | void BufWriter::format_s(Str* s) { |
163 | 250 | this->write(s); |
164 | 250 | } |
165 | | |
166 | 0 | void BufWriter::format_o(int i) { |
167 | 0 | NotImplemented(); |
168 | 0 | } |
169 | | |
170 | 91 | void BufWriter::format_d(int i) { |
171 | | // extend to the maximum size |
172 | 91 | data_ = static_cast<char*>(realloc(data_, len_ + kIntBufSize)); |
173 | 91 | int len = snprintf(data_ + len_, kIntBufSize, "%d", i); |
174 | | // but record only the number of bytes written |
175 | 91 | len_ += len; |
176 | 91 | } |
177 | | |
178 | | // repr() calls this too |
179 | | // |
180 | | // TODO: This could be replaced with QSN? The upper bound is greater there |
181 | | // because of \u{}. |
182 | 7 | void BufWriter::format_r(Str* s) { |
183 | | // Worst case: \0 becomes 4 bytes as '\\x00', and then two quote bytes. |
184 | 7 | int n = len(s); |
185 | 7 | int upper_bound = n * 4 + 2; |
186 | | |
187 | | // Extend the buffer |
188 | 7 | data_ = static_cast<char*>(realloc(data_, len_ + upper_bound + 1)); |
189 | | |
190 | 7 | char quote = '\''; |
191 | 7 | if (memchr(s->data_, '\'', n) && !memchr(s->data_, '"', n)) { |
192 | 2 | quote = '"'; |
193 | 2 | } |
194 | 7 | char* p = data_ + len_; // end of valid data |
195 | | |
196 | | // From PyString_Repr() |
197 | 7 | *p++ = quote; |
198 | 61 | for (int i = 0; i < n; ++i) { |
199 | 54 | char c = s->data_[i]; |
200 | 54 | if (c == quote || c == '\\') { |
201 | 0 | *p++ = '\\'; |
202 | 0 | *p++ = c; |
203 | 54 | } else if (c == '\t') { |
204 | 1 | *p++ = '\\'; |
205 | 1 | *p++ = 't'; |
206 | 53 | } else if (c == '\n') { |
207 | 2 | *p++ = '\\'; |
208 | 2 | *p++ = 'n'; |
209 | 51 | } else if (c == '\r') { |
210 | 1 | *p++ = '\\'; |
211 | 1 | *p++ = 'r'; |
212 | 50 | } else if (c < ' ' || c >= 0x7f) { |
213 | 3 | sprintf(p, "\\x%02x", c & 0xff); |
214 | 3 | p += 4; |
215 | 47 | } else { |
216 | 47 | *p++ = c; |
217 | 47 | } |
218 | 54 | } |
219 | 7 | *p++ = quote; |
220 | 7 | *p = '\0'; |
221 | | |
222 | 7 | len_ = p - data_; |
223 | | // Shrink the buffer. This is valid usage and GNU libc says it can actually |
224 | | // release. |
225 | 7 | data_ = static_cast<char*>(realloc(data_, len_ + 1)); |
226 | 7 | } |
227 | | |
228 | 119 | void CFileWriter::write(Str* s) { |
229 | | // note: throwing away the return value |
230 | 119 | fwrite(s->data_, sizeof(char), len(s), f_); |
231 | 119 | } |
232 | | |
233 | 0 | void CFileWriter::flush() { |
234 | 0 | ::fflush(f_); |
235 | 0 | } |
236 | | |
237 | 1 | bool CFileWriter::isatty() { |
238 | 1 | return ::isatty(fileno(f_)); |
239 | 1 | } |
240 | | |
241 | | } // namespace mylib |