• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/ucode/tests/custom/99_bugs/59_format_gc_live_values

  1 Values being stringified must stay alive until the formatter is done with
  2 them. A tostring callback may run the garbage collector while an array or
  3 object is being traversed; the container under stringification is immutable
  4 for the duration of the traversal, so callbacks cannot unlink or modify it.
  5 
  6 -- Testcase --
  7 {%
  8         let values = [
  9                 proto({}, { tostring: () => { gc(); return 'A'; } }),
 10                 { b: 7 }
 11         ];
 12 
 13         print(values, '\n');
 14         print(values[1].b, '\n');
 15 %}
 16 -- End --
 17 
 18 -- Expect stdout --
 19 [ "A", { "b": 7 } ]
 20 7
 21 -- End --
 22 
 23 
 24 The same protection applies to object values.
 25 
 26 -- Testcase --
 27 {%
 28         let values = {
 29                 a: proto({}, { tostring: () => { gc(); return 'A'; } }),
 30                 b: { n: 7 }
 31         };
 32 
 33         print(values, '\n');
 34         print(values.b.n, '\n');
 35 %}
 36 -- End --
 37 
 38 -- Expect stdout --
 39 { "a": "A", "b": { "n": 7 } }
 40 7
 41 -- End --
 42 
 43 
 44 Collection must preserve active cycle detection across nested formatting.
 45 Repeated references outside the active path are formatted independently.
 46 
 47 -- Testcase --
 48 {%
 49         let shared = { n: 7 };
 50         let values;
 51         values = [
 52                 proto({}, { tostring: () => { gc(); return sprintf('%J', values); } }),
 53                 shared,
 54                 shared
 55         ];
 56         push(values, values);
 57 
 58         print(values, '\n');
 59         gc();
 60         print(values, '\n');
 61         print(shared.n, '\n');
 62 %}
 63 -- End --
 64 
 65 -- Expect stdout --
 66 [ "null", { "n": 7 }, { "n": 7 }, null ]
 67 [ "null", { "n": 7 }, { "n": 7 }, null ]
 68 7
 69 -- End --
 70 
 71 
 72 Automatic collection from a converter must retain the remaining values too.
 73 
 74 -- Testcase --
 75 {%
 76         let values = [
 77                 proto({}, { tostring: () => {
 78                         for (let i = 0; i < 100; i++) {
 79                                 let garbage = {};
 80                                 garbage.self = garbage;
 81                         }
 82                         return 'A';
 83                 } }),
 84                 { b: 7 }
 85         ];
 86 
 87         gc('start', 1);
 88         print(values, '\n');
 89         gc('stop');
 90         print(values[1].b, '\n');
 91 %}
 92 -- End --
 93 
 94 -- Expect stdout --
 95 [ "A", { "b": 7 } ]
 96 7
 97 -- End --
 98 
 99 
