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