##// END OF EJS Templates
dirs: document Py_SIZE weirdness...
Gregory Szorc -
r30159:fb5504d7 default
parent child Browse files
Show More
@@ -1,313 +1,315
1 1 /*
2 2 dirs.c - dynamic directory diddling for dirstates
3 3
4 4 Copyright 2013 Facebook
5 5
6 6 This software may be used and distributed according to the terms of
7 7 the GNU General Public License, incorporated herein by reference.
8 8 */
9 9
10 10 #define PY_SSIZE_T_CLEAN
11 11 #include <Python.h>
12 12 #include "util.h"
13 13
14 14 #ifdef IS_PY3K
15 15 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[1]
16 16 #else
17 17 #define PYLONG_VALUE(o) PyInt_AS_LONG(o)
18 18 #endif
19 19
20 20 /*
21 21 * This is a multiset of directory names, built from the files that
22 22 * appear in a dirstate or manifest.
23 23 *
24 24 * A few implementation notes:
25 25 *
26 26 * We modify Python integers for refcounting, but those integers are
27 27 * never visible to Python code.
28 28 *
29 29 * We mutate strings in-place, but leave them immutable once they can
30 30 * be seen by Python code.
31 31 */
32 32 typedef struct {
33 33 PyObject_HEAD
34 34 PyObject *dict;
35 35 } dirsObject;
36 36
37 37 static inline Py_ssize_t _finddir(const char *path, Py_ssize_t pos)
38 38 {
39 39 while (pos != -1) {
40 40 if (path[pos] == '/')
41 41 break;
42 42 pos -= 1;
43 43 }
44 44
45 45 return pos;
46 46 }
47 47
48 48 static int _addpath(PyObject *dirs, PyObject *path)
49 49 {
50 50 const char *cpath = PyBytes_AS_STRING(path);
51 51 Py_ssize_t pos = PyBytes_GET_SIZE(path);
52 52 PyObject *key = NULL;
53 53 int ret = -1;
54 54
55 55 /* This loop is super critical for performance. That's why we inline
56 56 * access to Python structs instead of going through a supported API.
57 57 * The implementation, therefore, is heavily dependent on CPython
58 58 * implementation details. We also commit violations of the Python
59 59 * "protocol" such as mutating immutable objects. But since we only
60 60 * mutate objects created in this function or in other well-defined
61 61 * locations, the references are known so these violations should go
62 62 * unnoticed. The code for adjusting the length of a PyBytesObject is
63 63 * essentially a minimal version of _PyBytes_Resize. */
64 64 while ((pos = _finddir(cpath, pos - 1)) != -1) {
65 65 PyObject *val;
66 66
67 67 /* It's likely that every prefix already has an entry
68 68 in our dict. Try to avoid allocating and
69 69 deallocating a string for each prefix we check. */
70 70 if (key != NULL)
71 71 ((PyBytesObject *)key)->ob_shash = -1;
72 72 else {
73 73 /* Force Python to not reuse a small shared string. */
74 74 key = PyBytes_FromStringAndSize(cpath,
75 75 pos < 2 ? 2 : pos);
76 76 if (key == NULL)
77 77 goto bail;
78 78 }
79 /* Py_SIZE(o) refers to the ob_size member of the struct. Yes,
80 * assigning to what looks like a function seems wrong. */
79 81 Py_SIZE(key) = pos;
80 82 ((PyBytesObject *)key)->ob_sval[pos] = '\0';
81 83
82 84 val = PyDict_GetItem(dirs, key);
83 85 if (val != NULL) {
84 86 PYLONG_VALUE(val) += 1;
85 87 break;
86 88 }
87 89
88 90 /* Force Python to not reuse a small shared int. */
89 91 #ifdef IS_PY3K
90 92 val = PyLong_FromLong(0x1eadbeef);
91 93 #else
92 94 val = PyInt_FromLong(0x1eadbeef);
93 95 #endif
94 96
95 97 if (val == NULL)
96 98 goto bail;
97 99
98 100 PYLONG_VALUE(val) = 1;
99 101 ret = PyDict_SetItem(dirs, key, val);
100 102 Py_DECREF(val);
101 103 if (ret == -1)
102 104 goto bail;
103 105 Py_CLEAR(key);
104 106 }
105 107 ret = 0;
106 108
107 109 bail:
108 110 Py_XDECREF(key);
109 111
110 112 return ret;
111 113 }
112 114
113 115 static int _delpath(PyObject *dirs, PyObject *path)
114 116 {
115 117 char *cpath = PyBytes_AS_STRING(path);
116 118 Py_ssize_t pos = PyBytes_GET_SIZE(path);
117 119 PyObject *key = NULL;
118 120 int ret = -1;
119 121
120 122 while ((pos = _finddir(cpath, pos - 1)) != -1) {
121 123 PyObject *val;
122 124
123 125 key = PyBytes_FromStringAndSize(cpath, pos);
124 126
125 127 if (key == NULL)
126 128 goto bail;
127 129
128 130 val = PyDict_GetItem(dirs, key);
129 131 if (val == NULL) {
130 132 PyErr_SetString(PyExc_ValueError,
131 133 "expected a value, found none");
132 134 goto bail;
133 135 }
134 136
135 137 if (--PYLONG_VALUE(val) <= 0) {
136 138 if (PyDict_DelItem(dirs, key) == -1)
137 139 goto bail;
138 140 } else
139 141 break;
140 142 Py_CLEAR(key);
141 143 }
142 144 ret = 0;
143 145
144 146 bail:
145 147 Py_XDECREF(key);
146 148
147 149 return ret;
148 150 }
149 151
150 152 static int dirs_fromdict(PyObject *dirs, PyObject *source, char skipchar)
151 153 {
152 154 PyObject *key, *value;
153 155 Py_ssize_t pos = 0;
154 156
155 157 while (PyDict_Next(source, &pos, &key, &value)) {
156 158 if (!PyBytes_Check(key)) {
157 159 PyErr_SetString(PyExc_TypeError, "expected string key");
158 160 return -1;
159 161 }
160 162 if (skipchar) {
161 163 if (!dirstate_tuple_check(value)) {
162 164 PyErr_SetString(PyExc_TypeError,
163 165 "expected a dirstate tuple");
164 166 return -1;
165 167 }
166 168 if (((dirstateTupleObject *)value)->state == skipchar)
167 169 continue;
168 170 }
169 171
170 172 if (_addpath(dirs, key) == -1)
171 173 return -1;
172 174 }
173 175
174 176 return 0;
175 177 }
176 178
177 179 static int dirs_fromiter(PyObject *dirs, PyObject *source)
178 180 {
179 181 PyObject *iter, *item = NULL;
180 182 int ret;
181 183
182 184 iter = PyObject_GetIter(source);
183 185 if (iter == NULL)
184 186 return -1;
185 187
186 188 while ((item = PyIter_Next(iter)) != NULL) {
187 189 if (!PyBytes_Check(item)) {
188 190 PyErr_SetString(PyExc_TypeError, "expected string");
189 191 break;
190 192 }
191 193
192 194 if (_addpath(dirs, item) == -1)
193 195 break;
194 196 Py_CLEAR(item);
195 197 }
196 198
197 199 ret = PyErr_Occurred() ? -1 : 0;
198 200 Py_DECREF(iter);
199 201 Py_XDECREF(item);
200 202 return ret;
201 203 }
202 204
203 205 /*
204 206 * Calculate a refcounted set of directory names for the files in a
205 207 * dirstate.
206 208 */
207 209 static int dirs_init(dirsObject *self, PyObject *args)
208 210 {
209 211 PyObject *dirs = NULL, *source = NULL;
210 212 char skipchar = 0;
211 213 int ret = -1;
212 214
213 215 self->dict = NULL;
214 216
215 217 if (!PyArg_ParseTuple(args, "|Oc:__init__", &source, &skipchar))
216 218 return -1;
217 219
218 220 dirs = PyDict_New();
219 221
220 222 if (dirs == NULL)
221 223 return -1;
222 224
223 225 if (source == NULL)
224 226 ret = 0;
225 227 else if (PyDict_Check(source))
226 228 ret = dirs_fromdict(dirs, source, skipchar);
227 229 else if (skipchar)
228 230 PyErr_SetString(PyExc_ValueError,
229 231 "skip character is only supported "
230 232 "with a dict source");
231 233 else
232 234 ret = dirs_fromiter(dirs, source);
233 235
234 236 if (ret == -1)
235 237 Py_XDECREF(dirs);
236 238 else
237 239 self->dict = dirs;
238 240
239 241 return ret;
240 242 }
241 243
242 244 PyObject *dirs_addpath(dirsObject *self, PyObject *args)
243 245 {
244 246 PyObject *path;
245 247
246 248 if (!PyArg_ParseTuple(args, "O!:addpath", &PyBytes_Type, &path))
247 249 return NULL;
248 250
249 251 if (_addpath(self->dict, path) == -1)
250 252 return NULL;
251 253
252 254 Py_RETURN_NONE;
253 255 }
254 256
255 257 static PyObject *dirs_delpath(dirsObject *self, PyObject *args)
256 258 {
257 259 PyObject *path;
258 260
259 261 if (!PyArg_ParseTuple(args, "O!:delpath", &PyBytes_Type, &path))
260 262 return NULL;
261 263
262 264 if (_delpath(self->dict, path) == -1)
263 265 return NULL;
264 266
265 267 Py_RETURN_NONE;
266 268 }
267 269
268 270 static int dirs_contains(dirsObject *self, PyObject *value)
269 271 {
270 272 return PyBytes_Check(value) ? PyDict_Contains(self->dict, value) : 0;
271 273 }
272 274
273 275 static void dirs_dealloc(dirsObject *self)
274 276 {
275 277 Py_XDECREF(self->dict);
276 278 PyObject_Del(self);
277 279 }
278 280
279 281 static PyObject *dirs_iter(dirsObject *self)
280 282 {
281 283 return PyObject_GetIter(self->dict);
282 284 }
283 285
284 286 static PySequenceMethods dirs_sequence_methods;
285 287
286 288 static PyMethodDef dirs_methods[] = {
287 289 {"addpath", (PyCFunction)dirs_addpath, METH_VARARGS, "add a path"},
288 290 {"delpath", (PyCFunction)dirs_delpath, METH_VARARGS, "remove a path"},
289 291 {NULL} /* Sentinel */
290 292 };
291 293
292 294 static PyTypeObject dirsType = { PyObject_HEAD_INIT(NULL) };
293 295
294 296 void dirs_module_init(PyObject *mod)
295 297 {
296 298 dirs_sequence_methods.sq_contains = (objobjproc)dirs_contains;
297 299 dirsType.tp_name = "parsers.dirs";
298 300 dirsType.tp_new = PyType_GenericNew;
299 301 dirsType.tp_basicsize = sizeof(dirsObject);
300 302 dirsType.tp_dealloc = (destructor)dirs_dealloc;
301 303 dirsType.tp_as_sequence = &dirs_sequence_methods;
302 304 dirsType.tp_flags = Py_TPFLAGS_DEFAULT;
303 305 dirsType.tp_doc = "dirs";
304 306 dirsType.tp_iter = (getiterfunc)dirs_iter;
305 307 dirsType.tp_methods = dirs_methods;
306 308 dirsType.tp_init = (initproc)dirs_init;
307 309
308 310 if (PyType_Ready(&dirsType) < 0)
309 311 return;
310 312 Py_INCREF(&dirsType);
311 313
312 314 PyModule_AddObject(mod, "dirs", (PyObject *)&dirsType);
313 315 }
General Comments 0
You need to be logged in to leave comments. Login now