100 Containers under stringification are immutable. Converters attempting to
101 unlink or modify them raise an exception and leave them intact, mutability
102 is restored once formatting is done.
103 
104 -- Testcase --
105 {%
106         let outer;
107         let inner = [
108                 proto({}, { tostring: () => { outer[0] = null; gc(); return 'A'; } }),
109                 { b: 7 }
110         ];
111         outer = [ inner ];
112 
113         try {
114                 sprintf('%J', outer);
115         }
116         catch (e) {
117                 print(e.type, ': ', e.message, '\n');
118         }
119 
120         assert(outer[0] === inner && length(inner) == 2);
121 
122         let object;
123         object = {
124                 a: proto({}, { tostring: () => { delete object.b; return 'A'; } }),
125                 b: { n: 7 }
126         };
127 
128         try {
129                 sprintf('%J', object);
130         }
131         catch (e) {
132                 print(e.type, ': ', e.message, '\n');
133         }
134 
135         assert(object.b.n == 7);
136         push(outer, 2);
137         object.c = 3;
138         print(length(outer), ' ', join(',', keys(object)), '\n');
139 %}
140 -- End --
141 
142 -- Expect stdout --
143 Type error: array value is immutable
144 Type error: object value is immutable
145 2 a,b,c
146 -- End --
147 
148 
149 Formatting state is released after a converter throws an exception.
150 
151 -- Testcase --
152 {%
153         let values = [
154                 proto({}, { tostring: () => { gc(); die('conversion failed'); } }),
155                 { b: 7 }
156         ];
157 
158         try {
159                 sprintf('%J', values);
160         }
161         catch (e) {
162                 assert(e.message == 'conversion failed');
163                 print(e.message, '\n');
164         }
165 
166         proto(values[0], {});
167         gc();
168         push(values, 8);
169         print(values, '\n');
170         print(values[1].b, '\n');
171 %}
172 -- End --
173 
174 -- Expect stdout --
175 conversion failed
176 [ { }, { "b": 7 }, 8 ]
177 7
178 -- End --
179 
180 
181 Calling exit() from a converter discards the entire VM stack. The formatter
182 must not pop its own slot afterwards and must not invoke further converters.
183 Use sprintf() so the test does not depend on partial output after exit().
184 
185 -- Testcase --
186 {%
187         sprintf('%J', [
188                 proto({}, { tostring: () => exit(3) }),
189                 proto({}, { tostring: () => { print('converter called\n'); return 'B'; } }),
190                 { n: 7 }
191         ]);
192         print('not reached\n');
193 %}
194 -- End --
195 
196 -- Expect exitcode --
197 3
198 -- End --
199 
200 
201 An exiting converter may release a directly formatted temporary value.
202 The formatter must not access it again after the converter returns.
203 
204 -- Testcase --
205 {%
206         print(proto({}, { tostring: () => exit(3) }));
207         print('not reached\n');
208 %}
209 -- End --
210 
211 -- Expect exitcode --
212 3
213 -- End --
214 
215 
216 A converter may modify its own value, only the containers being traversed
217 are immutable.
218 
219 -- Testcase --
220 {%
221         let calls = 0;
222         let value = proto({}, { tostring: function() {
223                 calls++;
224                 this.cache ??= 'cached';
225                 return this.cache;
226         } });
227 
228         print([ value, value ], '\n');
229         print(calls, ' ', value.cache, '\n');
230 %}
231 -- End --
232 
233 -- Expect stdout --
234 [ "cached", "cached" ]
235 2 cached
236 -- End --
237 
238 
239 Values which are constant on their own, such as module namespaces, are
240 formatted normally, may be repeated and remain constant afterwards.
241 
242 -- Testcase --
243 import * as mod from "./files/mod.uc";
244 
245 print([ mod, mod ], "\n");
246 
247 try {
248         mod.b = 2;
249 }
250 catch (e) {
251         print(e.message, "\n");
252 }
253 
254 print(mod, "\n");
255 -- End --
256 
257 -- File mod.uc --
258 export let a = 1;
259 -- End --
260 
261 -- Args --
262 -R
263 -- End --
264 
265 -- Expect stdout --
266 [ { "a": 1 }, { "a": 1 } ]
267 object value is immutable
268 { "a": 1 }
269 -- End --
270 
271 
272 Explicit collection still reclaims unreachable cycles inside a converter.
273 
274 -- Testcase --
275 {%
276         let values = [
277                 proto({}, { tostring: () => {
278                         let garbage = {};
279                         garbage.self = garbage;
280                         garbage = null;
281                         let before = gc('count');
282                         gc();
283                         assert(gc('count') < before);
284                         return 'A';
285                 } }),
286                 { b: 7 }
287         ];
288 
289         print(values, '\n');
290         print(values[1].b, '\n');
291 %}
292 -- End --
293 
294 -- Expect stdout --
295 [ "A", { "b": 7 } ]
296 7
297 -- End --

This page was automatically generated by LXR 0.3.1.  •  OpenWrt