Line | Count | Source |
1 | | /* |
2 | | Copyright 2015 Google Inc. All rights reserved. |
3 | | |
4 | | Licensed under the Apache License, Version 2.0 (the "License"); |
5 | | you may not use this file except in compliance with the License. |
6 | | You may obtain a copy of the License at |
7 | | |
8 | | http://www.apache.org/licenses/LICENSE-2.0 |
9 | | |
10 | | Unless required by applicable law or agreed to in writing, software |
11 | | distributed under the License is distributed on an "AS IS" BASIS, |
12 | | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | | See the License for the specific language governing permissions and |
14 | | limitations under the License. |
15 | | */ |
16 | | |
17 | | #include <cassert> |
18 | | #include <cmath> |
19 | | #include <cstdlib> |
20 | | |
21 | | #include <memory> |
22 | | #include <set> |
23 | | #include <string> |
24 | | #include <string_view> |
25 | | |
26 | | #include "ast.h" |
27 | | #include "desugarer.h" |
28 | | #include "json.h" |
29 | | #include <nlohmann/json.hpp> |
30 | | #include "md5.h" |
31 | | #include "parser.h" |
32 | | #ifdef USE_SYSTEM_RAPIDYAML |
33 | | #include <ryml.hpp> |
34 | | #include <ryml_std.hpp> |
35 | | #else |
36 | | #include "rapidyaml-0.10.0.hpp" |
37 | | #endif |
38 | | #include "state.h" |
39 | | #include "static_analysis.h" |
40 | | #include "static_error.h" |
41 | | #include "string_utils.h" |
42 | | #include "vm.h" |
43 | | #include "path_utils.h" |
44 | | #include "unicode.h" |
45 | | |
46 | | namespace jsonnet::internal { |
47 | | |
48 | | /** Macro that dumps an error and aborts the program regardless of |
49 | | * whether NDEBUG is defined. This should be used to mark codepaths |
50 | | * as unreachable. |
51 | | */ |
52 | | #define JSONNET_UNREACHABLE() \ |
53 | 0 | do { \ |
54 | 0 | std::cerr << __FILE__ << ":" << __LINE__ \ |
55 | 0 | << ": INTERNAL ERROR: reached unreachable code path" \ |
56 | 0 | << std::endl; \ |
57 | 0 | abort(); \ |
58 | 0 | } while (0) |
59 | | |
60 | | using json = nlohmann::json; |
61 | | |
62 | | namespace { |
63 | | |
64 | | // Custom exception type thrown by RapidYAML error handling callback. |
65 | | // Should be caught and converted to a runtime error. |
66 | | struct RapidYamlError { |
67 | | // Construct any way that std::string can be constructed. |
68 | | template <typename... Args> |
69 | 0 | explicit RapidYamlError(Args&&... args): msg_(std::forward<Args>(args)...) {} |
70 | | std::string msg_; |
71 | | }; |
72 | | |
73 | | /** Stack frames. |
74 | | * |
75 | | * Of these, FRAME_CALL is the most special, as it is the only frame the stack |
76 | | * trace (for errors) displays. |
77 | | */ |
78 | | enum FrameKind { |
79 | | FRAME_APPLY_TARGET, // e in e(...) |
80 | | FRAME_BINARY_LEFT, // a in a + b |
81 | | FRAME_BINARY_RIGHT, // b in a + b |
82 | | FRAME_BINARY_OP, // a + b, with a and b already calculated |
83 | | FRAME_BUILTIN_FILTER, // When executing std.filter, used to hold intermediate state. |
84 | | FRAME_BUILTIN_FORCE_THUNKS, // When forcing builtin args, holds intermediate state. |
85 | | FRAME_CALL, // Used any time we have switched location in user code. |
86 | | FRAME_ERROR, // e in error e |
87 | | FRAME_IF, // e in if e then a else b |
88 | | FRAME_IN_SUPER_ELEMENT, // e in 'e in super' |
89 | | FRAME_INDEX_TARGET, // e in e[x] |
90 | | FRAME_INDEX_INDEX, // e in x[e] |
91 | | FRAME_INVARIANTS, // Caches the thunks that need to be executed one at a time. |
92 | | FRAME_LOCAL, // Stores thunk bindings as we execute e in local ...; e |
93 | | FRAME_OBJECT, // Stores intermediate state as we execute es in { [e]: ..., [e]: ... } |
94 | | FRAME_OBJECT_COMP_ARRAY, // e in {f:a for x in e] |
95 | | FRAME_OBJECT_COMP_ELEMENT, // Stores intermediate state when building object |
96 | | FRAME_STRING_CONCAT, // Stores intermediate state while co-ercing objects |
97 | | FRAME_SUPER_INDEX, // e in super[e] |
98 | | FRAME_UNARY, // e in -e |
99 | | FRAME_BUILTIN_JOIN_STRINGS, // When executing std.join over strings, used to hold intermediate state. |
100 | | FRAME_BUILTIN_JOIN_ARRAYS, // When executing std.join over arrays, used to hold intermediate state. |
101 | | FRAME_BUILTIN_DECODE_UTF8, // When executing std.decodeUTF8, used to hold intermediate state. |
102 | | }; |
103 | | |
104 | | /** A frame on the stack. |
105 | | * |
106 | | * Every time a subterm is evaluated, we first push a new stack frame to |
107 | | * store the continuation. |
108 | | * |
109 | | * The stack frame is a bit like a tagged union, except not as memory |
110 | | * efficient. The set of member variables that are actually used depends on |
111 | | * the value of the member variable kind. |
112 | | * |
113 | | * If the stack frame is of kind FRAME_CALL, then it counts towards the |
114 | | * maximum number of stack frames allowed. Other stack frames are not |
115 | | * counted. This is because FRAME_CALL exists where there is a branch in |
116 | | * the code, e.g. the forcing of a thunk, evaluation of a field, calling a |
117 | | * function, etc. |
118 | | * |
119 | | * The stack is used to mark objects during garbage |
120 | | * collection, so HeapObjects not referred to from the stack may be |
121 | | * prematurely collected. |
122 | | */ |
123 | | struct Frame { |
124 | | /** Tag (tagged union). */ |
125 | | FrameKind kind; |
126 | | |
127 | | /** The code we were executing before. */ |
128 | | const AST *ast; |
129 | | |
130 | | /** The location of the code we were executing before. |
131 | | * |
132 | | * location == ast->location when ast != nullptr |
133 | | */ |
134 | | LocationRange location; |
135 | | |
136 | | /** Reuse this stack frame for the purpose of tail call optimization. */ |
137 | | bool tailCall; |
138 | | |
139 | | /** Used for a variety of purposes. */ |
140 | | Value val; |
141 | | |
142 | | /** Used for a variety of purposes. */ |
143 | | Value val2; |
144 | | |
145 | | /** Used for a variety of purposes. */ |
146 | | DesugaredObject::Fields::const_iterator fit; |
147 | | |
148 | | /** Used for a variety of purposes. */ |
149 | | std::map<const Identifier *, HeapSimpleObject::Field> objectFields; |
150 | | |
151 | | /** Used for a variety of purposes. */ |
152 | | unsigned elementId; |
153 | | |
154 | | /** Used for a variety of purposes. */ |
155 | | std::map<const Identifier *, HeapThunk *> elements; |
156 | | |
157 | | /** Used for a variety of purposes. */ |
158 | | std::vector<HeapThunk *> thunks; |
159 | | |
160 | | /** Used for accumulating a joined string. */ |
161 | | UString str; |
162 | | bool first; |
163 | | |
164 | | /** Used for accumulating bytes */ |
165 | | std::string bytes; |
166 | | |
167 | | /** The context is used in error messages to attempt to find a reasonable name for the |
168 | | * object, function, or thunk value being executed. If it is a thunk, it is filled |
169 | | * with the value when the frame terminates. |
170 | | */ |
171 | | HeapEntity *context; |
172 | | |
173 | | /** The lexically nearest object we are in, or nullptr. Note |
174 | | * that this is not the same as context, because we could be inside a function, |
175 | | * inside an object and then context would be the function, but self would still point |
176 | | * to the object. |
177 | | */ |
178 | | HeapObject *self; |
179 | | |
180 | | /** The "super" level of self. Sometimes, we look upwards in the |
181 | | * inheritance tree, e.g. via an explicit use of super, or because a given field |
182 | | * has been inherited. When evaluating a field from one of these super objects, |
183 | | * we need to bind self to the concrete object (so self must point |
184 | | * there) but uses of super should be resolved relative to the object whose |
185 | | * field we are evaluating. Thus, we keep a second field for that. This is |
186 | | * usually 0, unless we are evaluating a super object's field. |
187 | | */ |
188 | | unsigned offset; |
189 | | |
190 | | /** A set of variables introduced at this point. */ |
191 | | BindingFrame bindings; |
192 | | |
193 | | Frame(const FrameKind &kind, const AST *ast) |
194 | 83.3M | : kind(kind), |
195 | 83.3M | ast(ast), |
196 | 83.3M | location(ast->location), |
197 | 83.3M | tailCall(false), |
198 | 83.3M | elementId(0), |
199 | 83.3M | first(false), |
200 | 83.3M | context(NULL), |
201 | 83.3M | self(NULL), |
202 | 83.3M | offset(0) |
203 | 83.3M | { |
204 | 83.3M | val.t = Value::NULL_TYPE; |
205 | 83.3M | val2.t = Value::NULL_TYPE; |
206 | 83.3M | } |
207 | | |
208 | | Frame(const FrameKind &kind, const LocationRange &location) |
209 | 80.7M | : kind(kind), |
210 | 80.7M | ast(nullptr), |
211 | 80.7M | location(location), |
212 | 80.7M | tailCall(false), |
213 | 80.7M | elementId(0), |
214 | 80.7M | first(false), |
215 | 80.7M | context(NULL), |
216 | 80.7M | self(NULL), |
217 | 80.7M | offset(0) |
218 | 80.7M | { |
219 | 80.7M | val.t = Value::NULL_TYPE; |
220 | 80.7M | val2.t = Value::NULL_TYPE; |
221 | 80.7M | } |
222 | | |
223 | | /** Mark everything visible from this frame. */ |
224 | | void mark(Heap &heap) const |
225 | 35.8M | { |
226 | 35.8M | heap.markFrom(val); |
227 | 35.8M | heap.markFrom(val2); |
228 | 35.8M | if (context) |
229 | 25.0M | heap.markFrom(context); |
230 | 35.8M | if (self) |
231 | 24.7M | heap.markFrom(self); |
232 | 35.8M | for (const auto &bind : bindings) |
233 | 34.2M | heap.markFrom(bind.second); |
234 | 35.8M | for (const auto &el : elements) |
235 | 0 | heap.markFrom(el.second); |
236 | 35.8M | for (const auto &th : thunks) |
237 | 3.33M | heap.markFrom(th); |
238 | 35.8M | } |
239 | | |
240 | | bool isCall(void) const |
241 | 983M | { |
242 | 983M | return kind == FRAME_CALL; |
243 | 983M | } |
244 | | }; |
245 | | |
246 | | /** The stack holds all the stack frames and manages the stack frame limit. */ |
247 | | class Stack { |
248 | | /** How many call frames are on the stack. */ |
249 | | unsigned calls; |
250 | | |
251 | | /** How many call frames should be allowed before aborting the program. */ |
252 | | unsigned limit; |
253 | | |
254 | | /** The stack frames. */ |
255 | | std::vector<Frame> stack; |
256 | | |
257 | | public: |
258 | 5.89k | Stack(unsigned limit) : calls(0), limit(limit) {} |
259 | | |
260 | 5.89k | ~Stack(void) {} |
261 | | |
262 | | unsigned size(void) |
263 | 239M | { |
264 | 239M | return stack.size(); |
265 | 239M | } |
266 | | |
267 | | /** Search for the closest variable in scope that matches the given name. */ |
268 | | HeapThunk *lookUpVar(const Identifier *id) |
269 | 1.48G | { |
270 | 2.14G | for (int i = stack.size() - 1; i >= 0; --i) { |
271 | 2.14G | const auto &binds = stack[i].bindings; |
272 | 2.14G | auto it = binds.find(id); |
273 | 2.14G | if (it != binds.end()) { |
274 | 1.48G | return it->second; |
275 | 1.48G | } |
276 | 660M | if (stack[i].isCall()) |
277 | 0 | break; |
278 | 660M | } |
279 | 0 | return nullptr; |
280 | 1.48G | } |
281 | | |
282 | | /** Mark everything visible from the stack (any frame). */ |
283 | | void mark(Heap &heap) |
284 | 154k | { |
285 | 35.8M | for (const auto &f : stack) { |
286 | 35.8M | f.mark(heap); |
287 | 35.8M | } |
288 | 154k | } |
289 | | |
290 | | Frame &top(void) |
291 | 845M | { |
292 | 845M | return stack.back(); |
293 | 845M | } |
294 | | |
295 | | const Frame &top(void) const |
296 | 0 | { |
297 | 0 | return stack.back(); |
298 | 0 | } |
299 | | |
300 | | void pop(void) |
301 | 160M | { |
302 | 160M | if (top().isCall()) |
303 | 63.5M | calls--; |
304 | 160M | stack.pop_back(); |
305 | 160M | } |
306 | | |
307 | | /** Attempt to find a name for a given heap entity. This may not be possible, but we try |
308 | | * reasonably hard. We look in the bindings for a variable in the closest scope that |
309 | | * happens to point at the entity in question. Otherwise, the best we can do is use its |
310 | | * type. |
311 | | */ |
312 | | std::string getName(unsigned from_here, const HeapEntity *e) |
313 | 555k | { |
314 | 555k | std::string name; |
315 | 2.25M | for (int i = from_here - 1; i >= 0; --i) { |
316 | 2.24M | const auto &f = stack[i]; |
317 | 2.24M | for (const auto &pair : f.bindings) { |
318 | 915k | HeapThunk *thunk = pair.second; |
319 | 915k | if (!thunk->filled) |
320 | 208k | continue; |
321 | 707k | if (!thunk->content.isHeap()) |
322 | 91.6k | continue; |
323 | 615k | if (e != thunk->content.v.h) |
324 | 544k | continue; |
325 | 71.6k | name = encode_utf8(pair.first->name); |
326 | 71.6k | } |
327 | | // Do not go into the next call frame, keep local reasoning. |
328 | 2.24M | if (f.isCall()) |
329 | 552k | break; |
330 | 2.24M | } |
331 | | |
332 | 555k | if (name == "") |
333 | 484k | name = "anonymous"; |
334 | 555k | if (dynamic_cast<const HeapObject *>(e)) { |
335 | 373k | return "object <" + name + ">"; |
336 | 373k | } else if (auto *thunk = dynamic_cast<const HeapThunk *>(e)) { |
337 | 96.8k | if (thunk->name == nullptr) { |
338 | 12.3k | return ""; // Argument of builtin, or root (since top level functions). |
339 | 84.5k | } else { |
340 | 84.5k | return "thunk <" + encode_utf8(thunk->name->name) + ">"; |
341 | 84.5k | } |
342 | 96.8k | } else { |
343 | 85.5k | const auto *func = static_cast<const HeapClosure *>(e); |
344 | 85.5k | if (func->isBuiltin()) { |
345 | 0 | return "builtin function <" + func->builtinName + ">"; |
346 | 0 | } |
347 | 85.5k | return "function <" + name + ">"; |
348 | 85.5k | } |
349 | 555k | } |
350 | | |
351 | | /** Dump the stack. |
352 | | * |
353 | | * This is useful to help debug the VM in gdb. It is virtual to stop it |
354 | | * being removed by the compiler. |
355 | | */ |
356 | | virtual void dump(void) |
357 | 0 | { |
358 | 0 | for (std::size_t i = 0; i < stack.size(); ++i) { |
359 | 0 | std::cout << "stack[" << i << "] = " << stack[i].location << " (" << stack[i].kind |
360 | 0 | << ")" << std::endl; |
361 | 0 | } |
362 | 0 | std::cout << std::endl; |
363 | 0 | } |
364 | | |
365 | | /** Creates the error object for throwing, and also populates it with the stack trace. |
366 | | */ |
367 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
368 | 5.03k | { |
369 | 5.03k | std::vector<TraceFrame> stack_trace; |
370 | 5.03k | stack_trace.push_back(TraceFrame(loc)); |
371 | 2.27M | for (int i = stack.size() - 1; i >= 0; --i) { |
372 | 2.27M | const auto &f = stack[i]; |
373 | 2.27M | if (f.isCall()) { |
374 | 555k | if (f.context != nullptr) { |
375 | | // Give the last line a name. |
376 | 555k | stack_trace[stack_trace.size() - 1].name = getName(i, f.context); |
377 | 555k | } |
378 | 555k | if (f.location.isSet() || f.location.file.length() > 0) |
379 | 442k | stack_trace.push_back(TraceFrame(f.location)); |
380 | 555k | } |
381 | 2.27M | } |
382 | 5.03k | return RuntimeError(stack_trace, msg); |
383 | 5.03k | } |
384 | | |
385 | | /** New (non-call) frame. */ |
386 | | template <class... Args> |
387 | | void newFrame(Args... args) |
388 | 99.4M | { |
389 | 99.4M | stack.emplace_back(args...); |
390 | 99.4M | } vm.cpp:void jsonnet::internal::(anonymous namespace)::Stack::newFrame<jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::AST const*>(jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::AST const*) Line | Count | Source | 388 | 83.3M | { | 389 | 83.3M | stack.emplace_back(args...); | 390 | 83.3M | } |
vm.cpp:void jsonnet::internal::(anonymous namespace)::Stack::newFrame<jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::LocationRange>(jsonnet::internal::(anonymous namespace)::FrameKind, jsonnet::internal::LocationRange) Line | Count | Source | 388 | 16.1M | { | 389 | 16.1M | stack.emplace_back(args...); | 390 | 16.1M | } |
|
391 | | |
392 | | /** If there is a tailstrict annotated frame followed by some locals, pop them all. */ |
393 | | void tailCallTrimStack(void) |
394 | 64.5M | { |
395 | 65.8M | for (int i = stack.size() - 1; i >= 0; --i) { |
396 | 65.1M | switch (stack[i].kind) { |
397 | 21.3M | case FRAME_CALL: { |
398 | 21.3M | if (!stack[i].tailCall || stack[i].thunks.size() > 0) { |
399 | 20.8M | return; |
400 | 20.8M | } |
401 | | // Remove all stack frames including this one. |
402 | 1.95M | while (stack.size() > unsigned(i)) |
403 | 1.43M | stack.pop_back(); |
404 | 518k | calls--; |
405 | 518k | return; |
406 | 21.3M | } break; |
407 | | |
408 | 1.27M | case FRAME_LOCAL: break; |
409 | | |
410 | 42.4M | default: return; |
411 | 65.1M | } |
412 | 65.1M | } |
413 | 64.5M | } |
414 | | |
415 | | /** New call frame. */ |
416 | | void newCall(const LocationRange &loc, HeapEntity *context, HeapObject *self, unsigned offset, |
417 | | const BindingFrame &up_values) |
418 | 64.5M | { |
419 | 64.5M | tailCallTrimStack(); |
420 | 64.5M | if (calls >= limit) { |
421 | 1.04k | throw makeError(loc, "max stack frames exceeded."); |
422 | 1.04k | } |
423 | 64.5M | stack.emplace_back(FRAME_CALL, loc); |
424 | 64.5M | calls++; |
425 | 64.5M | top().context = context; |
426 | 64.5M | top().self = self; |
427 | 64.5M | top().offset = offset; |
428 | 64.5M | top().bindings = up_values; |
429 | 64.5M | top().tailCall = false; |
430 | | |
431 | 64.5M | #ifndef NDEBUG |
432 | 64.5M | for (const auto &bind : up_values) { |
433 | 62.9M | if (bind.second == nullptr) { |
434 | 0 | std::cerr << "INTERNAL ERROR: No binding for variable " |
435 | 0 | << encode_utf8(bind.first->name) << std::endl; |
436 | 0 | std::abort(); |
437 | 0 | } |
438 | 62.9M | } |
439 | 64.5M | #endif |
440 | 64.5M | } |
441 | | |
442 | | /** Look up the stack to find the self binding. */ |
443 | | void getSelfBinding(HeapObject *&self, unsigned &offset) |
444 | 48.5M | { |
445 | 48.5M | self = nullptr; |
446 | 48.5M | offset = 0; |
447 | 158M | for (int i = stack.size() - 1; i >= 0; --i) { |
448 | 158M | if (stack[i].isCall()) { |
449 | 48.4M | self = stack[i].self; |
450 | 48.4M | offset = stack[i].offset; |
451 | 48.4M | return; |
452 | 48.4M | } |
453 | 158M | } |
454 | 48.5M | } |
455 | | |
456 | | /** Look up the stack to see if we're running assertions for this object. */ |
457 | | bool alreadyExecutingInvariants(HeapObject *self) |
458 | 16.1M | { |
459 | 4.56G | for (int i = stack.size() - 1; i >= 0; --i) { |
460 | 4.54G | if (stack[i].kind == FRAME_INVARIANTS) { |
461 | 13.2M | if (stack[i].self == self) |
462 | 45.0k | return true; |
463 | 13.2M | } |
464 | 4.54G | } |
465 | 16.1M | return false; |
466 | 16.1M | } |
467 | | }; |
468 | | |
469 | | /** Typedef to save some typing. */ |
470 | | typedef std::map<std::string, VmExt> ExtMap; |
471 | | |
472 | | /** Typedef to save some typing. */ |
473 | | typedef std::map<std::string, std::string> StrMap; |
474 | | |
475 | | class Interpreter; |
476 | | |
477 | | typedef const AST *(Interpreter::*BuiltinFunc)(const LocationRange &loc, |
478 | | const std::vector<Value> &args); |
479 | | |
480 | | /** Holds the intermediate state during execution and implements the necessary functions to |
481 | | * implement the semantics of the language. |
482 | | * |
483 | | * The garbage collector used is a simple stop-the-world mark and sweep collector. It runs upon |
484 | | * memory allocation if the heap is large enough and has grown enough since the last collection. |
485 | | * All reachable entities have their mark field incremented. Then all entities with the old |
486 | | * mark are removed from the heap. |
487 | | */ |
488 | | class Interpreter { |
489 | | /** The heap. */ |
490 | | Heap heap; |
491 | | |
492 | | /** The value last computed. */ |
493 | | Value scratch; |
494 | | |
495 | | /** The stack. */ |
496 | | Stack stack; |
497 | | |
498 | | /** Used to create ASTs if needed. |
499 | | * |
500 | | * This is used at import time, and in a few other cases. |
501 | | */ |
502 | | Allocator *alloc; |
503 | | |
504 | | /** Used to "name" thunks created to cache imports. */ |
505 | | const Identifier *idImport; |
506 | | |
507 | | /** Used to "name" thunks created on the inside of an array. */ |
508 | | const Identifier *idArrayElement; |
509 | | |
510 | | /** Used to "name" thunks created to execute invariants. */ |
511 | | const Identifier *idInvariant; |
512 | | |
513 | | /** Placehodler name for internal AST. */ |
514 | | const Identifier *idInternal; |
515 | | |
516 | | /** Used to "name" thunks created to convert JSON to Jsonnet objects. */ |
517 | | const Identifier *idJsonObjVar; |
518 | | |
519 | | const Identifier *idEmpty; |
520 | | |
521 | | /** Used to refer to idJsonObjVar. */ |
522 | | const AST *jsonObjVar; |
523 | | |
524 | | /* Standard Library AST */ |
525 | | const DesugaredObject *stdlibAST; |
526 | | HeapObject *stdObject; |
527 | | |
528 | | struct ImportCacheValue { |
529 | | std::string foundHere; |
530 | | std::string content; |
531 | | |
532 | | /** Thunk to store cached result of execution. |
533 | | * |
534 | | * Null if this file was only ever successfully imported with importstr/importbin. |
535 | | */ |
536 | | HeapThunk *thunk; |
537 | | }; |
538 | | |
539 | | /** Cache for imported Jsonnet files. */ |
540 | | std::map<std::pair<std::string, UString>, ImportCacheValue *> cachedImports; |
541 | | |
542 | | /** External variables for std.extVar. */ |
543 | | ExtMap externalVars; |
544 | | |
545 | | /** The callback used for loading imported files. */ |
546 | | VmNativeCallbackMap nativeCallbacks; |
547 | | |
548 | | /** The callback used for loading imported files. */ |
549 | | JsonnetImportCallback *importCallback; |
550 | | |
551 | | /** User context pointer for the import callback. */ |
552 | | void *importCallbackContext; |
553 | | |
554 | | /** Builtin functions by name. */ |
555 | | typedef std::map<std::string, BuiltinFunc> BuiltinMap; |
556 | | BuiltinMap builtins; |
557 | | |
558 | | /** Source values by name. Source values are values (usually functions) |
559 | | * implemented as Jsonnet source which we use internally in the interpreter. |
560 | | * In a sense they are the opposite of builtins. */ |
561 | | typedef std::map<std::string, HeapThunk *> SourceFuncMap; |
562 | | SourceFuncMap sourceVals; |
563 | | /* Just for memory management. */ |
564 | | std::vector<std::unique_ptr<Identifier>> sourceFuncIds; |
565 | | |
566 | | RuntimeError makeError(const LocationRange &loc, const std::string &msg) |
567 | 3.98k | { |
568 | 3.98k | return stack.makeError(loc, msg); |
569 | 3.98k | } |
570 | | |
571 | | /** Create an object on the heap, maybe collect garbage. |
572 | | * \param T Something under HeapEntity |
573 | | * \returns The new object |
574 | | */ |
575 | | template <class T, class... Args> |
576 | | T *makeHeap(Args &&... args) |
577 | 148M | { |
578 | 148M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); |
579 | 148M | if (heap.checkHeap()) { // Do a GC cycle? |
580 | | // Avoid the object we just made being collected. |
581 | 154k | heap.markFrom(r); |
582 | | |
583 | | // Mark from the stack. |
584 | 154k | stack.mark(heap); |
585 | | |
586 | | // Mark from the scratch register |
587 | 154k | heap.markFrom(scratch); |
588 | | |
589 | | // Mark from cached imports |
590 | 154k | for (const auto &pair : cachedImports) { |
591 | 0 | HeapThunk *thunk = pair.second->thunk; |
592 | 0 | if (thunk != nullptr) |
593 | 0 | heap.markFrom(thunk); |
594 | 0 | } |
595 | | |
596 | 24.8M | for (const auto &sourceVal : sourceVals) { |
597 | 24.8M | heap.markFrom(sourceVal.second); |
598 | 24.8M | } |
599 | | |
600 | | // Delete unreachable objects. |
601 | 154k | heap.sweep(); |
602 | 154k | } |
603 | 148M | return r; |
604 | 148M | } Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject* const&, unsigned int const&, jsonnet::internal::AST const* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject* const&, unsigned int const&, jsonnet::internal::AST const* const&) Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const* const&, decltype(nullptr), int, decltype(nullptr)>(jsonnet::internal::Identifier const* const&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapArray* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapArray, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapThunk*> > const&>(std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapThunk*> > const&) Line | Count | Source | 577 | 3.53M | { | 578 | 3.53M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 3.53M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 5.06k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 5.06k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 5.06k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 5.06k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 815k | for (const auto &sourceVal : sourceVals) { | 597 | 815k | heap.markFrom(sourceVal.second); | 598 | 815k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 5.06k | heap.sweep(); | 602 | 5.06k | } | 603 | 3.53M | return r; | 604 | 3.53M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapString* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapString, std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&>(std::__1::basic_string<char32_t, std::__1::char_traits<char32_t>, std::__1::allocator<char32_t> > const&) Line | Count | Source | 577 | 54.5M | { | 578 | 54.5M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 54.5M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 60.7k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 60.7k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 60.7k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 60.7k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 9.78M | for (const auto &sourceVal : sourceVals) { | 597 | 9.78M | heap.markFrom(sourceVal.second); | 598 | 9.78M | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 60.7k | heap.sweep(); | 602 | 60.7k | } | 603 | 54.5M | return r; | 604 | 54.5M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, decltype(nullptr), int, decltype(nullptr)>(jsonnet::internal::Identifier const*&, decltype(nullptr)&&, int&&, decltype(nullptr)&&) Line | Count | Source | 577 | 822k | { | 578 | 822k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 822k | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 992 | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 992 | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 992 | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 992 | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 159k | for (const auto &sourceVal : sourceVals) { | 597 | 159k | heap.markFrom(sourceVal.second); | 598 | 159k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 992 | heap.sweep(); | 602 | 992 | } | 603 | 822k | return r; | 604 | 822k | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapRestrictedObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapRestrictedObject, jsonnet::internal::(anonymous namespace)::HeapObject*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::ObjectField::Hide, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::ObjectField::Hide> > >&>(jsonnet::internal::(anonymous namespace)::HeapObject*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::ObjectField::Hide, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::ObjectField::Hide> > >&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapClosure* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapClosure, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, decltype(nullptr), int, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> >&, jsonnet::internal::BuiltinFunctionBody const*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&&, decltype(nullptr)&&, int&&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> >&, jsonnet::internal::BuiltinFunctionBody const*&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) Line | Count | Source | 577 | 11.6M | { | 578 | 11.6M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 11.6M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 12.6k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 12.6k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 12.6k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 12.6k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 2.04M | for (const auto &sourceVal : sourceVals) { | 597 | 2.04M | heap.markFrom(sourceVal.second); | 598 | 2.04M | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 12.6k | heap.sweep(); | 602 | 12.6k | } | 603 | 11.6M | return r; | 604 | 11.6M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapComprehensionObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST const*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST const*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, decltype(nullptr), decltype(nullptr), int, jsonnet::internal::AST const*>(decltype(nullptr)&&, decltype(nullptr)&&, int&&, jsonnet::internal::AST const*&&) Line | Count | Source | 577 | 5.89k | { | 578 | 5.89k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 5.89k | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 0 | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 0 | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 0 | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 0 | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | |
| 596 | 0 | for (const auto &sourceVal : sourceVals) { | 597 | 0 | heap.markFrom(sourceVal.second); | 598 | 0 | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 0 | heap.sweep(); | 602 | 0 | } | 603 | 5.89k | return r; | 604 | 5.89k | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier*, jsonnet::internal::(anonymous namespace)::HeapObject*&, int, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier*&&, jsonnet::internal::(anonymous namespace)::HeapObject*&, int&&, jsonnet::internal::AST* const&) Line | Count | Source | 577 | 949k | { | 578 | 949k | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 949k | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 0 | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 0 | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 0 | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 0 | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | |
| 596 | 0 | for (const auto &sourceVal : sourceVals) { | 597 | 0 | heap.markFrom(sourceVal.second); | 598 | 0 | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 0 | heap.sweep(); | 602 | 0 | } | 603 | 949k | return r; | 604 | 949k | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&) Line | Count | Source | 577 | 41.1M | { | 578 | 41.1M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 41.1M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 32.1k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 32.1k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 32.1k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 32.1k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 5.16M | for (const auto &sourceVal : sourceVals) { | 597 | 5.16M | heap.markFrom(sourceVal.second); | 598 | 5.16M | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 32.1k | heap.sweep(); | 602 | 32.1k | } | 603 | 41.1M | return r; | 604 | 41.1M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapClosure* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapClosure, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, char const (&) [1]>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, std::__1::vector<jsonnet::internal::(anonymous namespace)::HeapClosure::Param, std::__1::allocator<jsonnet::internal::(anonymous namespace)::HeapClosure::Param> > const&, jsonnet::internal::AST*&, char const (&) [1]) Line | Count | Source | 577 | 2.80M | { | 578 | 2.80M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 2.80M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 4.94k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 4.94k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 4.94k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 4.94k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 796k | for (const auto &sourceVal : sourceVals) { | 597 | 796k | heap.markFrom(sourceVal.second); | 598 | 796k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 4.94k | heap.sweep(); | 602 | 4.94k | } | 603 | 2.80M | return r; | 604 | 2.80M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, decltype(nullptr), int, jsonnet::internal::AST*&>(jsonnet::internal::Identifier const*&, decltype(nullptr)&&, int&&, jsonnet::internal::AST*&) vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const* const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&>(jsonnet::internal::Identifier const* const&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST* const&) Line | Count | Source | 577 | 25.6M | { | 578 | 25.6M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 25.6M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 30.6k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 30.6k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 30.6k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 30.6k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 4.92M | for (const auto &sourceVal : sourceVals) { | 597 | 4.92M | heap.markFrom(sourceVal.second); | 598 | 4.92M | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 30.6k | heap.sweep(); | 602 | 30.6k | } | 603 | 25.6M | return r; | 604 | 25.6M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapSimpleObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapSimpleObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >&, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >&) Line | Count | Source | 577 | 3.76M | { | 578 | 3.76M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 3.76M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 4.21k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 4.21k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 4.21k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 4.21k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 679k | for (const auto &sourceVal : sourceVals) { | 597 | 679k | heap.markFrom(sourceVal.second); | 598 | 679k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 4.21k | heap.sweep(); | 602 | 4.21k | } | 603 | 3.76M | return r; | 604 | 3.76M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST const* const&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST const* const&) Line | Count | Source | 577 | 115 | { | 578 | 115 | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 115 | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 0 | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 0 | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 0 | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 0 | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | |
| 596 | 0 | for (const auto &sourceVal : sourceVals) { | 597 | 0 | heap.markFrom(sourceVal.second); | 598 | 0 | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 0 | heap.sweep(); | 602 | 0 | } | 603 | 115 | return r; | 604 | 115 | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapExtendedObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapExtendedObject, jsonnet::internal::(anonymous namespace)::HeapObject*&, jsonnet::internal::(anonymous namespace)::HeapObject*&>(jsonnet::internal::(anonymous namespace)::HeapObject*&, jsonnet::internal::(anonymous namespace)::HeapObject*&) Line | Count | Source | 577 | 2.69M | { | 578 | 2.69M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 2.69M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 2.74k | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 2.74k | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 2.74k | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 2.74k | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 441k | for (const auto &sourceVal : sourceVals) { | 597 | 441k | heap.markFrom(sourceVal.second); | 598 | 441k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 2.74k | heap.sweep(); | 602 | 2.74k | } | 603 | 2.69M | return r; | 604 | 2.69M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::HeapThunk* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapThunk, jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST*&>(jsonnet::internal::Identifier const*&, jsonnet::internal::(anonymous namespace)::HeapObject*&, unsigned int&, jsonnet::internal::AST*&) Line | Count | Source | 577 | 1.14M | { | 578 | 1.14M | T *r = heap.makeEntity<T, Args...>(std::forward<Args>(args)...); | 579 | 1.14M | if (heap.checkHeap()) { // Do a GC cycle? | 580 | | // Avoid the object we just made being collected. | 581 | 303 | heap.markFrom(r); | 582 | | | 583 | | // Mark from the stack. | 584 | 303 | stack.mark(heap); | 585 | | | 586 | | // Mark from the scratch register | 587 | 303 | heap.markFrom(scratch); | 588 | | | 589 | | // Mark from cached imports | 590 | 303 | for (const auto &pair : cachedImports) { | 591 | 0 | HeapThunk *thunk = pair.second->thunk; | 592 | 0 | if (thunk != nullptr) | 593 | 0 | heap.markFrom(thunk); | 594 | 0 | } | 595 | | | 596 | 48.7k | for (const auto &sourceVal : sourceVals) { | 597 | 48.7k | heap.markFrom(sourceVal.second); | 598 | 48.7k | } | 599 | | | 600 | | // Delete unreachable objects. | 601 | 303 | heap.sweep(); | 602 | 303 | } | 603 | 1.14M | return r; | 604 | 1.14M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::HeapComprehensionObject* jsonnet::internal::(anonymous namespace)::Interpreter::makeHeap<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&>(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&, jsonnet::internal::AST*&, jsonnet::internal::Identifier const*&, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >&) |
605 | | |
606 | | Value makeBoolean(bool v) |
607 | 15.2M | { |
608 | 15.2M | Value r; |
609 | 15.2M | r.t = Value::BOOLEAN; |
610 | 15.2M | r.v.b = v; |
611 | 15.2M | return r; |
612 | 15.2M | } |
613 | | |
614 | | Value makeNumber(double v) |
615 | 11.3M | { |
616 | 11.3M | Value r; |
617 | 11.3M | r.t = Value::NUMBER; |
618 | 11.3M | r.v.d = v; |
619 | 11.3M | return r; |
620 | 11.3M | } |
621 | | |
622 | | Value makeNumberCheck(const LocationRange &loc, double v) |
623 | 10.2M | { |
624 | 10.2M | if (std::isnan(v)) { |
625 | 0 | throw makeError(loc, "not a number"); |
626 | 0 | } |
627 | 10.2M | if (std::isinf(v)) { |
628 | 44 | throw makeError(loc, "overflow"); |
629 | 44 | } |
630 | 10.2M | return makeNumber(v); |
631 | 10.2M | } |
632 | | |
633 | | Value makeNull(void) |
634 | 205k | { |
635 | 205k | Value r; |
636 | 205k | r.t = Value::NULL_TYPE; |
637 | 205k | return r; |
638 | 205k | } |
639 | | |
640 | | Value makeArray(const std::vector<HeapThunk *> &v) |
641 | 3.53M | { |
642 | 3.53M | Value r; |
643 | 3.53M | r.t = Value::ARRAY; |
644 | 3.53M | r.v.h = makeHeap<HeapArray>(v); |
645 | 3.53M | return r; |
646 | 3.53M | } |
647 | | |
648 | | Value makeClosure(const BindingFrame &env, HeapObject *self, unsigned offset, |
649 | | const HeapClosure::Params ¶ms, AST *body) |
650 | 2.80M | { |
651 | 2.80M | Value r; |
652 | 2.80M | r.t = Value::FUNCTION; |
653 | 2.80M | r.v.h = makeHeap<HeapClosure>(env, self, offset, params, body, ""); |
654 | 2.80M | return r; |
655 | 2.80M | } |
656 | | |
657 | | Value makeBuiltinFromAST(const BuiltinFunctionBody *body) |
658 | 11.6M | { |
659 | 11.6M | HeapClosure::Params hc_params; |
660 | 18.4M | for (const auto p : body->params) { |
661 | 18.4M | hc_params.emplace_back(p, nullptr); |
662 | 18.4M | } |
663 | 11.6M | Value r; |
664 | 11.6M | r.t = Value::FUNCTION; |
665 | 11.6M | r.v.h = makeHeap<HeapClosure>(BindingFrame(), nullptr, 0, hc_params, body, body->name); |
666 | 11.6M | return r; |
667 | 11.6M | } |
668 | | |
669 | | template <class T, class... Args> |
670 | | Value makeObject(Args... args) |
671 | 6.46M | { |
672 | 6.46M | Value r; |
673 | 6.46M | r.t = Value::OBJECT; |
674 | 6.46M | r.v.h = makeHeap<T>(args...); |
675 | 6.46M | return r; |
676 | 6.46M | } Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapRestrictedObject, jsonnet::internal::(anonymous namespace)::HeapObject*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::ObjectField::Hide, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::ObjectField::Hide> > > >(jsonnet::internal::(anonymous namespace)::HeapObject*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::ObjectField::Hide, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::ObjectField::Hide> > >) Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST const*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST const*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >) vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapSimpleObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapSimpleObject::Field> > >, std::__1::list<jsonnet::internal::AST*, std::__1::allocator<jsonnet::internal::AST*> >) Line | Count | Source | 671 | 3.76M | { | 672 | 3.76M | Value r; | 673 | 3.76M | r.t = Value::OBJECT; | 674 | 3.76M | r.v.h = makeHeap<T>(args...); | 675 | 3.76M | return r; | 676 | 3.76M | } |
vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapExtendedObject, jsonnet::internal::(anonymous namespace)::HeapObject*, jsonnet::internal::(anonymous namespace)::HeapObject*>(jsonnet::internal::(anonymous namespace)::HeapObject*, jsonnet::internal::(anonymous namespace)::HeapObject*) Line | Count | Source | 671 | 2.69M | { | 672 | 2.69M | Value r; | 673 | 2.69M | r.t = Value::OBJECT; | 674 | 2.69M | r.v.h = makeHeap<T>(args...); | 675 | 2.69M | return r; | 676 | 2.69M | } |
Unexecuted instantiation: vm.cpp:jsonnet::internal::(anonymous namespace)::Value jsonnet::internal::(anonymous namespace)::Interpreter::makeObject<jsonnet::internal::(anonymous namespace)::HeapComprehensionObject, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > > >(std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >, jsonnet::internal::AST*, jsonnet::internal::Identifier const*, std::__1::map<jsonnet::internal::Identifier const*, jsonnet::internal::(anonymous namespace)::HeapThunk*, std::__1::less<jsonnet::internal::Identifier const*>, std::__1::allocator<std::__1::pair<jsonnet::internal::Identifier const* const, jsonnet::internal::(anonymous namespace)::HeapThunk*> > >) |
677 | | |
678 | | Value makeString(const UString &v) |
679 | 54.5M | { |
680 | 54.5M | Value r; |
681 | 54.5M | r.t = Value::STRING; |
682 | 54.5M | r.v.h = makeHeap<HeapString>(v); |
683 | 54.5M | return r; |
684 | 54.5M | } |
685 | | |
686 | | /** Auxiliary function of objectIndex. |
687 | | * |
688 | | * Traverse the object's tree from right to left, looking for an object |
689 | | * with the given field. Call with offset initially set to 0. |
690 | | * |
691 | | * \param f The field we're looking for. |
692 | | * \param start_from Step over this many leaves first. |
693 | | * \param counter Return the level of "super" that contained the field. |
694 | | * \returns The first object with the field, or nullptr if it could not be found. |
695 | | */ |
696 | | HeapLeafObject *findObject(const Identifier *f, HeapObject *curr, unsigned start_from, |
697 | | unsigned &counter) |
698 | 1.41G | { |
699 | 1.41G | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
700 | 699M | auto *r = findObject(f, ext->right, start_from, counter); |
701 | 699M | if (r) |
702 | 9.55M | return r; |
703 | 689M | auto *l = findObject(f, ext->left, start_from, counter); |
704 | 689M | if (l) |
705 | 580M | return l; |
706 | 713M | } else if (auto *ext = dynamic_cast<HeapRestrictedObject *>(curr)) { |
707 | | // The RestrictedObject is on the 'right', that is it is checked/counted first. |
708 | 0 | if (counter >= start_from) { |
709 | 0 | if (ext->retainedKeys.find(f) == ext->retainedKeys.end()) { |
710 | 0 | counter += 1 + countLeaves(ext->obj); |
711 | 0 | return nullptr; |
712 | 0 | } |
713 | 0 | } |
714 | 0 | ++counter; |
715 | 0 | return findObject(f, ext->obj, start_from, counter); |
716 | 713M | } else { |
717 | 713M | if (counter >= start_from) { |
718 | 72.2M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
719 | 72.2M | auto it = simp->fields.find(f); |
720 | 72.2M | if (it != simp->fields.end()) { |
721 | 23.8M | return simp; |
722 | 23.8M | } |
723 | 72.2M | } else if (auto *comp = dynamic_cast<HeapComprehensionObject *>(curr)) { |
724 | 0 | auto it = comp->compValues.find(f); |
725 | 0 | if (it != comp->compValues.end()) { |
726 | 0 | return comp; |
727 | 0 | } |
728 | 0 | } |
729 | 72.2M | } |
730 | 690M | counter++; |
731 | 690M | } |
732 | 799M | return nullptr; |
733 | 1.41G | } |
734 | | |
735 | | typedef std::map<const Identifier *, ObjectField::Hide> IdHideMap; |
736 | | |
737 | | /** Auxiliary function. |
738 | | */ |
739 | | IdHideMap objectFieldsAux(const HeapObject *obj_) |
740 | 73.7M | { |
741 | 73.7M | IdHideMap r; |
742 | 73.7M | if (auto *obj = dynamic_cast<const HeapSimpleObject *>(obj_)) { |
743 | 37.5M | for (const auto &f : obj->fields) { |
744 | 29.6M | r[f.first] = f.second.hide; |
745 | 29.6M | } |
746 | | |
747 | 37.5M | } else if (auto *obj = dynamic_cast<const HeapRestrictedObject *>(obj_)) { |
748 | 0 | return obj->retainedKeys; |
749 | |
|
750 | 36.1M | } else if (auto *obj = dynamic_cast<const HeapExtendedObject *>(obj_)) { |
751 | 36.1M | r = objectFieldsAux(obj->right); |
752 | 111M | for (const auto &pair : objectFieldsAux(obj->left)) { |
753 | 111M | auto it = r.find(pair.first); |
754 | 111M | if (it == r.end()) { |
755 | | // First time it is seen |
756 | 84.2M | r[pair.first] = pair.second; |
757 | 84.2M | } else if (it->second == ObjectField::INHERIT) { |
758 | | // Seen before, but with inherited visibility so use new visibility |
759 | 26.5M | r[pair.first] = pair.second; |
760 | 26.5M | } |
761 | 111M | } |
762 | | |
763 | 36.1M | } else if (auto *obj = dynamic_cast<const HeapComprehensionObject *>(obj_)) { |
764 | 0 | for (const auto &f : obj->compValues) |
765 | 0 | r[f.first] = ObjectField::INHERIT; |
766 | 0 | } |
767 | 73.7M | return r; |
768 | 73.7M | } |
769 | | |
770 | | /** Auxiliary function. |
771 | | */ |
772 | | std::set<const Identifier *> objectFields(const HeapObject *obj_, bool manifesting) |
773 | 1.40M | { |
774 | 1.40M | std::set<const Identifier *> r; |
775 | 2.54M | for (const auto &pair : objectFieldsAux(obj_)) { |
776 | 2.54M | if (!manifesting || pair.second != ObjectField::HIDDEN) |
777 | 2.39M | r.insert(pair.first); |
778 | 2.54M | } |
779 | 1.40M | return r; |
780 | 1.40M | } |
781 | | |
782 | | /** Import another Jsonnet file. |
783 | | * |
784 | | * If the file has already been imported, then use that version. This maintains |
785 | | * referential transparency in the case of writes to disk during execution. The |
786 | | * cache holds a thunk in order to cache the resulting value of execution. |
787 | | * |
788 | | * \param loc Location of the import statement. |
789 | | * \param file Path to the filename. |
790 | | */ |
791 | | HeapThunk *import(const LocationRange &loc, const LiteralString *file) |
792 | 45 | { |
793 | 45 | ImportCacheValue *input = importData(loc, file); |
794 | 45 | if (input->thunk == nullptr) { |
795 | 0 | Tokens tokens = jsonnet_lex(input->foundHere, input->content.c_str()); |
796 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
797 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
798 | 0 | jsonnet_static_analysis(expr); |
799 | | // If no errors then populate cache. |
800 | 0 | auto *thunk = makeHeap<HeapThunk>(idImport, nullptr, 0, expr); |
801 | 0 | input->thunk = thunk; |
802 | 0 | } |
803 | 45 | return input->thunk; |
804 | 45 | } |
805 | | |
806 | | /** Import a file as a string or byte array. |
807 | | * |
808 | | * If the file has already been imported, then use that version. This maintains |
809 | | * referential transparency in the case of writes to disk during execution. |
810 | | * |
811 | | * \param loc Location of the import statement. |
812 | | * \param file Path to the filename. |
813 | | * \param found_here If non-null, used to store the actual path of the file |
814 | | */ |
815 | | ImportCacheValue *importData(const LocationRange &loc, const LiteralString *file) |
816 | 74 | { |
817 | | // `dir` is passed to the importCallback, which may be externally defined. |
818 | | // For backwards compatibility, we need to keep the trailing directory separator. |
819 | | // For example, the default callback in libjsonnet.cpp joins paths with simple |
820 | | // string concatenation. Other (external) implementations might do the same. |
821 | 74 | std::string dir = path_dir_with_trailing_separator(loc.file); |
822 | | |
823 | 74 | const UString &path = file->value; |
824 | | |
825 | 74 | std::pair<std::string, UString> key(dir, path); |
826 | 74 | ImportCacheValue *cached_value = cachedImports[key]; |
827 | 74 | if (cached_value != nullptr) |
828 | 0 | return cached_value; |
829 | | |
830 | 74 | char *found_here_cptr; |
831 | 74 | char *buf = NULL; |
832 | 74 | size_t buflen = 0; |
833 | 74 | int result = importCallback(importCallbackContext, |
834 | 74 | dir.c_str(), |
835 | 74 | encode_utf8(path).c_str(), |
836 | 74 | &found_here_cptr, |
837 | 74 | &buf, |
838 | 74 | &buflen); |
839 | | |
840 | 74 | std::string input(buf, buflen); |
841 | 74 | ::free(buf); |
842 | | |
843 | 74 | if (result == 1) { // failure |
844 | 74 | std::string epath = encode_utf8(jsonnet_string_escape(path, false)); |
845 | 74 | std::string msg = "couldn't open import \"" + epath + "\": "; |
846 | 74 | msg += input; |
847 | 74 | throw makeError(loc, msg); |
848 | 74 | } |
849 | | |
850 | 0 | auto *input_ptr = new ImportCacheValue(); |
851 | 0 | input_ptr->foundHere = found_here_cptr; |
852 | 0 | input_ptr->content = input; |
853 | 0 | input_ptr->thunk = nullptr; // May be filled in later by import(). |
854 | 0 | ::free(found_here_cptr); |
855 | 0 | cachedImports[key] = input_ptr; |
856 | 0 | return input_ptr; |
857 | 74 | } |
858 | | |
859 | | /** Capture the required variables from the environment. */ |
860 | | BindingFrame capture(const std::vector<const Identifier *> &free_vars) |
861 | 74.6M | { |
862 | 74.6M | BindingFrame env; |
863 | 1.44G | for (auto fv : free_vars) { |
864 | 1.44G | auto *th = stack.lookUpVar(fv); |
865 | 1.44G | env[fv] = th; |
866 | 1.44G | } |
867 | 74.6M | return env; |
868 | 74.6M | } |
869 | | |
870 | | /** Count the number of leaves in the tree. |
871 | | * |
872 | | * \param obj The root of the tree. |
873 | | * \returns The number of leaves. |
874 | | */ |
875 | | unsigned countLeaves(HeapObject *obj) |
876 | 2.93G | { |
877 | 2.93G | if (auto *ext = dynamic_cast<HeapExtendedObject *>(obj)) { |
878 | 1.46G | return countLeaves(ext->left) + countLeaves(ext->right); |
879 | 1.47G | } else if (auto *ext = dynamic_cast<HeapRestrictedObject *>(obj)) { |
880 | 0 | return countLeaves(ext->obj) + 1; |
881 | 1.47G | } else { |
882 | | // Must be a HeapLeafObject. |
883 | 1.47G | return 1; |
884 | 1.47G | } |
885 | 2.93G | } |
886 | | |
887 | 5.89k | void prepareSourceValThunks() { |
888 | 949k | for (const auto &field : stdlibAST->fields) { |
889 | 949k | AST *nameAST = field.name; |
890 | 949k | if (nameAST->type != AST_LITERAL_STRING) { |
891 | | // Skip any fields without a known name. |
892 | 0 | continue; |
893 | 0 | } |
894 | 949k | UString name = dynamic_cast<LiteralString *>(nameAST)->value; |
895 | | |
896 | 949k | sourceFuncIds.emplace_back(new Identifier(name)); |
897 | 949k | auto *th = makeHeap<HeapThunk>(sourceFuncIds.back().get(), stdObject, 0, field.body); |
898 | 949k | sourceVals[encode_utf8(name)] = th; |
899 | 949k | } |
900 | 5.89k | } |
901 | | |
902 | | /** Safely converts a double to an int64_t, with range and validity checks. |
903 | | * |
904 | | * This function is used primarily for bitwise operations which require integer operands. |
905 | | * It performs two safety checks: |
906 | | * 1. Verifies the value is finite (not NaN or Infinity) |
907 | | * 2. Ensures the value is within the safe integer range [-2^53, 2^53] |
908 | | * |
909 | | * The safe integer range limitation is necessary because IEEE 754 double precision |
910 | | * floating point numbers can only precisely represent integers in the range [-2^53, 2^53]. |
911 | | * Beyond this range, precision is lost, which would lead to unpredictable results |
912 | | * in bitwise operations that depend on exact bit patterns. |
913 | | * |
914 | | * \param value The double value to convert |
915 | | * \param loc The location in source code (for error reporting) |
916 | | * \throws RuntimeError if value is not finite or outside the safe integer range |
917 | | * \returns The value converted to int64_t |
918 | | */ |
919 | 98.1k | int64_t safeDoubleToInt64(double value, const internal::LocationRange& loc) { |
920 | 98.1k | if (std::isnan(value) || std::isinf(value)) { |
921 | 0 | throw internal::StaticError(loc, "numeric value is not finite"); |
922 | 0 | } |
923 | | |
924 | | // Constants for safe double-to-int conversion |
925 | | // Jsonnet uses IEEE 754 doubles, which precisely represent integers in the range [-2^53 + 1, 2^53 - 1]. |
926 | 98.1k | constexpr int64_t DOUBLE_MAX_SAFE_INTEGER = (1LL << 53) - 1; |
927 | 98.1k | constexpr int64_t DOUBLE_MIN_SAFE_INTEGER = -((1LL << 53) - 1); |
928 | | |
929 | | // Check if the value is within the safe integer range |
930 | 98.1k | if (value < DOUBLE_MIN_SAFE_INTEGER || value > DOUBLE_MAX_SAFE_INTEGER) { |
931 | 9 | throw makeError(loc, "numeric value outside safe integer range for bitwise operation."); |
932 | 9 | } |
933 | 98.1k | return static_cast<int64_t>(value); |
934 | 98.1k | } |
935 | | |
936 | | public: |
937 | | /** Create a new interpreter. |
938 | | * |
939 | | * \param loc The location range of the file to be executed. |
940 | | */ |
941 | | Interpreter(Allocator *alloc, const ExtMap &ext_vars, unsigned max_stack, double gc_min_objects, |
942 | | double gc_growth_trigger, const VmNativeCallbackMap &native_callbacks, |
943 | | JsonnetImportCallback *import_callback, void *import_callback_context) |
944 | | |
945 | 5.89k | : heap(gc_min_objects, gc_growth_trigger), |
946 | 5.89k | stack(max_stack), |
947 | 5.89k | alloc(alloc), |
948 | 5.89k | idImport(alloc->makeIdentifier(U"import")), |
949 | 5.89k | idArrayElement(alloc->makeIdentifier(U"array_element")), |
950 | 5.89k | idInvariant(alloc->makeIdentifier(U"object_assert")), |
951 | 5.89k | idInternal(alloc->makeIdentifier(U"__internal__")), |
952 | 5.89k | idJsonObjVar(alloc->makeIdentifier(U"_")), |
953 | 5.89k | idEmpty(alloc->makeIdentifier(U"")), |
954 | 5.89k | jsonObjVar(alloc->make<Var>(LocationRange(), Fodder{}, idJsonObjVar)), |
955 | 5.89k | externalVars(ext_vars), |
956 | 5.89k | importCallback(import_callback), |
957 | 5.89k | importCallbackContext(import_callback_context) |
958 | 5.89k | { |
959 | 5.89k | scratch = makeNull(); |
960 | | // Add a prefix to avoid conflicting with names from `native_callbacks`. |
961 | 5.89k | builtins["std:makeArray"] = &Interpreter::builtinMakeArray; |
962 | 5.89k | builtins["std:pow"] = &Interpreter::builtinPow; |
963 | 5.89k | builtins["std:floor"] = &Interpreter::builtinFloor; |
964 | 5.89k | builtins["std:ceil"] = &Interpreter::builtinCeil; |
965 | 5.89k | builtins["std:sqrt"] = &Interpreter::builtinSqrt; |
966 | 5.89k | builtins["std:sin"] = &Interpreter::builtinSin; |
967 | 5.89k | builtins["std:cos"] = &Interpreter::builtinCos; |
968 | 5.89k | builtins["std:tan"] = &Interpreter::builtinTan; |
969 | 5.89k | builtins["std:asin"] = &Interpreter::builtinAsin; |
970 | 5.89k | builtins["std:acos"] = &Interpreter::builtinAcos; |
971 | 5.89k | builtins["std:atan"] = &Interpreter::builtinAtan; |
972 | 5.89k | builtins["std:type"] = &Interpreter::builtinType; |
973 | 5.89k | builtins["std:filter"] = &Interpreter::builtinFilter; |
974 | 5.89k | builtins["std:objectHasEx"] = &Interpreter::builtinObjectHasEx; |
975 | 5.89k | builtins["std:length"] = &Interpreter::builtinLength; |
976 | 5.89k | builtins["std:objectFieldsEx"] = &Interpreter::builtinObjectFieldsEx; |
977 | 5.89k | builtins["std:objectRemoveKey"] = &Interpreter::builtinObjectRemoveKey; |
978 | 5.89k | builtins["std:codepoint"] = &Interpreter::builtinCodepoint; |
979 | 5.89k | builtins["std:char"] = &Interpreter::builtinChar; |
980 | 5.89k | builtins["std:log"] = &Interpreter::builtinLog; |
981 | 5.89k | builtins["std:exp"] = &Interpreter::builtinExp; |
982 | 5.89k | builtins["std:mantissa"] = &Interpreter::builtinMantissa; |
983 | 5.89k | builtins["std:exponent"] = &Interpreter::builtinExponent; |
984 | 5.89k | builtins["std:modulo"] = &Interpreter::builtinModulo; |
985 | 5.89k | builtins["std:extVar"] = &Interpreter::builtinExtVar; |
986 | 5.89k | builtins["std:primitiveEquals"] = &Interpreter::builtinPrimitiveEquals; |
987 | 5.89k | builtins["std:native"] = &Interpreter::builtinNative; |
988 | 5.89k | builtins["std:md5"] = &Interpreter::builtinMd5; |
989 | 5.89k | builtins["std:trace"] = &Interpreter::builtinTrace; |
990 | 5.89k | builtins["std:splitLimit"] = &Interpreter::builtinSplitLimit; |
991 | 5.89k | builtins["std:substr"] = &Interpreter::builtinSubstr; |
992 | 5.89k | builtins["std:range"] = &Interpreter::builtinRange; |
993 | 5.89k | builtins["std:strReplace"] = &Interpreter::builtinStrReplace; |
994 | 5.89k | builtins["std:asciiLower"] = &Interpreter::builtinAsciiLower; |
995 | 5.89k | builtins["std:asciiUpper"] = &Interpreter::builtinAsciiUpper; |
996 | 5.89k | builtins["std:join"] = &Interpreter::builtinJoin; |
997 | 5.89k | builtins["std:parseJson"] = &Interpreter::builtinParseJson; |
998 | 5.89k | builtins["std:parseYaml"] = &Interpreter::builtinParseYaml; |
999 | 5.89k | builtins["std:encodeUTF8"] = &Interpreter::builtinEncodeUTF8; |
1000 | 5.89k | builtins["std:decodeUTF8"] = &Interpreter::builtinDecodeUTF8; |
1001 | 5.89k | builtins["std:atan2"] = &Interpreter::builtinAtan2; |
1002 | 5.89k | builtins["std:hypot"] = &Interpreter::builtinHypot; |
1003 | | |
1004 | | // Add a prefix `native:` to names of provided callbacks to ensure they can't clash with the builtins above. |
1005 | | // Although we have separate lookup tables for them, the HeapClosure and BuiltinFunctionBody types just hold |
1006 | | // function "name" as a std::string to identify the function. |
1007 | 5.89k | for (const auto& [name, cb] : native_callbacks) { |
1008 | 0 | nativeCallbacks.emplace("native:" + name, cb); |
1009 | 0 | } |
1010 | | |
1011 | 5.89k | DesugaredObject *stdlib = makeStdlibAST(alloc, "__internal__"); |
1012 | 5.89k | jsonnet_static_analysis(stdlib); |
1013 | 5.89k | stdlibAST = stdlib; // stdlibAST is const, so we need to do analysis before this assignment |
1014 | 5.89k | auto stdThunk = makeHeap<HeapThunk>(nullptr, nullptr, 0, static_cast<const AST*>(stdlibAST)); |
1015 | 5.89k | stack.newCall(stdThunk->body->location, stdThunk, stdThunk->self, stdThunk->offset, stdThunk->upValues); |
1016 | 5.89k | evaluate(stdThunk->body, 0); |
1017 | 5.89k | stdObject = dynamic_cast<HeapObject*>(scratch.v.h); |
1018 | 5.89k | prepareSourceValThunks(); |
1019 | 5.89k | } |
1020 | | |
1021 | | |
1022 | | /** Clean up the heap, stack, stash, and builtin function ASTs. */ |
1023 | | ~Interpreter() |
1024 | 5.89k | { |
1025 | 5.89k | for (const auto &pair : cachedImports) { |
1026 | 74 | delete pair.second; |
1027 | 74 | } |
1028 | 5.89k | } |
1029 | | |
1030 | | const Value &getScratchRegister(void) |
1031 | 0 | { |
1032 | 0 | return scratch; |
1033 | 0 | } |
1034 | | |
1035 | | void setScratchRegister(const Value &v) |
1036 | 0 | { |
1037 | 0 | scratch = v; |
1038 | 0 | } |
1039 | | |
1040 | | /** Raise an error if the arguments aren't the expected types. */ |
1041 | | void validateBuiltinArgs(const LocationRange &loc, const std::string &name, |
1042 | | const std::vector<Value> &args, const std::vector<Value::Type> params) |
1043 | 351k | { |
1044 | 351k | if (args.size() == params.size()) { |
1045 | 1.02M | for (std::size_t i = 0; i < args.size(); ++i) { |
1046 | 669k | if (args[i].t != params[i]) |
1047 | 0 | goto bad; |
1048 | 669k | } |
1049 | 351k | return; |
1050 | 351k | } |
1051 | 0 | bad:; |
1052 | 0 | std::stringstream ss; |
1053 | 0 | ss << "Builtin function " + name + " expected ("; |
1054 | 0 | const char *prefix = ""; |
1055 | 0 | for (auto p : params) { |
1056 | 0 | ss << prefix << type_str(p); |
1057 | 0 | prefix = ", "; |
1058 | 0 | } |
1059 | 0 | ss << ") but got ("; |
1060 | 0 | prefix = ""; |
1061 | 0 | for (auto a : args) { |
1062 | 0 | ss << prefix << type_str(a); |
1063 | 0 | prefix = ", "; |
1064 | 0 | } |
1065 | 0 | ss << ")"; |
1066 | 0 | throw makeError(loc, ss.str()); |
1067 | 351k | } |
1068 | | |
1069 | | const AST *builtinMakeArray(const LocationRange &loc, const std::vector<Value> &args) |
1070 | 0 | { |
1071 | 0 | Frame &f = stack.top(); |
1072 | 0 | validateBuiltinArgs(loc, "makeArray", args, {Value::NUMBER, Value::FUNCTION}); |
1073 | 0 | long sz = long(args[0].v.d); |
1074 | 0 | if (sz < 0) { |
1075 | 0 | std::stringstream ss; |
1076 | 0 | ss << "makeArray requires size >= 0, got " << sz; |
1077 | 0 | throw makeError(loc, ss.str()); |
1078 | 0 | } |
1079 | 0 | auto *func = static_cast<const HeapClosure *>(args[1].v.h); |
1080 | 0 | std::vector<HeapThunk *> elements; |
1081 | 0 | if (func->params.size() != 1) { |
1082 | 0 | std::stringstream ss; |
1083 | 0 | ss << "makeArray function must take 1 param, got: " << func->params.size(); |
1084 | 0 | throw makeError(loc, ss.str()); |
1085 | 0 | } |
1086 | 0 | elements.resize(sz); |
1087 | 0 | for (long i = 0; i < sz; ++i) { |
1088 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, func->self, func->offset, func->body); |
1089 | | // The next line stops the new thunks from being GCed. |
1090 | 0 | f.thunks.push_back(th); |
1091 | 0 | th->upValues = func->upValues; |
1092 | |
|
1093 | 0 | auto *el = makeHeap<HeapThunk>(func->params[0].id, nullptr, 0, nullptr); |
1094 | 0 | el->fill(makeNumber(i)); // i guaranteed not to be inf/NaN |
1095 | 0 | th->upValues[func->params[0].id] = el; |
1096 | 0 | elements[i] = th; |
1097 | 0 | } |
1098 | 0 | scratch = makeArray(elements); |
1099 | 0 | return nullptr; |
1100 | 0 | } |
1101 | | |
1102 | | const AST *builtinPow(const LocationRange &loc, const std::vector<Value> &args) |
1103 | 2.62k | { |
1104 | 2.62k | validateBuiltinArgs(loc, "pow", args, {Value::NUMBER, Value::NUMBER}); |
1105 | 2.62k | scratch = makeNumberCheck(loc, std::pow(args[0].v.d, args[1].v.d)); |
1106 | 2.62k | return nullptr; |
1107 | 2.62k | } |
1108 | | |
1109 | | const AST *builtinFloor(const LocationRange &loc, const std::vector<Value> &args) |
1110 | 29.0k | { |
1111 | 29.0k | validateBuiltinArgs(loc, "floor", args, {Value::NUMBER}); |
1112 | 29.0k | scratch = makeNumberCheck(loc, std::floor(args[0].v.d)); |
1113 | 29.0k | return nullptr; |
1114 | 29.0k | } |
1115 | | |
1116 | | const AST *builtinCeil(const LocationRange &loc, const std::vector<Value> &args) |
1117 | 0 | { |
1118 | 0 | validateBuiltinArgs(loc, "ceil", args, {Value::NUMBER}); |
1119 | 0 | scratch = makeNumberCheck(loc, std::ceil(args[0].v.d)); |
1120 | 0 | return nullptr; |
1121 | 0 | } |
1122 | | |
1123 | | const AST *builtinSqrt(const LocationRange &loc, const std::vector<Value> &args) |
1124 | 0 | { |
1125 | 0 | validateBuiltinArgs(loc, "sqrt", args, {Value::NUMBER}); |
1126 | 0 | scratch = makeNumberCheck(loc, std::sqrt(args[0].v.d)); |
1127 | 0 | return nullptr; |
1128 | 0 | } |
1129 | | |
1130 | | const AST *builtinSin(const LocationRange &loc, const std::vector<Value> &args) |
1131 | 0 | { |
1132 | 0 | validateBuiltinArgs(loc, "sin", args, {Value::NUMBER}); |
1133 | 0 | scratch = makeNumberCheck(loc, std::sin(args[0].v.d)); |
1134 | 0 | return nullptr; |
1135 | 0 | } |
1136 | | |
1137 | | const AST *builtinCos(const LocationRange &loc, const std::vector<Value> &args) |
1138 | 0 | { |
1139 | 0 | validateBuiltinArgs(loc, "cos", args, {Value::NUMBER}); |
1140 | 0 | scratch = makeNumberCheck(loc, std::cos(args[0].v.d)); |
1141 | 0 | return nullptr; |
1142 | 0 | } |
1143 | | |
1144 | | const AST *builtinTan(const LocationRange &loc, const std::vector<Value> &args) |
1145 | 0 | { |
1146 | 0 | validateBuiltinArgs(loc, "tan", args, {Value::NUMBER}); |
1147 | 0 | scratch = makeNumberCheck(loc, std::tan(args[0].v.d)); |
1148 | 0 | return nullptr; |
1149 | 0 | } |
1150 | | |
1151 | | const AST *builtinAsin(const LocationRange &loc, const std::vector<Value> &args) |
1152 | 0 | { |
1153 | 0 | validateBuiltinArgs(loc, "asin", args, {Value::NUMBER}); |
1154 | 0 | scratch = makeNumberCheck(loc, std::asin(args[0].v.d)); |
1155 | 0 | return nullptr; |
1156 | 0 | } |
1157 | | |
1158 | | const AST *builtinAcos(const LocationRange &loc, const std::vector<Value> &args) |
1159 | 0 | { |
1160 | 0 | validateBuiltinArgs(loc, "acos", args, {Value::NUMBER}); |
1161 | 0 | scratch = makeNumberCheck(loc, std::acos(args[0].v.d)); |
1162 | 0 | return nullptr; |
1163 | 0 | } |
1164 | | |
1165 | | const AST *builtinAtan(const LocationRange &loc, const std::vector<Value> &args) |
1166 | 0 | { |
1167 | 0 | validateBuiltinArgs(loc, "atan", args, {Value::NUMBER}); |
1168 | 0 | scratch = makeNumberCheck(loc, std::atan(args[0].v.d)); |
1169 | 0 | return nullptr; |
1170 | 0 | } |
1171 | | |
1172 | | const AST *builtinAtan2(const LocationRange &loc, const std::vector<Value> &args) |
1173 | 0 | { |
1174 | 0 | validateBuiltinArgs(loc, "atan2", args, {Value::NUMBER, Value::NUMBER}); |
1175 | 0 | scratch = makeNumberCheck(loc, std::atan2(args[0].v.d, args[1].v.d)); |
1176 | 0 | return nullptr; |
1177 | 0 | } |
1178 | | |
1179 | | const AST *builtinHypot(const LocationRange &loc, const std::vector<Value> &args) |
1180 | 0 | { |
1181 | 0 | validateBuiltinArgs(loc, "hypot", args, {Value::NUMBER, Value::NUMBER}); |
1182 | 0 | scratch = makeNumberCheck(loc, std::hypot(args[0].v.d, args[1].v.d)); |
1183 | 0 | return nullptr; |
1184 | 0 | } |
1185 | | |
1186 | | const AST *builtinType(const LocationRange &, const std::vector<Value> &args) |
1187 | 4.07M | { |
1188 | 4.07M | switch (args[0].t) { |
1189 | 293k | case Value::NULL_TYPE: scratch = makeString(U"null"); return nullptr; |
1190 | | |
1191 | 358k | case Value::BOOLEAN: scratch = makeString(U"boolean"); return nullptr; |
1192 | | |
1193 | 606k | case Value::NUMBER: scratch = makeString(U"number"); return nullptr; |
1194 | | |
1195 | 306k | case Value::ARRAY: scratch = makeString(U"array"); return nullptr; |
1196 | | |
1197 | 49 | case Value::FUNCTION: scratch = makeString(U"function"); return nullptr; |
1198 | | |
1199 | 258k | case Value::OBJECT: scratch = makeString(U"object"); return nullptr; |
1200 | | |
1201 | 2.25M | case Value::STRING: scratch = makeString(U"string"); return nullptr; |
1202 | 4.07M | } |
1203 | 0 | return nullptr; // Quiet, compiler. |
1204 | 4.07M | } |
1205 | | |
1206 | | const AST *builtinFilter(const LocationRange &loc, const std::vector<Value> &args) |
1207 | 0 | { |
1208 | 0 | Frame &f = stack.top(); |
1209 | 0 | validateBuiltinArgs(loc, "filter", args, {Value::FUNCTION, Value::ARRAY}); |
1210 | 0 | auto *func = static_cast<HeapClosure *>(args[0].v.h); |
1211 | 0 | auto *arr = static_cast<HeapArray *>(args[1].v.h); |
1212 | 0 | if (func->params.size() != 1) { |
1213 | 0 | throw makeError(loc, "filter function takes 1 parameter."); |
1214 | 0 | } |
1215 | 0 | if (arr->elements.size() == 0) { |
1216 | 0 | scratch = makeArray({}); |
1217 | 0 | } else { |
1218 | 0 | f.kind = FRAME_BUILTIN_FILTER; |
1219 | 0 | f.val = args[0]; |
1220 | 0 | f.val2 = args[1]; |
1221 | 0 | f.thunks.clear(); |
1222 | 0 | f.elementId = 0; |
1223 | |
|
1224 | 0 | auto *thunk = arr->elements[f.elementId]; |
1225 | 0 | BindingFrame bindings = func->upValues; |
1226 | 0 | bindings[func->params[0].id] = thunk; |
1227 | 0 | stack.newCall(loc, func, func->self, func->offset, bindings); |
1228 | 0 | return func->body; |
1229 | 0 | } |
1230 | 0 | return nullptr; |
1231 | 0 | } |
1232 | | |
1233 | | const AST *builtinObjectHasEx(const LocationRange &loc, const std::vector<Value> &args) |
1234 | 0 | { |
1235 | 0 | validateBuiltinArgs( |
1236 | 0 | loc, "objectHasEx", args, {Value::OBJECT, Value::STRING, Value::BOOLEAN}); |
1237 | 0 | const auto *obj = static_cast<const HeapObject *>(args[0].v.h); |
1238 | 0 | const auto *str = static_cast<const HeapString *>(args[1].v.h); |
1239 | 0 | bool include_hidden = args[2].v.b; |
1240 | 0 | bool found = false; |
1241 | 0 | for (const auto &field : objectFields(obj, !include_hidden)) { |
1242 | 0 | if (field->name == str->value) { |
1243 | 0 | found = true; |
1244 | 0 | break; |
1245 | 0 | } |
1246 | 0 | } |
1247 | 0 | scratch = makeBoolean(found); |
1248 | 0 | return nullptr; |
1249 | 0 | } |
1250 | | |
1251 | | const AST *builtinLength(const LocationRange &loc, const std::vector<Value> &args) |
1252 | 868k | { |
1253 | 868k | if (args.size() != 1) { |
1254 | 0 | throw makeError(loc, "length takes 1 parameter."); |
1255 | 0 | } |
1256 | 868k | HeapEntity *e = args[0].v.h; |
1257 | 868k | switch (args[0].t) { |
1258 | 1 | case Value::OBJECT: { |
1259 | 1 | auto fields = objectFields(static_cast<HeapObject *>(e), true); |
1260 | 1 | scratch = makeNumber(fields.size()); |
1261 | 1 | } break; |
1262 | | |
1263 | 470k | case Value::ARRAY: |
1264 | 470k | scratch = makeNumber(static_cast<HeapArray *>(e)->elements.size()); |
1265 | 470k | break; |
1266 | | |
1267 | 397k | case Value::STRING: |
1268 | 397k | scratch = makeNumber(static_cast<HeapString *>(e)->value.length()); |
1269 | 397k | break; |
1270 | | |
1271 | 0 | case Value::FUNCTION: |
1272 | 0 | scratch = makeNumber(static_cast<HeapClosure *>(e)->params.size()); |
1273 | 0 | break; |
1274 | | |
1275 | 13 | default: |
1276 | 13 | throw makeError(loc, |
1277 | 13 | "length operates on strings, objects, " |
1278 | 13 | "and arrays, got " + |
1279 | 13 | type_str(args[0])); |
1280 | 868k | } |
1281 | 868k | return nullptr; |
1282 | 868k | } |
1283 | | |
1284 | | const AST *builtinObjectFieldsEx(const LocationRange &loc, const std::vector<Value> &args) |
1285 | 242k | { |
1286 | 242k | validateBuiltinArgs(loc, "objectFieldsEx", args, {Value::OBJECT, Value::BOOLEAN}); |
1287 | 242k | const auto *obj = static_cast<HeapObject *>(args[0].v.h); |
1288 | 242k | bool include_hidden = args[1].v.b; |
1289 | | // Stash in a set first to sort them. |
1290 | 242k | std::set<UString> fields; |
1291 | 822k | for (const auto &field : objectFields(obj, !include_hidden)) { |
1292 | 822k | fields.insert(field->name); |
1293 | 822k | } |
1294 | 242k | scratch = makeArray({}); |
1295 | 242k | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1296 | 822k | for (const auto &field : fields) { |
1297 | 822k | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1298 | 822k | elements.push_back(th); |
1299 | 822k | th->fill(makeString(field)); |
1300 | 822k | } |
1301 | 242k | return nullptr; |
1302 | 242k | } |
1303 | | |
1304 | | const AST *builtinObjectRemoveKey(const LocationRange &loc, const std::vector<Value> &args) |
1305 | 0 | { |
1306 | 0 | validateBuiltinArgs(loc, "objectRemoveKey", args, {Value::OBJECT, Value::STRING}); |
1307 | 0 | auto *obj = static_cast<HeapObject *>(args[0].v.h); |
1308 | 0 | const auto *key = static_cast<HeapString *>(args[1].v.h); |
1309 | 0 | const auto *key_id = alloc->makeIdentifier(key->value); |
1310 | 0 | auto fields = objectFieldsAux(obj); |
1311 | 0 | const auto it = fields.find(key_id); |
1312 | 0 | if (it != fields.end()) { |
1313 | 0 | fields.erase(it); |
1314 | 0 | } |
1315 | | // TODO: If the key isn't found perhaps we can evaluate to the existing `obj`? |
1316 | 0 | scratch = makeObject<HeapRestrictedObject>(obj, fields); |
1317 | 0 | return nullptr; |
1318 | 0 | } |
1319 | | |
1320 | | const AST *builtinCodepoint(const LocationRange &loc, const std::vector<Value> &args) |
1321 | 0 | { |
1322 | 0 | validateBuiltinArgs(loc, "codepoint", args, {Value::STRING}); |
1323 | 0 | const UString &str = static_cast<HeapString *>(args[0].v.h)->value; |
1324 | 0 | if (str.length() != 1) { |
1325 | 0 | std::stringstream ss; |
1326 | 0 | ss << "codepoint takes a string of length 1, got length " << str.length(); |
1327 | 0 | throw makeError(loc, ss.str()); |
1328 | 0 | } |
1329 | 0 | char32_t c = static_cast<HeapString *>(args[0].v.h)->value[0]; |
1330 | 0 | scratch = makeNumber((unsigned long)(c)); |
1331 | 0 | return nullptr; |
1332 | 0 | } |
1333 | | |
1334 | | const AST *builtinChar(const LocationRange &loc, const std::vector<Value> &args) |
1335 | 2.03k | { |
1336 | 2.03k | validateBuiltinArgs(loc, "char", args, {Value::NUMBER}); |
1337 | 2.03k | long l = long(args[0].v.d); |
1338 | 2.03k | if (l < 0) { |
1339 | 92 | std::stringstream ss; |
1340 | 92 | ss << "codepoints must be >= 0, got " << l; |
1341 | 92 | throw makeError(loc, ss.str()); |
1342 | 92 | } |
1343 | 1.94k | if (l >= JSONNET_CODEPOINT_MAX) { |
1344 | 62 | std::stringstream ss; |
1345 | 62 | ss << "invalid unicode codepoint, got " << l; |
1346 | 62 | throw makeError(loc, ss.str()); |
1347 | 62 | } |
1348 | 1.88k | char32_t c = l; |
1349 | 1.88k | scratch = makeString(UString(&c, 1)); |
1350 | 1.88k | return nullptr; |
1351 | 1.94k | } |
1352 | | |
1353 | | const AST *builtinLog(const LocationRange &loc, const std::vector<Value> &args) |
1354 | 3.31k | { |
1355 | 3.31k | validateBuiltinArgs(loc, "log", args, {Value::NUMBER}); |
1356 | 3.31k | scratch = makeNumberCheck(loc, std::log(args[0].v.d)); |
1357 | 3.31k | return nullptr; |
1358 | 3.31k | } |
1359 | | |
1360 | | const AST *builtinExp(const LocationRange &loc, const std::vector<Value> &args) |
1361 | 0 | { |
1362 | 0 | validateBuiltinArgs(loc, "exp", args, {Value::NUMBER}); |
1363 | 0 | scratch = makeNumberCheck(loc, std::exp(args[0].v.d)); |
1364 | 0 | return nullptr; |
1365 | 0 | } |
1366 | | |
1367 | | const AST *builtinMantissa(const LocationRange &loc, const std::vector<Value> &args) |
1368 | 0 | { |
1369 | 0 | validateBuiltinArgs(loc, "mantissa", args, {Value::NUMBER}); |
1370 | 0 | int exp; |
1371 | 0 | double m = std::frexp(args[0].v.d, &exp); |
1372 | 0 | scratch = makeNumberCheck(loc, m); |
1373 | 0 | return nullptr; |
1374 | 0 | } |
1375 | | |
1376 | | const AST *builtinExponent(const LocationRange &loc, const std::vector<Value> &args) |
1377 | 0 | { |
1378 | 0 | validateBuiltinArgs(loc, "exponent", args, {Value::NUMBER}); |
1379 | 0 | int exp; |
1380 | 0 | std::frexp(args[0].v.d, &exp); |
1381 | 0 | scratch = makeNumberCheck(loc, exp); |
1382 | 0 | return nullptr; |
1383 | 0 | } |
1384 | | |
1385 | | const AST *builtinModulo(const LocationRange &loc, const std::vector<Value> &args) |
1386 | 71.4k | { |
1387 | 71.4k | validateBuiltinArgs(loc, "modulo", args, {Value::NUMBER, Value::NUMBER}); |
1388 | 71.4k | double a = args[0].v.d; |
1389 | 71.4k | double b = args[1].v.d; |
1390 | 71.4k | if (b == 0) |
1391 | 25 | throw makeError(loc, "division by zero."); |
1392 | 71.3k | scratch = makeNumberCheck(loc, std::fmod(a, b)); |
1393 | 71.3k | return nullptr; |
1394 | 71.4k | } |
1395 | | |
1396 | | const AST *builtinExtVar(const LocationRange &loc, const std::vector<Value> &args) |
1397 | 0 | { |
1398 | 0 | validateBuiltinArgs(loc, "extVar", args, {Value::STRING}); |
1399 | 0 | const UString &var = static_cast<HeapString *>(args[0].v.h)->value; |
1400 | 0 | std::string var8 = encode_utf8(var); |
1401 | 0 | auto it = externalVars.find(var8); |
1402 | 0 | if (it == externalVars.end()) { |
1403 | 0 | std::string msg = "undefined external variable: " + var8; |
1404 | 0 | throw makeError(loc, msg); |
1405 | 0 | } |
1406 | 0 | const VmExt &ext = it->second; |
1407 | 0 | if (ext.isCode) { |
1408 | 0 | std::string filename = "<extvar:" + var8 + ">"; |
1409 | 0 | Tokens tokens = jsonnet_lex(filename, ext.data.c_str()); |
1410 | 0 | AST *expr = jsonnet_parse(alloc, tokens); |
1411 | 0 | jsonnet_desugar(alloc, expr, nullptr); |
1412 | 0 | jsonnet_static_analysis(expr); |
1413 | 0 | stack.pop(); |
1414 | 0 | return expr; |
1415 | 0 | } else { |
1416 | 0 | scratch = makeString(decode_utf8(ext.data)); |
1417 | 0 | return nullptr; |
1418 | 0 | } |
1419 | 0 | } |
1420 | | |
1421 | | const AST *builtinPrimitiveEquals(const LocationRange &loc, const std::vector<Value> &args) |
1422 | 6.38M | { |
1423 | 6.38M | if (args.size() != 2) { |
1424 | 0 | std::stringstream ss; |
1425 | 0 | ss << "primitiveEquals takes 2 parameters, got " << args.size(); |
1426 | 0 | throw makeError(loc, ss.str()); |
1427 | 0 | } |
1428 | 6.38M | if (args[0].t != args[1].t) { |
1429 | 0 | scratch = makeBoolean(false); |
1430 | 0 | return nullptr; |
1431 | 0 | } |
1432 | 6.38M | bool r; |
1433 | 6.38M | switch (args[0].t) { |
1434 | 113k | case Value::BOOLEAN: r = args[0].v.b == args[1].v.b; break; |
1435 | | |
1436 | 260k | case Value::NUMBER: r = args[0].v.d == args[1].v.d; break; |
1437 | | |
1438 | 5.91M | case Value::STRING: |
1439 | 5.91M | r = static_cast<HeapString *>(args[0].v.h)->value == |
1440 | 5.91M | static_cast<HeapString *>(args[1].v.h)->value; |
1441 | 5.91M | break; |
1442 | | |
1443 | 103k | case Value::NULL_TYPE: r = true; break; |
1444 | | |
1445 | 0 | case Value::FUNCTION: throw makeError(loc, "cannot test equality of functions"); break; |
1446 | | |
1447 | 0 | default: |
1448 | 0 | throw makeError(loc, |
1449 | 0 | "primitiveEquals operates on primitive " |
1450 | 0 | "types, got " + |
1451 | 0 | type_str(args[0])); |
1452 | 6.38M | } |
1453 | 6.38M | scratch = makeBoolean(r); |
1454 | 6.38M | return nullptr; |
1455 | 6.38M | } |
1456 | | |
1457 | | const AST *builtinNative(const LocationRange &loc, const std::vector<Value> &args) |
1458 | 0 | { |
1459 | 0 | validateBuiltinArgs(loc, "native", args, {Value::STRING}); |
1460 | | |
1461 | | // Add a prefix to avoid conflicting with any name in `std`. |
1462 | 0 | std::string builtin_name = "native:" + encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1463 | |
|
1464 | 0 | VmNativeCallbackMap::const_iterator nit = nativeCallbacks.find(builtin_name); |
1465 | 0 | if (nit == nativeCallbacks.end()) { |
1466 | 0 | scratch = makeNull(); |
1467 | 0 | } else { |
1468 | 0 | const VmNativeCallback &cb = nit->second; |
1469 | 0 | const auto ¶ms = cb.params; |
1470 | 0 | const auto body = alloc->makeBuiltinBody(LocationRange{}, builtin_name, |
1471 | 0 | [this, ¶ms]()->Identifiers { |
1472 | 0 | Identifiers ids; |
1473 | 0 | for (const auto &name : params) { |
1474 | 0 | ids.emplace_back(this->alloc->makeIdentifier(decode_utf8(name))); |
1475 | 0 | } |
1476 | 0 | return ids; |
1477 | 0 | }); |
1478 | 0 | scratch = makeBuiltinFromAST(body); |
1479 | 0 | } |
1480 | 0 | return nullptr; |
1481 | 0 | } |
1482 | | |
1483 | | const AST *builtinMd5(const LocationRange &loc, const std::vector<Value> &args) |
1484 | 0 | { |
1485 | 0 | validateBuiltinArgs(loc, "md5", args, {Value::STRING}); |
1486 | |
|
1487 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1488 | |
|
1489 | 0 | scratch = makeString(decode_utf8(md5(value))); |
1490 | 0 | return nullptr; |
1491 | 0 | } |
1492 | | |
1493 | | const AST *builtinEncodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1494 | 0 | { |
1495 | 0 | validateBuiltinArgs(loc, "encodeUTF8", args, {Value::STRING}); |
1496 | |
|
1497 | 0 | std::string byteString = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1498 | |
|
1499 | 0 | scratch = makeArray({}); |
1500 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1501 | 0 | for (const auto c : byteString) { |
1502 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1503 | 0 | elements.push_back(th); |
1504 | 0 | th->fill(makeNumber(uint8_t(c))); |
1505 | 0 | } |
1506 | 0 | return nullptr; |
1507 | 0 | } |
1508 | | |
1509 | | const AST *decodeUTF8(void) |
1510 | 0 | { |
1511 | 0 | Frame &f = stack.top(); |
1512 | 0 | const auto& elements = static_cast<HeapArray*>(f.val.v.h)->elements; |
1513 | 0 | while (f.elementId < elements.size()) { |
1514 | 0 | auto *th = elements[f.elementId]; |
1515 | 0 | if (th->filled) { |
1516 | 0 | auto b = th->content; |
1517 | 0 | if (b.t != Value::NUMBER) { |
1518 | 0 | std::stringstream ss; |
1519 | 0 | ss << "Element " << f.elementId << " of the provided array was not a number"; |
1520 | 0 | throw makeError(stack.top().location, ss.str()); |
1521 | 0 | } else { |
1522 | 0 | double d = b.v.d; |
1523 | 0 | if (d < 0 || d > 255 || d != int(d)) { |
1524 | 0 | std::stringstream ss; |
1525 | 0 | ss << "Element " << f.elementId << " of the provided array was not an integer in range [0,255]"; |
1526 | 0 | throw makeError(stack.top().location, ss.str()); |
1527 | 0 | } |
1528 | 0 | f.bytes.push_back(uint8_t(d)); |
1529 | 0 | } |
1530 | 0 | f.elementId++; |
1531 | 0 | } else { |
1532 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1533 | 0 | return th->body; |
1534 | 0 | } |
1535 | 0 | } |
1536 | 0 | scratch = makeString(decode_utf8(f.bytes)); |
1537 | 0 | return nullptr; |
1538 | 0 | } |
1539 | | |
1540 | | const AST *builtinDecodeUTF8(const LocationRange &loc, const std::vector<Value> &args) |
1541 | 0 | { |
1542 | 0 | validateBuiltinArgs(loc, "decodeUTF8", args, {Value::ARRAY}); |
1543 | |
|
1544 | 0 | Frame &f = stack.top(); |
1545 | 0 | f.kind = FRAME_BUILTIN_DECODE_UTF8; |
1546 | 0 | f.val = args[0]; // arr |
1547 | 0 | f.bytes.clear(); |
1548 | 0 | f.elementId = 0; |
1549 | 0 | return decodeUTF8(); |
1550 | 0 | } |
1551 | | |
1552 | | const AST *builtinTrace(const LocationRange &loc, const std::vector<Value> &args) |
1553 | 0 | { |
1554 | 0 | if(args[0].t != Value::STRING) { |
1555 | 0 | std::stringstream ss; |
1556 | 0 | ss << "Builtin function trace expected string as first parameter but " |
1557 | 0 | << "got " << type_str(args[0].t); |
1558 | 0 | throw makeError(loc, ss.str()); |
1559 | 0 | } |
1560 | | |
1561 | 0 | std::string str = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1562 | 0 | std::cerr << "TRACE: " << loc.file << ":" << loc.begin.line << " " << str |
1563 | 0 | << std::endl; |
1564 | |
|
1565 | 0 | scratch = args[1]; |
1566 | 0 | return nullptr; |
1567 | 0 | } |
1568 | | |
1569 | | const AST *builtinSplitLimit(const LocationRange &loc, const std::vector<Value> &args) |
1570 | 0 | { |
1571 | 0 | validateBuiltinArgs(loc, "splitLimit", args, {Value::STRING, Value::STRING, Value::NUMBER}); |
1572 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1573 | 0 | const auto *c = static_cast<const HeapString *>(args[1].v.h); |
1574 | 0 | long maxsplits = long(args[2].v.d); |
1575 | 0 | unsigned start = 0; |
1576 | 0 | unsigned test = 0; |
1577 | 0 | scratch = makeArray({}); |
1578 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1579 | 0 | while (test < str->value.size() && (maxsplits == -1 || |
1580 | 0 | size_t(maxsplits) > elements.size())) { |
1581 | 0 | if (c->value == str->value.substr(test, c->value.size())) { |
1582 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1583 | 0 | elements.push_back(th); |
1584 | 0 | th->fill(makeString(str->value.substr(start, test - start))); |
1585 | 0 | start = test + c->value.size(); |
1586 | 0 | test = start; |
1587 | 0 | } else { |
1588 | 0 | ++test; |
1589 | 0 | } |
1590 | 0 | } |
1591 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1592 | 0 | elements.push_back(th); |
1593 | 0 | th->fill(makeString(str->value.substr(start))); |
1594 | |
|
1595 | 0 | return nullptr; |
1596 | 0 | } |
1597 | | |
1598 | | const AST *builtinSubstr(const LocationRange &loc, const std::vector<Value> &args) |
1599 | 955 | { |
1600 | 955 | validateBuiltinArgs(loc, "substr", args, {Value::STRING, Value::NUMBER, Value::NUMBER}); |
1601 | 955 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1602 | 955 | long from = long(args[1].v.d); |
1603 | 955 | long len = long(args[2].v.d); |
1604 | 955 | if (from < 0) { |
1605 | 0 | std::stringstream ss; |
1606 | 0 | ss << "substr second parameter should be greater than zero, got " << from; |
1607 | 0 | throw makeError(loc, ss.str()); |
1608 | 0 | } |
1609 | 955 | if (len < 0) { |
1610 | 0 | std::stringstream ss; |
1611 | 0 | ss << "substr third parameter should be greater than zero, got " << len; |
1612 | 0 | throw makeError(loc, ss.str()); |
1613 | 0 | } |
1614 | 955 | if (static_cast<unsigned long>(from) > str->value.size()) { |
1615 | 0 | scratch = makeString(UString()); |
1616 | 0 | return nullptr; |
1617 | 0 | } |
1618 | 955 | if (size_t(len + from) > str->value.size()) { |
1619 | 0 | len = str->value.size() - from; |
1620 | 0 | } |
1621 | 955 | scratch = makeString(str->value.substr(from, len)); |
1622 | 955 | return nullptr; |
1623 | 955 | } |
1624 | | |
1625 | | const AST *builtinRange(const LocationRange &loc, const std::vector<Value> &args) |
1626 | 0 | { |
1627 | 0 | validateBuiltinArgs(loc, "range", args, {Value::NUMBER, Value::NUMBER}); |
1628 | 0 | long from = long(args[0].v.d); |
1629 | 0 | long to = long(args[1].v.d); |
1630 | 0 | long len = to - from + 1; |
1631 | 0 | scratch = makeArray({}); |
1632 | 0 | if (len > 0) { |
1633 | 0 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
1634 | 0 | for (int i = 0; i < len; ++i) { |
1635 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
1636 | 0 | elements.push_back(th); |
1637 | 0 | th->fill(makeNumber(from + i)); |
1638 | 0 | } |
1639 | 0 | } |
1640 | 0 | return nullptr; |
1641 | 0 | } |
1642 | | |
1643 | | const AST *builtinStrReplace(const LocationRange &loc, const std::vector<Value> &args) |
1644 | 0 | { |
1645 | 0 | validateBuiltinArgs(loc, "strReplace", args, {Value::STRING, Value::STRING, Value::STRING}); |
1646 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1647 | 0 | const auto *from = static_cast<const HeapString *>(args[1].v.h); |
1648 | 0 | const auto *to = static_cast<const HeapString *>(args[2].v.h); |
1649 | 0 | if (from->value.empty()) { |
1650 | 0 | throw makeError(loc, "'from' string must not be zero length."); |
1651 | 0 | } |
1652 | 0 | UString new_str(str->value); |
1653 | 0 | UString::size_type pos = 0; |
1654 | 0 | while (pos < new_str.size()) { |
1655 | 0 | auto index = new_str.find(from->value, pos); |
1656 | 0 | if (index == new_str.npos) { |
1657 | 0 | break; |
1658 | 0 | } |
1659 | 0 | new_str.replace(index, from->value.size(), to->value); |
1660 | 0 | pos = index + to->value.size(); |
1661 | 0 | } |
1662 | 0 | scratch = makeString(new_str); |
1663 | 0 | return nullptr; |
1664 | 0 | } |
1665 | | |
1666 | | const AST *builtinAsciiLower(const LocationRange &loc, const std::vector<Value> &args) |
1667 | 0 | { |
1668 | 0 | validateBuiltinArgs(loc, "asciiLower", args, {Value::STRING}); |
1669 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1670 | 0 | UString new_str(str->value); |
1671 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1672 | 0 | if (new_str[i] >= 'A' && new_str[i] <= 'Z') { |
1673 | 0 | new_str[i] = new_str[i] - 'A' + 'a'; |
1674 | 0 | } |
1675 | 0 | } |
1676 | 0 | scratch = makeString(new_str); |
1677 | 0 | return nullptr; |
1678 | 0 | } |
1679 | | |
1680 | | const AST *builtinAsciiUpper(const LocationRange &loc, const std::vector<Value> &args) |
1681 | 0 | { |
1682 | 0 | validateBuiltinArgs(loc, "asciiUpper", args, {Value::STRING}); |
1683 | 0 | const auto *str = static_cast<const HeapString *>(args[0].v.h); |
1684 | 0 | UString new_str(str->value); |
1685 | 0 | for (size_t i = 0; i < new_str.size(); ++i) { |
1686 | 0 | if (new_str[i] >= 'a' && new_str[i] <= 'z') { |
1687 | 0 | new_str[i] = new_str[i] - 'a' + 'A'; |
1688 | 0 | } |
1689 | 0 | } |
1690 | 0 | scratch = makeString(new_str); |
1691 | 0 | return nullptr; |
1692 | 0 | } |
1693 | | |
1694 | | const AST *builtinParseJson(const LocationRange &loc, const std::vector<Value> &args) |
1695 | 0 | { |
1696 | 0 | validateBuiltinArgs(loc, "parseJson", args, {Value::STRING}); |
1697 | |
|
1698 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1699 | |
|
1700 | 0 | try { |
1701 | 0 | auto j = json::parse(value); |
1702 | |
|
1703 | 0 | bool filled; |
1704 | 0 | otherJsonToHeap(j, filled, scratch); |
1705 | 0 | } catch (const json::parse_error &e) { |
1706 | 0 | throw makeError(loc, e.what()); |
1707 | 0 | } |
1708 | | |
1709 | 0 | return nullptr; |
1710 | 0 | } |
1711 | | |
1712 | | const AST *builtinParseYaml(const LocationRange &loc, const std::vector<Value> &args) |
1713 | 0 | { |
1714 | 0 | validateBuiltinArgs(loc, "parseYaml", args, {Value::STRING}); |
1715 | 0 | std::string value = encode_utf8(static_cast<HeapString *>(args[0].v.h)->value); |
1716 | 0 | json j; |
1717 | 0 | try { |
1718 | | // Use a custom EventHandler so we can attach error handling. |
1719 | 0 | ryml::EventHandlerTree et{ryml::Callbacks{ |
1720 | 0 | nullptr, nullptr, nullptr, &Interpreter::handleRapidYamlError |
1721 | 0 | }}; |
1722 | 0 | ryml::Parser pe(&et); |
1723 | 0 | ryml::Tree tree = ryml::parse_in_arena(&pe, ryml::to_csubstr(value)); |
1724 | |
|
1725 | 0 | if (tree.type(tree.root_id()).is_notype()) { |
1726 | | // Nothing to do; `j` is already null! |
1727 | 0 | } else if (tree.is_stream(tree.root_id())) { |
1728 | 0 | for (ryml::ConstNodeRef node : tree.crootref().children()) { |
1729 | 0 | std::ostringstream jsonText; |
1730 | 0 | jsonText << ryml::as_json(node); |
1731 | 0 | j.push_back(json::parse(jsonText.str())); |
1732 | 0 | } |
1733 | 0 | } else { |
1734 | 0 | std::ostringstream jsonText; |
1735 | 0 | jsonText << ryml::as_json(tree); |
1736 | 0 | j = json::parse(jsonText.str()); |
1737 | 0 | } |
1738 | 0 | } catch (const RapidYamlError& exc) { |
1739 | 0 | throw makeError(loc, exc.msg_); |
1740 | 0 | } |
1741 | 0 | bool filled_unused; |
1742 | 0 | otherJsonToHeap(j, filled_unused, scratch); |
1743 | 0 | return nullptr; |
1744 | 0 | } |
1745 | | |
1746 | 0 | const std::vector<std::string> split(const std::string& s, const std::string& delimiter) { |
1747 | 0 | size_t pos_start = 0, pos_end, delim_len = delimiter.length(); |
1748 | 0 | std::string token; |
1749 | 0 | std::vector<std::string> res; |
1750 | 0 |
|
1751 | 0 | while ((pos_end = s.find(delimiter, pos_start)) != std::string::npos) { |
1752 | 0 | token = s.substr(pos_start, pos_end - pos_start); |
1753 | 0 | pos_start = pos_end + delim_len; |
1754 | 0 | res.push_back(token); |
1755 | 0 | } |
1756 | 0 |
|
1757 | 0 | res.push_back(s.substr(pos_start)); |
1758 | 0 | return res; |
1759 | 0 | } |
1760 | | |
1761 | 0 | void otherJsonToHeap(const json &v, bool &filled, Value &attach) { |
1762 | | // In order to not anger the garbage collector, assign to attach immediately after |
1763 | | // making the heap object. |
1764 | 0 | switch (v.type()) { |
1765 | 0 | case json::value_t::string: |
1766 | 0 | attach = makeString(decode_utf8(v.get<std::string>())); |
1767 | 0 | filled = true; |
1768 | 0 | break; |
1769 | | |
1770 | 0 | case json::value_t::boolean: |
1771 | 0 | attach = makeBoolean(v.get<bool>()); |
1772 | 0 | filled = true; |
1773 | 0 | break; |
1774 | | |
1775 | 0 | case json::value_t::number_integer: |
1776 | 0 | case json::value_t::number_unsigned: |
1777 | 0 | case json::value_t::number_float: |
1778 | 0 | attach = makeNumber(v.get<double>()); |
1779 | 0 | filled = true; |
1780 | 0 | break; |
1781 | | |
1782 | 0 | case json::value_t::null: |
1783 | 0 | attach = makeNull(); |
1784 | 0 | filled = true; |
1785 | 0 | break; |
1786 | | |
1787 | 0 | case json::value_t::array:{ |
1788 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1789 | 0 | filled = true; |
1790 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1791 | 0 | for (size_t i = 0; i < v.size(); ++i) { |
1792 | 0 | arr->elements.push_back(makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1793 | 0 | otherJsonToHeap(v[i], arr->elements[i]->filled, arr->elements[i]->content); |
1794 | 0 | } |
1795 | 0 | } break; |
1796 | | |
1797 | 0 | case json::value_t::object: { |
1798 | 0 | attach = makeObject<HeapComprehensionObject>( |
1799 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1800 | 0 | filled = true; |
1801 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1802 | 0 | for (auto it = v.begin(); it != v.end(); ++it) { |
1803 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1804 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(it.key()))] = thunk; |
1805 | 0 | otherJsonToHeap(it.value(), thunk->filled, thunk->content); |
1806 | 0 | } |
1807 | 0 | } break; |
1808 | | |
1809 | 0 | case json::value_t::discarded: { |
1810 | 0 | abort(); |
1811 | 0 | } |
1812 | | |
1813 | 0 | default: { |
1814 | | // Newer nlohmann json.hpp (from v3.8.0 https://github.com/nlohmann/json/pull/1662) |
1815 | | // add a `value_t::binary` type, used when dealing with some JSON-adjacent binary |
1816 | | // formats (BSON, CBOR, etc). Since it doesn't exist prior to v3.8.0 we can't match |
1817 | | // on it explicitly, but we can just treat any unknown type as an error. |
1818 | 0 | abort(); |
1819 | 0 | } |
1820 | 0 | } |
1821 | 0 | } |
1822 | | |
1823 | | void joinString(bool &first, UString &running, const Value &sep, unsigned idx, const Value &elt) |
1824 | 0 | { |
1825 | 0 | if (elt.t == Value::NULL_TYPE) { |
1826 | 0 | return; |
1827 | 0 | } |
1828 | 0 | if (elt.t != Value::STRING) { |
1829 | 0 | std::stringstream ss; |
1830 | 0 | ss << "expected string but arr[" << idx << "] was " << type_str(elt); |
1831 | 0 | throw makeError(stack.top().location, ss.str()); |
1832 | 0 | } |
1833 | 0 | if (!first) { |
1834 | 0 | running.append(static_cast<HeapString *>(sep.v.h)->value); |
1835 | 0 | } |
1836 | 0 | first = false; |
1837 | 0 | running.append(static_cast<HeapString *>(elt.v.h)->value); |
1838 | 0 | } |
1839 | | |
1840 | | const AST *joinStrings(void) |
1841 | 0 | { |
1842 | 0 | Frame &f = stack.top(); |
1843 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1844 | 0 | while (f.elementId < elements.size()) { |
1845 | 0 | auto *th = elements[f.elementId]; |
1846 | 0 | if (th->filled) { |
1847 | 0 | joinString(f.first, f.str, f.val, f.elementId, th->content); |
1848 | 0 | f.elementId++; |
1849 | 0 | } else { |
1850 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1851 | 0 | return th->body; |
1852 | 0 | } |
1853 | 0 | } |
1854 | 0 | scratch = makeString(f.str); |
1855 | 0 | return nullptr; |
1856 | 0 | } |
1857 | | |
1858 | | void joinArray(bool &first, std::vector<HeapThunk*> &running, const Value &sep, unsigned idx, |
1859 | | const Value &elt) |
1860 | 0 | { |
1861 | 0 | if (elt.t == Value::NULL_TYPE) { |
1862 | 0 | return; |
1863 | 0 | } |
1864 | 0 | if (elt.t != Value::ARRAY) { |
1865 | 0 | std::stringstream ss; |
1866 | 0 | ss << "expected array but arr[" << idx << "] was " << type_str(elt); |
1867 | 0 | throw makeError(stack.top().location, ss.str()); |
1868 | 0 | } |
1869 | 0 | if (!first) { |
1870 | 0 | auto& elts = static_cast<HeapArray *>(sep.v.h)->elements; |
1871 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1872 | 0 | } |
1873 | 0 | first = false; |
1874 | 0 | auto& elts = static_cast<HeapArray *>(elt.v.h)->elements; |
1875 | 0 | running.insert(running.end(), elts.begin(), elts.end()); |
1876 | 0 | } |
1877 | | |
1878 | | const AST *joinArrays(void) |
1879 | 0 | { |
1880 | 0 | Frame &f = stack.top(); |
1881 | 0 | const auto& elements = static_cast<HeapArray*>(f.val2.v.h)->elements; |
1882 | 0 | while (f.elementId < elements.size()) { |
1883 | 0 | auto *th = elements[f.elementId]; |
1884 | 0 | if (th->filled) { |
1885 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, th->content); |
1886 | 0 | f.elementId++; |
1887 | 0 | } else { |
1888 | 0 | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
1889 | 0 | return th->body; |
1890 | 0 | } |
1891 | 0 | } |
1892 | 0 | scratch = makeArray(f.thunks); |
1893 | 0 | return nullptr; |
1894 | 0 | } |
1895 | | |
1896 | | const AST *builtinJoin(const LocationRange &loc, const std::vector<Value> &args) |
1897 | 0 | { |
1898 | 0 | if (args[0].t != Value::ARRAY && args[0].t != Value::STRING) { |
1899 | 0 | std::stringstream ss; |
1900 | 0 | ss << "join first parameter should be string or array, got " << type_str(args[0]); |
1901 | 0 | throw makeError(loc, ss.str()); |
1902 | 0 | } |
1903 | 0 | if (args[1].t != Value::ARRAY) { |
1904 | 0 | std::stringstream ss; |
1905 | 0 | ss << "join second parameter should be array, got " << type_str(args[1]); |
1906 | 0 | throw makeError(loc, ss.str()); |
1907 | 0 | } |
1908 | 0 | Frame &f = stack.top(); |
1909 | 0 | if (args[0].t == Value::STRING) { |
1910 | 0 | f.kind = FRAME_BUILTIN_JOIN_STRINGS; |
1911 | 0 | f.val = args[0]; // sep |
1912 | 0 | f.val2 = args[1]; // arr |
1913 | 0 | f.str.clear(); |
1914 | 0 | f.first = true; |
1915 | 0 | f.elementId = 0; |
1916 | 0 | return joinStrings(); |
1917 | 0 | } else { |
1918 | 0 | f.kind = FRAME_BUILTIN_JOIN_ARRAYS; |
1919 | 0 | f.val = args[0]; // sep |
1920 | 0 | f.val2 = args[1]; // arr |
1921 | 0 | f.thunks.clear(); |
1922 | 0 | f.first = true; |
1923 | 0 | f.elementId = 0; |
1924 | 0 | return joinArrays(); |
1925 | 0 | } |
1926 | 0 | } |
1927 | | |
1928 | | void jsonToHeap(const std::unique_ptr<JsonnetJsonValue> &v, bool &filled, Value &attach) |
1929 | 0 | { |
1930 | | // In order to not anger the garbage collector, assign to attach immediately after |
1931 | | // making the heap object. |
1932 | 0 | switch (v->kind) { |
1933 | 0 | case JsonnetJsonValue::STRING: |
1934 | 0 | attach = makeString(decode_utf8(v->string)); |
1935 | 0 | filled = true; |
1936 | 0 | break; |
1937 | | |
1938 | 0 | case JsonnetJsonValue::BOOL: |
1939 | 0 | attach = makeBoolean(v->number != 0.0); |
1940 | 0 | filled = true; |
1941 | 0 | break; |
1942 | | |
1943 | 0 | case JsonnetJsonValue::NUMBER: |
1944 | 0 | attach = makeNumber(v->number); |
1945 | 0 | filled = true; |
1946 | 0 | break; |
1947 | | |
1948 | 0 | case JsonnetJsonValue::NULL_KIND: |
1949 | 0 | attach = makeNull(); |
1950 | 0 | filled = true; |
1951 | 0 | break; |
1952 | | |
1953 | 0 | case JsonnetJsonValue::ARRAY: { |
1954 | 0 | attach = makeArray(std::vector<HeapThunk *>{}); |
1955 | 0 | filled = true; |
1956 | 0 | auto *arr = static_cast<HeapArray *>(attach.v.h); |
1957 | 0 | for (size_t i = 0; i < v->elements.size(); ++i) { |
1958 | 0 | arr->elements.push_back( |
1959 | 0 | makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr)); |
1960 | 0 | jsonToHeap(v->elements[i], arr->elements[i]->filled, arr->elements[i]->content); |
1961 | 0 | } |
1962 | 0 | } break; |
1963 | | |
1964 | 0 | case JsonnetJsonValue::OBJECT: { |
1965 | 0 | attach = makeObject<HeapComprehensionObject>( |
1966 | 0 | BindingFrame{}, jsonObjVar, idJsonObjVar, BindingFrame{}); |
1967 | 0 | filled = true; |
1968 | 0 | auto *obj = static_cast<HeapComprehensionObject *>(attach.v.h); |
1969 | 0 | for (const auto &pair : v->fields) { |
1970 | 0 | auto *thunk = makeHeap<HeapThunk>(idJsonObjVar, nullptr, 0, nullptr); |
1971 | 0 | obj->compValues[alloc->makeIdentifier(decode_utf8(pair.first))] = thunk; |
1972 | 0 | jsonToHeap(pair.second, thunk->filled, thunk->content); |
1973 | 0 | } |
1974 | 0 | } break; |
1975 | 0 | } |
1976 | 0 | } |
1977 | | |
1978 | | UString toString(const LocationRange &loc) |
1979 | 500k | { |
1980 | 500k | return manifestJson(loc, false, U""); |
1981 | 500k | } |
1982 | | |
1983 | | /** Recursively collect an object's invariants. |
1984 | | * |
1985 | | * \param curr |
1986 | | * \param self |
1987 | | * \param offset |
1988 | | * \param thunks |
1989 | | */ |
1990 | | void objectInvariants(HeapObject *curr, HeapObject *self, unsigned &counter, |
1991 | | std::vector<HeapThunk *> &thunks) |
1992 | 82.2M | { |
1993 | 82.2M | if (auto *ext = dynamic_cast<HeapExtendedObject *>(curr)) { |
1994 | 33.0M | objectInvariants(ext->right, self, counter, thunks); |
1995 | 33.0M | objectInvariants(ext->left, self, counter, thunks); |
1996 | 49.1M | } else if (auto *ext = dynamic_cast<HeapRestrictedObject *>(curr)) { |
1997 | 0 | objectInvariants(ext->obj, self, counter, thunks); |
1998 | 49.1M | } else { |
1999 | 49.1M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(curr)) { |
2000 | 49.1M | for (AST *assert : simp->asserts) { |
2001 | 1.14M | auto *el_th = makeHeap<HeapThunk>(idInvariant, self, counter, assert); |
2002 | 1.14M | el_th->upValues = simp->upValues; |
2003 | 1.14M | thunks.push_back(el_th); |
2004 | 1.14M | } |
2005 | 49.1M | } |
2006 | 49.1M | counter++; |
2007 | 49.1M | } |
2008 | 82.2M | } |
2009 | | |
2010 | | /** Index an object's field. |
2011 | | * |
2012 | | * \param loc Location where the e.f occurred. |
2013 | | * \param obj The target |
2014 | | * \param f The field |
2015 | | */ |
2016 | | const AST *objectIndex(const LocationRange &loc, HeapObject *obj, const Identifier *f, |
2017 | | unsigned offset) |
2018 | 20.1M | { |
2019 | 20.1M | unsigned found_at = 0; |
2020 | 20.1M | HeapObject *self = obj; |
2021 | 20.1M | HeapLeafObject *found = findObject(f, obj, offset, found_at); |
2022 | 20.1M | if (found == nullptr) { |
2023 | 42 | throw makeError(loc, "field does not exist: " + encode_utf8(f->name)); |
2024 | 42 | } |
2025 | 20.1M | if (auto *simp = dynamic_cast<HeapSimpleObject *>(found)) { |
2026 | 20.1M | auto it = simp->fields.find(f); |
2027 | 20.1M | const AST *body = it->second.body; |
2028 | | |
2029 | 20.1M | stack.newCall(loc, simp, self, found_at, simp->upValues); |
2030 | 20.1M | return body; |
2031 | 20.1M | } else { |
2032 | | // If a HeapLeafObject is not HeapSimpleObject, it must be HeapComprehensionObject. |
2033 | 0 | auto *comp = static_cast<HeapComprehensionObject *>(found); |
2034 | 0 | auto it = comp->compValues.find(f); |
2035 | 0 | auto *th = it->second; |
2036 | 0 | BindingFrame binds = comp->upValues; |
2037 | 0 | binds[comp->id] = th; |
2038 | 0 | stack.newCall(loc, comp, self, found_at, binds); |
2039 | 0 | return comp->value; |
2040 | 0 | } |
2041 | 20.1M | } |
2042 | | |
2043 | | void runInvariants(const LocationRange &loc, HeapObject *self) |
2044 | 1.16M | { |
2045 | 1.16M | if (stack.alreadyExecutingInvariants(self)) |
2046 | 2.29k | return; |
2047 | | |
2048 | 1.16M | unsigned counter = 0; |
2049 | 1.16M | unsigned initial_stack_size = stack.size(); |
2050 | 1.16M | stack.newFrame(FRAME_INVARIANTS, loc); |
2051 | 1.16M | std::vector<HeapThunk *> &thunks = stack.top().thunks; |
2052 | 1.16M | objectInvariants(self, self, counter, thunks); |
2053 | 1.16M | if (thunks.size() == 0) { |
2054 | 1.14M | stack.pop(); |
2055 | 1.14M | return; |
2056 | 1.14M | } |
2057 | 22.6k | HeapThunk *thunk = thunks[0]; |
2058 | 22.6k | stack.top().elementId = 1; |
2059 | 22.6k | stack.top().self = self; |
2060 | 22.6k | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
2061 | 22.6k | evaluate(thunk->body, initial_stack_size); |
2062 | 22.6k | } |
2063 | | |
2064 | | /** Call a sourceVal function with given arguments. |
2065 | | * |
2066 | | * This function requires all arguments to be positional. It also does not |
2067 | | * support default arguments. This is intended to be used internally so, |
2068 | | * error checking is also skipped. |
2069 | | */ |
2070 | 28.3k | const AST *callSourceVal(const AST *ast, HeapThunk *sourceVal, std::vector<HeapThunk*> args) { |
2071 | 28.3k | assert(sourceVal != nullptr); |
2072 | 28.3k | assert(sourceVal->filled); |
2073 | 28.3k | assert(sourceVal->content.t == Value::FUNCTION); |
2074 | 28.3k | auto *func = static_cast<HeapClosure *>(sourceVal->content.v.h); |
2075 | 28.3k | BindingFrame up_values = func->upValues; |
2076 | 84.9k | for (size_t i = 0; i < args.size(); ++i) { |
2077 | 56.6k | up_values.insert({func->params[i].id, args[i]}); |
2078 | 56.6k | } |
2079 | 28.3k | stack.newCall(ast->location, func, func->self, func->offset, up_values); |
2080 | 28.3k | return func->body; |
2081 | 28.3k | } |
2082 | | |
2083 | | /** Evaluate the given AST to a value. |
2084 | | * |
2085 | | * Rather than call itself recursively, this function maintains a separate stack of |
2086 | | * partially-evaluated constructs. First, the AST is handled depending on its type. If |
2087 | | * this cannot be completed without evaluating another AST (e.g. a sub expression) then a |
2088 | | * frame is pushed onto the stack containing the partial state, and the code jumps back to |
2089 | | * the beginning of this function. Once there are no more ASTs to evaluate, the code |
2090 | | * executes the second part of the function to unwind the stack. If the stack cannot be |
2091 | | * completely unwound without evaluating an AST then it jumps back to the beginning of the |
2092 | | * function again. The process terminates when the AST has been processed and the stack is |
2093 | | * the same size it was at the beginning of the call to evaluate. |
2094 | | */ |
2095 | | void evaluate(const AST *ast_, unsigned initial_stack_size) |
2096 | 9.80M | { |
2097 | 188M | recurse: |
2098 | | |
2099 | 188M | switch (ast_->type) { |
2100 | 14.9M | case AST_APPLY: { |
2101 | 14.9M | const auto &ast = *static_cast<const Apply *>(ast_); |
2102 | 14.9M | stack.newFrame(FRAME_APPLY_TARGET, ast_); |
2103 | 14.9M | ast_ = ast.target; |
2104 | 14.9M | goto recurse; |
2105 | 0 | } break; |
2106 | | |
2107 | 1.91M | case AST_ARRAY: { |
2108 | 1.91M | const auto &ast = *static_cast<const Array *>(ast_); |
2109 | 1.91M | HeapObject *self; |
2110 | 1.91M | unsigned offset; |
2111 | 1.91M | stack.getSelfBinding(self, offset); |
2112 | 1.91M | scratch = makeArray({}); |
2113 | 1.91M | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
2114 | 15.8M | for (const auto &el : ast.elements) { |
2115 | 15.8M | auto *el_th = makeHeap<HeapThunk>(idArrayElement, self, offset, el.expr); |
2116 | 15.8M | el_th->upValues = capture(el.expr->freeVariables); |
2117 | 15.8M | elements.push_back(el_th); |
2118 | 15.8M | } |
2119 | 1.91M | } break; |
2120 | | |
2121 | 8.36M | case AST_BINARY: { |
2122 | 8.36M | const auto &ast = *static_cast<const Binary *>(ast_); |
2123 | 8.36M | stack.newFrame(FRAME_BINARY_LEFT, ast_); |
2124 | 8.36M | ast_ = ast.left; |
2125 | 8.36M | goto recurse; |
2126 | 0 | } break; |
2127 | | |
2128 | 11.6M | case AST_BUILTIN_FUNCTION: { |
2129 | 11.6M | const auto &ast = *static_cast<const BuiltinFunction *>(ast_); |
2130 | 11.6M | scratch = makeBuiltinFromAST(ast.body); |
2131 | 11.6M | } break; |
2132 | | |
2133 | 0 | case AST_BUILTIN_FUNCTION_BODY: { |
2134 | | // We can reach here if we end up evaluating a HeapThunk that was constructed |
2135 | | // from a built-in function. Synthesize a FRAME_BUILTIN_FORCE_THUNKS to call it. |
2136 | 0 | assert(stack.top().isCall()); |
2137 | 0 | const auto fbody = static_cast<const BuiltinFunctionBody*>(ast_); |
2138 | |
|
2139 | 0 | const BindingFrame up_values = stack.top().bindings; |
2140 | 0 | std::vector<HeapThunk*> arg_thunks; |
2141 | 0 | for (const auto &p : fbody->params) { |
2142 | 0 | const auto it = up_values.find(p); |
2143 | 0 | if (it != up_values.end()) { |
2144 | 0 | arg_thunks.push_back(it->second); |
2145 | 0 | } else { |
2146 | 0 | JSONNET_UNREACHABLE(); |
2147 | 0 | } |
2148 | 0 | } |
2149 | 0 | const LocationRange &loc = stack.top().location; |
2150 | |
|
2151 | 0 | stack.newFrame(FRAME_BUILTIN_FORCE_THUNKS, loc); |
2152 | 0 | stack.top().bindings = up_values; |
2153 | 0 | stack.top().thunks = arg_thunks; |
2154 | 0 | stack.top().val = makeBuiltinFromAST(fbody); |
2155 | 0 | } break; |
2156 | | |
2157 | 11.3M | case AST_CONDITIONAL: { |
2158 | 11.3M | const auto &ast = *static_cast<const Conditional *>(ast_); |
2159 | 11.3M | stack.newFrame(FRAME_IF, ast_); |
2160 | 11.3M | ast_ = ast.cond; |
2161 | 11.3M | goto recurse; |
2162 | 0 | } break; |
2163 | | |
2164 | 8.44k | case AST_ERROR: { |
2165 | 8.44k | const auto &ast = *static_cast<const Error *>(ast_); |
2166 | 8.44k | stack.newFrame(FRAME_ERROR, ast_); |
2167 | 8.44k | ast_ = ast.expr; |
2168 | 8.44k | goto recurse; |
2169 | 0 | } break; |
2170 | | |
2171 | 2.80M | case AST_FUNCTION: { |
2172 | 2.80M | const auto &ast = *static_cast<const Function *>(ast_); |
2173 | 2.80M | auto env = capture(ast.freeVariables); |
2174 | 2.80M | HeapObject *self; |
2175 | 2.80M | unsigned offset; |
2176 | 2.80M | stack.getSelfBinding(self, offset); |
2177 | 2.80M | HeapClosure::Params params; |
2178 | 2.80M | params.reserve(ast.params.size()); |
2179 | 5.32M | for (const auto &p : ast.params) { |
2180 | 5.32M | params.emplace_back(p.id, p.expr); |
2181 | 5.32M | } |
2182 | 2.80M | scratch = makeClosure(env, self, offset, params, ast.body); |
2183 | 2.80M | } break; |
2184 | | |
2185 | 45 | case AST_IMPORT: { |
2186 | 45 | const auto &ast = *static_cast<const Import *>(ast_); |
2187 | 45 | HeapThunk *thunk = import(ast.location, ast.file); |
2188 | 45 | if (thunk->filled) { |
2189 | 0 | scratch = thunk->content; |
2190 | 45 | } else { |
2191 | 45 | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2192 | 45 | ast_ = thunk->body; |
2193 | 45 | goto recurse; |
2194 | 45 | } |
2195 | 45 | } break; |
2196 | | |
2197 | 20 | case AST_IMPORTSTR: { |
2198 | 20 | const auto &ast = *static_cast<const Importstr *>(ast_); |
2199 | 20 | const ImportCacheValue *value = importData(ast.location, ast.file); |
2200 | 20 | scratch = makeString(decode_utf8(value->content)); |
2201 | 20 | } break; |
2202 | | |
2203 | 9 | case AST_IMPORTBIN: { |
2204 | 9 | const auto &ast = *static_cast<const Importbin *>(ast_); |
2205 | 9 | const ImportCacheValue *value = importData(ast.location, ast.file); |
2206 | 9 | scratch = makeArray({}); |
2207 | 9 | auto &elements = static_cast<HeapArray *>(scratch.v.h)->elements; |
2208 | 9 | elements.reserve(value->content.size()); |
2209 | 9 | for (const auto c : value->content) { |
2210 | 0 | auto *th = makeHeap<HeapThunk>(idArrayElement, nullptr, 0, nullptr); |
2211 | 0 | elements.push_back(th); |
2212 | 0 | th->fill(makeNumber(uint8_t(c))); |
2213 | 0 | } |
2214 | 9 | } break; |
2215 | | |
2216 | 4.13M | case AST_IN_SUPER: { |
2217 | 4.13M | const auto &ast = *static_cast<const InSuper *>(ast_); |
2218 | 4.13M | stack.newFrame(FRAME_IN_SUPER_ELEMENT, ast_); |
2219 | 4.13M | ast_ = ast.element; |
2220 | 4.13M | goto recurse; |
2221 | 45 | } break; |
2222 | | |
2223 | 15.6M | case AST_INDEX: { |
2224 | 15.6M | const auto &ast = *static_cast<const Index *>(ast_); |
2225 | 15.6M | stack.newFrame(FRAME_INDEX_TARGET, ast_); |
2226 | 15.6M | ast_ = ast.target; |
2227 | 15.6M | goto recurse; |
2228 | 45 | } break; |
2229 | | |
2230 | 7.90M | case AST_LOCAL: { |
2231 | 7.90M | const auto &ast = *static_cast<const Local *>(ast_); |
2232 | 7.90M | stack.newFrame(FRAME_LOCAL, ast_); |
2233 | 7.90M | Frame &f = stack.top(); |
2234 | | // First build all the thunks and bind them. |
2235 | 7.90M | HeapObject *self; |
2236 | 7.90M | unsigned offset; |
2237 | 7.90M | stack.getSelfBinding(self, offset); |
2238 | 25.6M | for (const auto &bind : ast.binds) { |
2239 | | // Note that these 2 lines must remain separate to avoid the GC running |
2240 | | // when bindings has a nullptr for key bind.first. |
2241 | 25.6M | auto *th = makeHeap<HeapThunk>(bind.var, self, offset, bind.body); |
2242 | 25.6M | f.bindings[bind.var] = th; |
2243 | 25.6M | } |
2244 | | // Now capture the environment (including the new thunks, to make cycles). |
2245 | 25.6M | for (const auto &bind : ast.binds) { |
2246 | 25.6M | auto *thunk = f.bindings[bind.var]; |
2247 | 25.6M | thunk->upValues = capture(bind.body->freeVariables); |
2248 | 25.6M | } |
2249 | 7.90M | ast_ = ast.body; |
2250 | 7.90M | goto recurse; |
2251 | 45 | } break; |
2252 | | |
2253 | 781k | case AST_LITERAL_BOOLEAN: { |
2254 | 781k | const auto &ast = *static_cast<const LiteralBoolean *>(ast_); |
2255 | 781k | scratch = makeBoolean(ast.value); |
2256 | 781k | } break; |
2257 | | |
2258 | 9.50M | case AST_LITERAL_NUMBER: { |
2259 | 9.50M | const auto &ast = *static_cast<const LiteralNumber *>(ast_); |
2260 | 9.50M | scratch = makeNumberCheck(ast_->location, ast.value); |
2261 | 9.50M | } break; |
2262 | | |
2263 | 47.3M | case AST_LITERAL_STRING: { |
2264 | 47.3M | const auto &ast = *static_cast<const LiteralString *>(ast_); |
2265 | 47.3M | scratch = makeString(ast.value); |
2266 | 47.3M | } break; |
2267 | | |
2268 | 199k | case AST_LITERAL_NULL: { |
2269 | 199k | scratch = makeNull(); |
2270 | 199k | } break; |
2271 | | |
2272 | 3.76M | case AST_DESUGARED_OBJECT: { |
2273 | 3.76M | const auto &ast = *static_cast<const DesugaredObject *>(ast_); |
2274 | 3.76M | if (ast.fields.empty()) { |
2275 | 2.36M | auto env = capture(ast.freeVariables); |
2276 | 2.36M | std::map<const Identifier *, HeapSimpleObject::Field> fields; |
2277 | 2.36M | scratch = makeObject<HeapSimpleObject>(env, fields, ast.asserts); |
2278 | 2.36M | } else { |
2279 | 1.40M | auto env = capture(ast.freeVariables); |
2280 | 1.40M | stack.newFrame(FRAME_OBJECT, ast_); |
2281 | 1.40M | auto fit = ast.fields.begin(); |
2282 | 1.40M | stack.top().fit = fit; |
2283 | 1.40M | ast_ = fit->name; |
2284 | 1.40M | goto recurse; |
2285 | 1.40M | } |
2286 | 3.76M | } break; |
2287 | | |
2288 | 2.36M | case AST_OBJECT_COMPREHENSION_SIMPLE: { |
2289 | 112 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(ast_); |
2290 | 112 | stack.newFrame(FRAME_OBJECT_COMP_ARRAY, ast_); |
2291 | 112 | ast_ = ast.array; |
2292 | 112 | goto recurse; |
2293 | 3.76M | } break; |
2294 | | |
2295 | 2.84M | case AST_SELF: { |
2296 | 2.84M | scratch.t = Value::OBJECT; |
2297 | 2.84M | HeapObject *self; |
2298 | 2.84M | unsigned offset; |
2299 | 2.84M | stack.getSelfBinding(self, offset); |
2300 | 2.84M | scratch.v.h = self; |
2301 | 2.84M | } break; |
2302 | | |
2303 | 3.70M | case AST_SUPER_INDEX: { |
2304 | 3.70M | const auto &ast = *static_cast<const SuperIndex *>(ast_); |
2305 | 3.70M | stack.newFrame(FRAME_SUPER_INDEX, ast_); |
2306 | 3.70M | ast_ = ast.index; |
2307 | 3.70M | goto recurse; |
2308 | 3.76M | } break; |
2309 | | |
2310 | 4.11M | case AST_UNARY: { |
2311 | 4.11M | const auto &ast = *static_cast<const Unary *>(ast_); |
2312 | 4.11M | stack.newFrame(FRAME_UNARY, ast_); |
2313 | 4.11M | ast_ = ast.expr; |
2314 | 4.11M | goto recurse; |
2315 | 3.76M | } break; |
2316 | | |
2317 | 37.5M | case AST_VAR: { |
2318 | 37.5M | const auto &ast = *static_cast<const Var *>(ast_); |
2319 | 37.5M | auto *thunk = stack.lookUpVar(ast.id); |
2320 | 37.5M | if (thunk == nullptr) { |
2321 | 0 | std::cerr << "INTERNAL ERROR: Could not bind variable: " |
2322 | 0 | << encode_utf8(ast.id->name) << " at " |
2323 | 0 | << ast.location << std::endl; |
2324 | 0 | std::abort(); |
2325 | 0 | } |
2326 | 37.5M | if (thunk->filled) { |
2327 | 28.3M | scratch = thunk->content; |
2328 | 28.3M | } else { |
2329 | 9.15M | stack.newCall(ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
2330 | 9.15M | ast_ = thunk->body; |
2331 | 9.15M | goto recurse; |
2332 | 9.15M | } |
2333 | 37.5M | } break; |
2334 | | |
2335 | 28.3M | default: |
2336 | 0 | std::cerr << "INTERNAL ERROR: Unknown AST: " << ast_->type << std::endl; |
2337 | 0 | std::abort(); |
2338 | 188M | } |
2339 | | |
2340 | | // To evaluate another AST, set ast to it, then goto recurse. |
2341 | | // To pop, exit the switch or goto popframe |
2342 | | // To change the frame and re-enter the switch, goto replaceframe |
2343 | 213M | while (stack.size() > initial_stack_size) { |
2344 | 203M | Frame &f = stack.top(); |
2345 | 203M | switch (f.kind) { |
2346 | 14.9M | case FRAME_APPLY_TARGET: { |
2347 | 14.9M | const auto &ast = *static_cast<const Apply *>(f.ast); |
2348 | 14.9M | if (scratch.t != Value::FUNCTION) { |
2349 | 21 | throw makeError(ast.location, |
2350 | 21 | "only functions can be called, got " + type_str(scratch)); |
2351 | 21 | } |
2352 | 14.9M | auto *func = static_cast<HeapClosure *>(scratch.v.h); |
2353 | | |
2354 | 14.9M | std::set<const Identifier *> params_needed; |
2355 | 25.2M | for (const auto ¶m : func->params) { |
2356 | 25.2M | params_needed.insert(param.id); |
2357 | 25.2M | } |
2358 | | |
2359 | | // Create thunks for arguments. |
2360 | 14.9M | std::vector<HeapThunk *> positional_args; |
2361 | 14.9M | BindingFrame args; |
2362 | 14.9M | bool got_named = false; |
2363 | 40.2M | for (std::size_t i = 0; i < ast.args.size(); ++i) { |
2364 | 25.2M | const auto &arg = ast.args[i]; |
2365 | | |
2366 | 25.2M | const Identifier *name; |
2367 | 25.2M | if (arg.id != nullptr) { |
2368 | 0 | got_named = true; |
2369 | 0 | name = arg.id; |
2370 | 25.2M | } else { |
2371 | 25.2M | if (got_named) { |
2372 | 0 | std::stringstream ss; |
2373 | 0 | ss << "internal error: got positional param after named at index " |
2374 | 0 | << i; |
2375 | 0 | throw makeError(ast.location, ss.str()); |
2376 | 0 | } |
2377 | 25.2M | if (i >= func->params.size()) { |
2378 | 0 | std::stringstream ss; |
2379 | 0 | ss << "too many args, function has " << func->params.size() |
2380 | 0 | << " parameter(s)"; |
2381 | 0 | throw makeError(ast.location, ss.str()); |
2382 | 0 | } |
2383 | 25.2M | name = func->params[i].id; |
2384 | 25.2M | } |
2385 | | // Special case for builtin functions -- leave identifier blank for |
2386 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2387 | 25.2M | const Identifier *name_ = func->isBuiltin() ? nullptr : name; |
2388 | 25.2M | HeapObject *self; |
2389 | 25.2M | unsigned offset; |
2390 | 25.2M | stack.getSelfBinding(self, offset); |
2391 | 25.2M | auto *thunk = makeHeap<HeapThunk>(name_, self, offset, arg.expr); |
2392 | 25.2M | thunk->upValues = capture(arg.expr->freeVariables); |
2393 | | // While making the thunks, keep them in a frame to avoid premature garbage |
2394 | | // collection. |
2395 | 25.2M | f.thunks.push_back(thunk); |
2396 | 25.2M | if (args.find(name) != args.end()) { |
2397 | 0 | std::stringstream ss; |
2398 | 0 | ss << "binding parameter a second time: " << encode_utf8(name->name); |
2399 | 0 | throw makeError(ast.location, ss.str()); |
2400 | 0 | } |
2401 | 25.2M | args[name] = thunk; |
2402 | 25.2M | if (params_needed.find(name) == params_needed.end()) { |
2403 | 0 | std::stringstream ss; |
2404 | 0 | ss << "function has no parameter " << encode_utf8(name->name); |
2405 | 0 | throw makeError(ast.location, ss.str()); |
2406 | 0 | } |
2407 | 25.2M | } |
2408 | | |
2409 | | // For any func params for which there was no arg, create a thunk for those and |
2410 | | // bind the default argument. Allow default thunks to see other params. If no |
2411 | | // default argument than raise an error. |
2412 | | |
2413 | | // Raise errors for unbound params, create thunks (but don't fill in upvalues). |
2414 | | // This is a subset of f.thunks, so will not get garbage collected. |
2415 | 14.9M | std::vector<HeapThunk *> def_arg_thunks; |
2416 | 25.2M | for (const auto ¶m : func->params) { |
2417 | 25.2M | if (args.find(param.id) != args.end()) |
2418 | 25.2M | continue; |
2419 | 131 | if (param.def == nullptr) { |
2420 | 16 | std::stringstream ss; |
2421 | 16 | ss << "function parameter " << encode_utf8(param.id->name) |
2422 | 16 | << " not bound in call."; |
2423 | 16 | throw makeError(ast.location, ss.str()); |
2424 | 16 | } |
2425 | | |
2426 | | // Special case for builtin functions -- leave identifier blank for |
2427 | | // them in the thunk. This removes the thunk frame from the stacktrace. |
2428 | 115 | const Identifier *name_ = func->isBuiltin() ? nullptr : param.id; |
2429 | 115 | auto *thunk = |
2430 | 115 | makeHeap<HeapThunk>(name_, func->self, func->offset, param.def); |
2431 | 115 | f.thunks.push_back(thunk); |
2432 | 115 | def_arg_thunks.push_back(thunk); |
2433 | 115 | args[param.id] = thunk; |
2434 | 115 | } |
2435 | | |
2436 | 14.9M | BindingFrame up_values = func->upValues; |
2437 | 14.9M | up_values.insert(args.begin(), args.end()); |
2438 | | |
2439 | | // Fill in upvalues |
2440 | 14.9M | for (HeapThunk *thunk : def_arg_thunks) { |
2441 | 88 | thunk->upValues = up_values; |
2442 | 88 | } |
2443 | | |
2444 | | // Cache these, because pop will invalidate them. |
2445 | 14.9M | std::vector<HeapThunk *> thunks_copy = f.thunks; |
2446 | | |
2447 | 14.9M | const AST *f_ast = f.ast; |
2448 | 14.9M | stack.pop(); |
2449 | | |
2450 | 14.9M | if (func->isBuiltin()) { |
2451 | | // Built-in function. |
2452 | | // Give nullptr for self because no one looking at this frame will |
2453 | | // attempt to bind to self (it's native code). |
2454 | 11.6M | stack.newFrame(FRAME_BUILTIN_FORCE_THUNKS, f_ast); |
2455 | 11.6M | assert(func->upValues.empty()); |
2456 | 11.6M | stack.top().bindings = up_values; |
2457 | 11.6M | stack.top().thunks = thunks_copy; |
2458 | 11.6M | stack.top().val = scratch; |
2459 | 11.6M | goto replaceframe; |
2460 | 11.6M | } else { |
2461 | | // User defined function. |
2462 | 3.29M | stack.newCall(ast.location, func, func->self, func->offset, up_values); |
2463 | 3.29M | if (ast.tailstrict) { |
2464 | 2.24M | stack.top().tailCall = true; |
2465 | 2.24M | if (thunks_copy.size() == 0) { |
2466 | | // No need to force thunks, proceed straight to body. |
2467 | 0 | ast_ = func->body; |
2468 | 0 | goto recurse; |
2469 | 2.24M | } else { |
2470 | | // The check for args.size() > 0 |
2471 | 2.24M | stack.top().thunks = thunks_copy; |
2472 | 2.24M | stack.top().val = scratch; |
2473 | 2.24M | goto replaceframe; |
2474 | 2.24M | } |
2475 | 2.24M | } else { |
2476 | 1.05M | ast_ = func->body; |
2477 | 1.05M | goto recurse; |
2478 | 1.05M | } |
2479 | 3.29M | } |
2480 | 14.9M | } break; |
2481 | | |
2482 | 8.17M | case FRAME_BINARY_LEFT: { |
2483 | 8.17M | const auto &ast = *static_cast<const Binary *>(f.ast); |
2484 | 8.17M | const Value &lhs = scratch; |
2485 | 8.17M | if (lhs.t == Value::BOOLEAN) { |
2486 | | // Handle short-cut semantics |
2487 | 275k | switch (ast.op) { |
2488 | 88.6k | case BOP_AND: { |
2489 | 88.6k | if (!lhs.v.b) { |
2490 | 10.4k | scratch = makeBoolean(false); |
2491 | 10.4k | goto popframe; |
2492 | 10.4k | } |
2493 | 88.6k | } break; |
2494 | | |
2495 | 183k | case BOP_OR: { |
2496 | 183k | if (lhs.v.b) { |
2497 | 3.98k | scratch = makeBoolean(true); |
2498 | 3.98k | goto popframe; |
2499 | 3.98k | } |
2500 | 183k | } break; |
2501 | | |
2502 | 179k | default:; |
2503 | 275k | } |
2504 | 275k | } |
2505 | 8.15M | stack.top().kind = FRAME_BINARY_RIGHT; |
2506 | 8.15M | stack.top().val = lhs; |
2507 | 8.15M | ast_ = ast.right; |
2508 | 8.15M | goto recurse; |
2509 | 8.17M | } break; |
2510 | | |
2511 | 8.14M | case FRAME_BINARY_RIGHT: { |
2512 | 8.14M | stack.top().val2 = scratch; |
2513 | 8.14M | stack.top().kind = FRAME_BINARY_OP; |
2514 | 8.14M | } |
2515 | 8.14M | [[fallthrough]]; |
2516 | 8.14M | case FRAME_BINARY_OP: { |
2517 | 8.14M | const auto &ast = *static_cast<const Binary *>(f.ast); |
2518 | 8.14M | const Value &lhs = stack.top().val; |
2519 | 8.14M | const Value &rhs = stack.top().val2; |
2520 | | |
2521 | | // Handle cases where the LHS and RHS are not the same type. |
2522 | 8.14M | if (lhs.t == Value::STRING || rhs.t == Value::STRING) { |
2523 | 2.19M | if (ast.op == BOP_PLUS) { |
2524 | | // Handle co-ercions for string processing. |
2525 | 2.03M | stack.top().kind = FRAME_STRING_CONCAT; |
2526 | 2.03M | stack.top().val2 = rhs; |
2527 | 2.03M | goto replaceframe; |
2528 | 2.03M | } |
2529 | 2.19M | } |
2530 | 6.11M | switch (ast.op) { |
2531 | | // Equality can be used when the types don't match. |
2532 | 0 | case BOP_MANIFEST_EQUAL: |
2533 | 0 | std::cerr << "INTERNAL ERROR: Equals not desugared" << std::endl; |
2534 | 0 | abort(); |
2535 | | |
2536 | | // Equality can be used when the types don't match. |
2537 | 0 | case BOP_MANIFEST_UNEQUAL: |
2538 | 0 | std::cerr << "INTERNAL ERROR: Notequals not desugared" << std::endl; |
2539 | 0 | abort(); |
2540 | | |
2541 | | // e in e |
2542 | 8.39k | case BOP_IN: { |
2543 | 8.39k | if (lhs.t != Value::STRING) { |
2544 | 6 | throw makeError(ast.location, |
2545 | 6 | "the left hand side of the 'in' operator should be " |
2546 | 6 | "a string, got " + |
2547 | 6 | type_str(lhs)); |
2548 | 6 | } |
2549 | 8.39k | auto *field = static_cast<HeapString *>(lhs.v.h); |
2550 | 8.39k | switch (rhs.t) { |
2551 | 8.38k | case Value::OBJECT: { |
2552 | 8.38k | auto *obj = static_cast<HeapObject *>(rhs.v.h); |
2553 | 8.38k | auto *fid = alloc->makeIdentifier(field->value); |
2554 | 8.38k | unsigned unused_found_at = 0; |
2555 | 8.38k | bool in = findObject(fid, obj, 0, unused_found_at); |
2556 | 8.38k | scratch = makeBoolean(in); |
2557 | 8.38k | } break; |
2558 | | |
2559 | 4 | default: |
2560 | 4 | throw makeError( |
2561 | 4 | ast.location, |
2562 | 4 | "the right hand side of the 'in' operator should be" |
2563 | 4 | " an object, got " + |
2564 | 4 | type_str(rhs)); |
2565 | 8.39k | } |
2566 | 8.38k | goto popframe; |
2567 | 8.39k | } |
2568 | | |
2569 | 6.10M | default:; |
2570 | 6.11M | } |
2571 | | // Everything else requires matching types. |
2572 | 6.10M | if (lhs.t != rhs.t) { |
2573 | 185 | throw makeError(ast.location, |
2574 | 185 | "binary operator " + bop_string(ast.op) + |
2575 | 185 | " requires " |
2576 | 185 | "matching types, got " + |
2577 | 185 | type_str(lhs) + " and " + type_str(rhs) + "."); |
2578 | 185 | } |
2579 | 6.10M | switch (lhs.t) { |
2580 | 1.40M | case Value::ARRAY: |
2581 | 1.40M | if (ast.op == BOP_PLUS) { |
2582 | 1.38M | auto *arr_l = static_cast<HeapArray *>(lhs.v.h); |
2583 | 1.38M | auto *arr_r = static_cast<HeapArray *>(rhs.v.h); |
2584 | 1.38M | std::vector<HeapThunk *> elements; |
2585 | 1.38M | for (auto *el : arr_l->elements) |
2586 | 84.5M | elements.push_back(el); |
2587 | 1.38M | for (auto *el : arr_r->elements) |
2588 | 2.71M | elements.push_back(el); |
2589 | 1.38M | scratch = makeArray(elements); |
2590 | 1.38M | } else if (ast.op == BOP_LESS || ast.op == BOP_LESS_EQ || ast.op == BOP_GREATER || ast.op == BOP_GREATER_EQ) { |
2591 | 28.6k | HeapThunk *func; |
2592 | 28.6k | switch(ast.op) { |
2593 | 9.90k | case BOP_LESS: |
2594 | 9.90k | func = sourceVals["__array_less"]; |
2595 | 9.90k | break; |
2596 | 8.40k | case BOP_LESS_EQ: |
2597 | 8.40k | func = sourceVals["__array_less_or_equal"]; |
2598 | 8.40k | break; |
2599 | 3.28k | case BOP_GREATER: |
2600 | 3.28k | func = sourceVals["__array_greater"]; |
2601 | 3.28k | break; |
2602 | 7.03k | case BOP_GREATER_EQ: |
2603 | 7.03k | func = sourceVals["__array_greater_or_equal"]; |
2604 | 7.03k | break; |
2605 | 0 | default: |
2606 | 0 | JSONNET_UNREACHABLE(); |
2607 | 28.6k | } |
2608 | 28.6k | if (!func->filled) { |
2609 | 300 | stack.newCall(ast.location, func, func->self, func->offset, func->upValues); |
2610 | 300 | ast_ = func->body; |
2611 | 300 | goto recurse; |
2612 | 300 | } |
2613 | 28.3k | auto *lhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.left); |
2614 | 28.3k | lhs_th->fill(lhs); |
2615 | 28.3k | f.thunks.push_back(lhs_th); |
2616 | 28.3k | auto *rhs_th = makeHeap<HeapThunk>(idInternal, f.self, f.offset, ast.right); |
2617 | 28.3k | rhs_th->fill(rhs); |
2618 | 28.3k | f.thunks.push_back(rhs_th); |
2619 | 28.3k | const AST *orig_ast = ast_; |
2620 | 28.3k | stack.pop(); |
2621 | 28.3k | ast_ = callSourceVal(orig_ast, func, {lhs_th, rhs_th}); |
2622 | 28.3k | goto recurse; |
2623 | 28.6k | } else { |
2624 | 12 | throw makeError(ast.location, |
2625 | 12 | "binary operator " + bop_string(ast.op) + |
2626 | 12 | " does not operate on arrays."); |
2627 | 12 | } |
2628 | 1.38M | break; |
2629 | | |
2630 | 1.38M | case Value::BOOLEAN: |
2631 | 258k | switch (ast.op) { |
2632 | 78.1k | case BOP_AND: scratch = makeBoolean(lhs.v.b && rhs.v.b); break; |
2633 | | |
2634 | 179k | case BOP_OR: scratch = makeBoolean(lhs.v.b || rhs.v.b); break; |
2635 | | |
2636 | 45 | default: |
2637 | 45 | throw makeError(ast.location, |
2638 | 45 | "binary operator " + bop_string(ast.op) + |
2639 | 45 | " does not operate on booleans."); |
2640 | 258k | } |
2641 | 258k | break; |
2642 | | |
2643 | 1.59M | case Value::NUMBER: |
2644 | 1.59M | switch (ast.op) { |
2645 | 436k | case BOP_PLUS: |
2646 | 436k | scratch = makeNumberCheck(ast.location, lhs.v.d + rhs.v.d); |
2647 | 436k | break; |
2648 | | |
2649 | 124k | case BOP_MINUS: |
2650 | 124k | scratch = makeNumberCheck(ast.location, lhs.v.d - rhs.v.d); |
2651 | 124k | break; |
2652 | | |
2653 | 6.09k | case BOP_MULT: |
2654 | 6.09k | scratch = makeNumberCheck(ast.location, lhs.v.d * rhs.v.d); |
2655 | 6.09k | break; |
2656 | | |
2657 | 62.4k | case BOP_DIV: |
2658 | 62.4k | if (rhs.v.d == 0) |
2659 | 7 | throw makeError(ast.location, "division by zero."); |
2660 | 62.4k | scratch = makeNumberCheck(ast.location, lhs.v.d / rhs.v.d); |
2661 | 62.4k | break; |
2662 | | |
2663 | | // No need to check doubles made from longs |
2664 | | |
2665 | 1.39k | case BOP_SHIFT_L: { |
2666 | 1.39k | if (rhs.v.d < 0) |
2667 | 2 | throw makeError(ast.location, "shift by negative exponent."); |
2668 | | |
2669 | 1.39k | int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); |
2670 | 1.39k | int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); |
2671 | 1.39k | long_r = long_r % 64; |
2672 | | |
2673 | | // Additional safety check for left shifts to prevent undefined behavior. |
2674 | | // Left-shift that would move the highest set bit into the sign bit position is undefined. |
2675 | 1.39k | if (long_r >= 1 && abs(long_l) >= (1LL << (63 - long_r))) { |
2676 | 62 | throw makeError(ast.location, |
2677 | 62 | "numeric value outside safe integer range for bitwise operation."); |
2678 | 62 | } |
2679 | | |
2680 | | // Left-shift of a negative number is undefined until C++20. |
2681 | | // Perform the shift on unsigned int to avoid that. |
2682 | 1.32k | scratch = makeNumber(static_cast<int64_t>(static_cast<uint64_t>(long_l) << long_r)); |
2683 | 1.32k | } break; |
2684 | | |
2685 | 87 | case BOP_SHIFT_R: { |
2686 | 87 | if (rhs.v.d < 0) |
2687 | 2 | throw makeError(ast.location, "shift by negative exponent."); |
2688 | | |
2689 | 85 | int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); |
2690 | 85 | int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); |
2691 | 85 | long_r = long_r % 64; |
2692 | 85 | scratch = makeNumber(long_l >> long_r); |
2693 | 85 | } break; |
2694 | | |
2695 | 26.9k | case BOP_BITWISE_AND: { |
2696 | 26.9k | int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); |
2697 | 26.9k | int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); |
2698 | 26.9k | scratch = makeNumber(long_l & long_r); |
2699 | 26.9k | } break; |
2700 | | |
2701 | 13.6k | case BOP_BITWISE_XOR: { |
2702 | 13.6k | int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); |
2703 | 13.6k | int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); |
2704 | 13.6k | scratch = makeNumber(long_l ^ long_r); |
2705 | 13.6k | } break; |
2706 | | |
2707 | 7.03k | case BOP_BITWISE_OR: { |
2708 | 7.03k | int64_t long_l = safeDoubleToInt64(lhs.v.d, ast.location); |
2709 | 7.03k | int64_t long_r = safeDoubleToInt64(rhs.v.d, ast.location); |
2710 | 7.03k | scratch = makeNumber(long_l | long_r); |
2711 | 7.03k | } break; |
2712 | | |
2713 | 45.2k | case BOP_LESS_EQ: scratch = makeBoolean(lhs.v.d <= rhs.v.d); break; |
2714 | | |
2715 | 681k | case BOP_GREATER_EQ: |
2716 | 681k | scratch = makeBoolean(lhs.v.d >= rhs.v.d); |
2717 | 681k | break; |
2718 | | |
2719 | 161k | case BOP_LESS: scratch = makeBoolean(lhs.v.d < rhs.v.d); break; |
2720 | | |
2721 | 26.6k | case BOP_GREATER: scratch = makeBoolean(lhs.v.d > rhs.v.d); break; |
2722 | | |
2723 | 8 | default: |
2724 | 8 | throw makeError(ast.location, |
2725 | 8 | "binary operator " + bop_string(ast.op) + |
2726 | 8 | " does not operate on numbers."); |
2727 | 1.59M | } |
2728 | 1.59M | break; |
2729 | | |
2730 | 1.59M | case Value::FUNCTION: |
2731 | 2 | throw makeError(ast.location, |
2732 | 2 | "binary operator " + bop_string(ast.op) + |
2733 | 2 | " does not operate on functions."); |
2734 | | |
2735 | 3 | case Value::NULL_TYPE: |
2736 | 3 | throw makeError(ast.location, |
2737 | 3 | "binary operator " + bop_string(ast.op) + |
2738 | 3 | " does not operate on null."); |
2739 | | |
2740 | 2.69M | case Value::OBJECT: { |
2741 | 2.69M | if (ast.op != BOP_PLUS) { |
2742 | 16 | throw makeError(ast.location, |
2743 | 16 | "binary operator " + bop_string(ast.op) + |
2744 | 16 | " does not operate on objects."); |
2745 | 16 | } |
2746 | 2.69M | auto *lhs_obj = static_cast<HeapObject *>(lhs.v.h); |
2747 | 2.69M | auto *rhs_obj = static_cast<HeapObject *>(rhs.v.h); |
2748 | 2.69M | scratch = makeObject<HeapExtendedObject>(lhs_obj, rhs_obj); |
2749 | 2.69M | } break; |
2750 | | |
2751 | 153k | case Value::STRING: { |
2752 | 153k | const UString &lhs_str = static_cast<HeapString *>(lhs.v.h)->value; |
2753 | 153k | const UString &rhs_str = static_cast<HeapString *>(rhs.v.h)->value; |
2754 | 153k | switch (ast.op) { |
2755 | 0 | case BOP_PLUS: scratch = makeString(lhs_str + rhs_str); break; |
2756 | | |
2757 | 23.1k | case BOP_LESS_EQ: scratch = makeBoolean(lhs_str <= rhs_str); break; |
2758 | | |
2759 | 109k | case BOP_GREATER_EQ: |
2760 | 109k | scratch = makeBoolean(lhs_str >= rhs_str); |
2761 | 109k | break; |
2762 | | |
2763 | 10.5k | case BOP_LESS: scratch = makeBoolean(lhs_str < rhs_str); break; |
2764 | | |
2765 | 10.6k | case BOP_GREATER: scratch = makeBoolean(lhs_str > rhs_str); break; |
2766 | | |
2767 | 46 | default: |
2768 | 46 | throw makeError(ast.location, |
2769 | 46 | "binary operator " + bop_string(ast.op) + |
2770 | 46 | " does not operate on strings."); |
2771 | 153k | } |
2772 | 153k | } break; |
2773 | 6.10M | } |
2774 | 6.10M | } break; |
2775 | | |
2776 | 6.07M | case FRAME_BUILTIN_FILTER: { |
2777 | 0 | const auto &ast = *static_cast<const Apply *>(f.ast); |
2778 | 0 | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2779 | 0 | auto *arr = static_cast<HeapArray *>(f.val2.v.h); |
2780 | 0 | if (scratch.t != Value::BOOLEAN) { |
2781 | 0 | throw makeError( |
2782 | 0 | ast.location, |
2783 | 0 | "filter function must return boolean, got: " + type_str(scratch)); |
2784 | 0 | } |
2785 | 0 | if (scratch.v.b) |
2786 | 0 | f.thunks.push_back(arr->elements[f.elementId]); |
2787 | 0 | f.elementId++; |
2788 | | // Iterate through arr, calling the function on each. |
2789 | 0 | if (f.elementId == arr->elements.size()) { |
2790 | 0 | scratch = makeArray(f.thunks); |
2791 | 0 | } else { |
2792 | 0 | auto *thunk = arr->elements[f.elementId]; |
2793 | 0 | BindingFrame bindings = func->upValues; |
2794 | 0 | bindings[func->params[0].id] = thunk; |
2795 | 0 | stack.newCall(ast.location, func, func->self, func->offset, bindings); |
2796 | 0 | ast_ = func->body; |
2797 | 0 | goto recurse; |
2798 | 0 | } |
2799 | 0 | } break; |
2800 | | |
2801 | 30.0M | case FRAME_BUILTIN_FORCE_THUNKS: { |
2802 | 30.0M | const auto &location = f.location; |
2803 | 30.0M | auto *func = static_cast<HeapClosure *>(f.val.v.h); |
2804 | 30.0M | if (f.elementId == f.thunks.size()) { |
2805 | | // All thunks forced, now the builtin implementations. |
2806 | 11.6M | const std::string &builtin_name = func->builtinName; |
2807 | 11.6M | std::vector<Value> args; |
2808 | 18.3M | for (const auto &p : func->params) { |
2809 | 18.3M | const auto it = f.bindings.find(p.id); |
2810 | 18.3M | if (it != f.bindings.end()) { |
2811 | 18.3M | assert(it->second->filled); |
2812 | 18.3M | args.push_back(it->second->content); |
2813 | 18.3M | } else { |
2814 | 0 | std::stringstream ss; |
2815 | 0 | ss << "function parameter " << encode_utf8(p.id->name) |
2816 | 0 | << " not bound in call."; |
2817 | 0 | throw makeError(location, ss.str()); |
2818 | 0 | } |
2819 | 18.3M | } |
2820 | | |
2821 | 11.6M | BuiltinMap::const_iterator bit = builtins.find(builtin_name); |
2822 | 11.6M | if (bit != builtins.end()) { |
2823 | 11.6M | const AST *new_ast = (this->*bit->second)(location, args); |
2824 | 11.6M | if (new_ast != nullptr) { |
2825 | 0 | ast_ = new_ast; |
2826 | 0 | goto recurse; |
2827 | 0 | } |
2828 | 11.6M | break; |
2829 | 11.6M | } |
2830 | 0 | VmNativeCallbackMap::const_iterator nit = |
2831 | 0 | nativeCallbacks.find(builtin_name); |
2832 | | // TODO(dcunnin): Support arrays. |
2833 | | // TODO(dcunnin): Support objects. |
2834 | 0 | std::vector<JsonnetJsonValue> args2; |
2835 | 0 | for (const Value &arg : args) { |
2836 | 0 | switch (arg.t) { |
2837 | 0 | case Value::STRING: |
2838 | 0 | args2.emplace_back( |
2839 | 0 | JsonnetJsonValue::STRING, |
2840 | 0 | encode_utf8(static_cast<HeapString *>(arg.v.h)->value), |
2841 | 0 | 0); |
2842 | 0 | break; |
2843 | | |
2844 | 0 | case Value::BOOLEAN: |
2845 | 0 | args2.emplace_back( |
2846 | 0 | JsonnetJsonValue::BOOL, "", arg.v.b ? 1.0 : 0.0); |
2847 | 0 | break; |
2848 | | |
2849 | 0 | case Value::NUMBER: |
2850 | 0 | args2.emplace_back(JsonnetJsonValue::NUMBER, "", arg.v.d); |
2851 | 0 | break; |
2852 | | |
2853 | 0 | case Value::NULL_TYPE: |
2854 | 0 | args2.emplace_back(JsonnetJsonValue::NULL_KIND, "", 0); |
2855 | 0 | break; |
2856 | | |
2857 | 0 | default: |
2858 | 0 | throw makeError(location, |
2859 | 0 | "native extensions can only take primitives."); |
2860 | 0 | } |
2861 | 0 | } |
2862 | 0 | std::vector<const JsonnetJsonValue *> args3; |
2863 | 0 | for (size_t i = 0; i < args2.size(); ++i) { |
2864 | 0 | args3.push_back(&args2[i]); |
2865 | 0 | } |
2866 | 0 | if (nit == nativeCallbacks.end()) { |
2867 | 0 | throw makeError(location, |
2868 | 0 | "unrecognized builtin name: " + builtin_name); |
2869 | 0 | } |
2870 | 0 | const VmNativeCallback &cb = nit->second; |
2871 | |
|
2872 | 0 | int succ; |
2873 | 0 | std::unique_ptr<JsonnetJsonValue> r(cb.cb(cb.ctx, args3.data(), &succ)); |
2874 | |
|
2875 | 0 | if (succ) { |
2876 | 0 | bool unused; |
2877 | 0 | jsonToHeap(r, unused, scratch); |
2878 | 0 | } else { |
2879 | 0 | if (r->kind != JsonnetJsonValue::STRING) { |
2880 | 0 | throw makeError( |
2881 | 0 | location, |
2882 | 0 | "native extension returned an error that was not a string."); |
2883 | 0 | } |
2884 | 0 | std::string rs = r->string; |
2885 | 0 | throw makeError(location, rs); |
2886 | 0 | } |
2887 | |
|
2888 | 18.4M | } else { |
2889 | | // Not all arguments forced yet. |
2890 | 18.4M | HeapThunk *th = f.thunks[f.elementId++]; |
2891 | 18.4M | if (!th->filled) { |
2892 | 18.4M | stack.newCall(location, th, th->self, th->offset, th->upValues); |
2893 | 18.4M | ast_ = th->body; |
2894 | 18.4M | goto recurse; |
2895 | 18.4M | } else { |
2896 | | // Otherwise loop back around to force the next thunk. |
2897 | 0 | goto replaceframe; |
2898 | 0 | } |
2899 | 18.4M | } |
2900 | 30.0M | } break; |
2901 | | |
2902 | 61.2M | case FRAME_CALL: { |
2903 | 61.2M | if (auto *thunk = dynamic_cast<HeapThunk *>(f.context)) { |
2904 | | // If we called a thunk, cache result. |
2905 | 32.7M | thunk->fill(scratch); |
2906 | 32.7M | } else if (auto *closure = dynamic_cast<HeapClosure *>(f.context)) { |
2907 | 9.92M | if (f.elementId < f.thunks.size()) { |
2908 | | // If tailstrict, force thunks |
2909 | 4.97M | HeapThunk *th = f.thunks[f.elementId++]; |
2910 | 4.97M | if (!th->filled) { |
2911 | 4.97M | stack.newCall(f.location, th, th->self, th->offset, th->upValues); |
2912 | 4.97M | ast_ = th->body; |
2913 | 4.97M | goto recurse; |
2914 | 4.97M | } |
2915 | 4.97M | } else if (f.thunks.size() == 0) { |
2916 | | // Body has now been executed |
2917 | 2.72M | } else { |
2918 | | // Execute the body |
2919 | 2.22M | f.thunks.clear(); |
2920 | 2.22M | f.elementId = 0; |
2921 | 2.22M | ast_ = closure->body; |
2922 | 2.22M | goto recurse; |
2923 | 2.22M | } |
2924 | 9.92M | } |
2925 | | // Result of call is in scratch, just pop. |
2926 | 61.2M | } break; |
2927 | | |
2928 | 54.0M | case FRAME_ERROR: { |
2929 | 4.20k | const auto &ast = *static_cast<const Error *>(f.ast); |
2930 | 4.20k | UString msg; |
2931 | 4.20k | if (scratch.t == Value::STRING) { |
2932 | 611 | msg = static_cast<HeapString *>(scratch.v.h)->value; |
2933 | 3.59k | } else { |
2934 | 3.59k | msg = toString(ast.location); |
2935 | 3.59k | } |
2936 | 4.20k | throw makeError(ast.location, encode_utf8(msg)); |
2937 | 61.2M | } break; |
2938 | | |
2939 | 11.3M | case FRAME_IF: { |
2940 | 11.3M | const auto &ast = *static_cast<const Conditional *>(f.ast); |
2941 | 11.3M | if (scratch.t != Value::BOOLEAN) { |
2942 | 104 | throw makeError( |
2943 | 104 | ast.location, |
2944 | 104 | "condition must be boolean, got " + type_str(scratch) + "."); |
2945 | 104 | } |
2946 | 11.3M | ast_ = scratch.v.b ? ast.branchTrue : ast.branchFalse; |
2947 | 11.3M | stack.pop(); |
2948 | 11.3M | goto recurse; |
2949 | 11.3M | } break; |
2950 | | |
2951 | 3.70M | case FRAME_SUPER_INDEX: { |
2952 | 3.70M | const auto &ast = *static_cast<const SuperIndex *>(f.ast); |
2953 | 3.70M | HeapObject *self; |
2954 | 3.70M | unsigned offset; |
2955 | 3.70M | stack.getSelfBinding(self, offset); |
2956 | 3.70M | offset++; |
2957 | 3.70M | if (offset >= countLeaves(self)) { |
2958 | 2 | throw makeError(ast.location, |
2959 | 2 | "attempt to use super when there is no super class."); |
2960 | 2 | } |
2961 | 3.70M | if (scratch.t != Value::STRING) { |
2962 | 0 | throw makeError( |
2963 | 0 | ast.location, |
2964 | 0 | "super index must be string, got " + type_str(scratch) + "."); |
2965 | 0 | } |
2966 | | |
2967 | 3.70M | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
2968 | 3.70M | auto *fid = alloc->makeIdentifier(index_name); |
2969 | 3.70M | stack.pop(); |
2970 | 3.70M | ast_ = objectIndex(ast.location, self, fid, offset); |
2971 | 3.70M | goto recurse; |
2972 | 3.70M | } break; |
2973 | | |
2974 | 4.13M | case FRAME_IN_SUPER_ELEMENT: { |
2975 | 4.13M | const auto &ast = *static_cast<const InSuper *>(f.ast); |
2976 | 4.13M | HeapObject *self; |
2977 | 4.13M | unsigned offset; |
2978 | 4.13M | stack.getSelfBinding(self, offset); |
2979 | 4.13M | offset++; |
2980 | 4.13M | if (scratch.t != Value::STRING) { |
2981 | 1 | throw makeError(ast.location, |
2982 | 1 | "left hand side of e in super must be string, got " + |
2983 | 1 | type_str(scratch) + "."); |
2984 | 1 | } |
2985 | 4.13M | if (offset >= countLeaves(self)) { |
2986 | | // There is no super object. |
2987 | 209k | scratch = makeBoolean(false); |
2988 | 3.92M | } else { |
2989 | 3.92M | const UString &element_name = static_cast<HeapString *>(scratch.v.h)->value; |
2990 | 3.92M | auto *fid = alloc->makeIdentifier(element_name); |
2991 | 3.92M | unsigned unused_found_at = 0; |
2992 | 3.92M | bool in = findObject(fid, self, offset, unused_found_at); |
2993 | 3.92M | scratch = makeBoolean(in); |
2994 | 3.92M | } |
2995 | 4.13M | } break; |
2996 | | |
2997 | 15.6M | case FRAME_INDEX_INDEX: { |
2998 | 15.6M | const auto &ast = *static_cast<const Index *>(f.ast); |
2999 | 15.6M | const Value &target = f.val; |
3000 | 15.6M | if (target.t == Value::ARRAY) { |
3001 | 337k | const auto *array = static_cast<HeapArray *>(target.v.h); |
3002 | 337k | if (scratch.t == Value::STRING) { |
3003 | 114 | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
3004 | 114 | throw makeError( |
3005 | 114 | ast.location, |
3006 | 114 | "attempted index an array with string \"" |
3007 | 114 | + encode_utf8(jsonnet_string_escape(str, false)) + "\"."); |
3008 | 114 | } |
3009 | 337k | if (scratch.t != Value::NUMBER) { |
3010 | 21 | throw makeError( |
3011 | 21 | ast.location, |
3012 | 21 | "array index must be number, got " + type_str(scratch) + "."); |
3013 | 21 | } |
3014 | 337k | double index = ::floor(scratch.v.d); |
3015 | 337k | long sz = array->elements.size(); |
3016 | 337k | if (index < 0 || index >= sz) { |
3017 | 16 | std::stringstream ss; |
3018 | 16 | ss << "array bounds error: " << index << " not within [0, " << sz |
3019 | 16 | << ")"; |
3020 | 16 | throw makeError(ast.location, ss.str()); |
3021 | 16 | } |
3022 | 337k | if (scratch.v.d != index) { |
3023 | 13 | std::stringstream ss; |
3024 | 13 | ss << "array index was not integer: " << scratch.v.d; |
3025 | 13 | throw makeError(ast.location, ss.str()); |
3026 | 13 | } |
3027 | | // index < sz <= SIZE_T_MAX |
3028 | 337k | auto *thunk = array->elements[size_t(index)]; |
3029 | 337k | if (thunk->filled) { |
3030 | 148k | scratch = thunk->content; |
3031 | 189k | } else { |
3032 | 189k | stack.pop(); |
3033 | 189k | stack.newCall( |
3034 | 189k | ast.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
3035 | 189k | ast_ = thunk->body; |
3036 | 189k | goto recurse; |
3037 | 189k | } |
3038 | 15.3M | } else if (target.t == Value::OBJECT) { |
3039 | 15.0M | auto *obj = static_cast<HeapObject *>(target.v.h); |
3040 | 15.0M | assert(obj != nullptr); |
3041 | 15.0M | if (scratch.t != Value::STRING) { |
3042 | 14 | throw makeError( |
3043 | 14 | ast.location, |
3044 | 14 | "object index must be string, got " + type_str(scratch) + "."); |
3045 | 14 | } |
3046 | 15.0M | const UString &index_name = static_cast<HeapString *>(scratch.v.h)->value; |
3047 | 15.0M | auto *fid = alloc->makeIdentifier(index_name); |
3048 | 15.0M | stack.pop(); |
3049 | 15.0M | ast_ = objectIndex(ast.location, obj, fid, 0); |
3050 | 15.0M | goto recurse; |
3051 | 15.0M | } else if (target.t == Value::STRING) { |
3052 | 301k | auto *obj = static_cast<HeapString *>(target.v.h); |
3053 | 301k | assert(obj != nullptr); |
3054 | 301k | if (scratch.t != Value::NUMBER) { |
3055 | 6 | throw makeError( |
3056 | 6 | ast.location, |
3057 | 6 | "string index must be a number, got " + type_str(scratch) + "."); |
3058 | 6 | } |
3059 | 301k | long sz = obj->value.length(); |
3060 | 301k | long i = (long)scratch.v.d; |
3061 | 301k | if (i < 0 || i >= sz) { |
3062 | 189 | std::stringstream ss; |
3063 | 189 | ss << "string bounds error: " << i << " not within [0, " << sz << ")"; |
3064 | 189 | throw makeError(ast.location, ss.str()); |
3065 | 189 | } |
3066 | 301k | char32_t ch[] = {obj->value[i], U'\0'}; |
3067 | 301k | scratch = makeString(ch); |
3068 | 301k | } else { |
3069 | 0 | std::cerr << "INTERNAL ERROR: not object / array / string." << std::endl; |
3070 | 0 | abort(); |
3071 | 0 | } |
3072 | 15.6M | } break; |
3073 | | |
3074 | 15.6M | case FRAME_INDEX_TARGET: { |
3075 | 15.6M | const auto &ast = *static_cast<const Index *>(f.ast); |
3076 | 15.6M | if (scratch.t != Value::ARRAY && scratch.t != Value::OBJECT && |
3077 | 301k | scratch.t != Value::STRING) { |
3078 | 26 | throw makeError(ast.location, |
3079 | 26 | "can only index objects, strings, and arrays, got " + |
3080 | 26 | type_str(scratch) + "."); |
3081 | 26 | } |
3082 | 15.6M | f.val = scratch; |
3083 | 15.6M | f.kind = FRAME_INDEX_INDEX; |
3084 | 15.6M | if (scratch.t == Value::OBJECT) { |
3085 | 15.0M | auto *self = static_cast<HeapObject *>(scratch.v.h); |
3086 | 15.0M | if (!stack.alreadyExecutingInvariants(self)) { |
3087 | 14.9M | stack.newFrame(FRAME_INVARIANTS, ast.location); |
3088 | 14.9M | Frame &f2 = stack.top(); |
3089 | 14.9M | f2.self = self; |
3090 | 14.9M | unsigned counter = 0; |
3091 | 14.9M | objectInvariants(self, self, counter, f2.thunks); |
3092 | 14.9M | if (f2.thunks.size() > 0) { |
3093 | 1.85k | auto *thunk = f2.thunks[0]; |
3094 | 1.85k | f2.elementId = 1; |
3095 | 1.85k | stack.newCall(ast.location, |
3096 | 1.85k | thunk, |
3097 | 1.85k | thunk->self, |
3098 | 1.85k | thunk->offset, |
3099 | 1.85k | thunk->upValues); |
3100 | 1.85k | ast_ = thunk->body; |
3101 | 1.85k | goto recurse; |
3102 | 1.85k | } |
3103 | 14.9M | } |
3104 | 15.0M | } |
3105 | 15.6M | ast_ = ast.index; |
3106 | 15.6M | goto recurse; |
3107 | 15.6M | } break; |
3108 | | |
3109 | 15.0M | case FRAME_INVARIANTS: { |
3110 | 15.0M | if (f.elementId >= f.thunks.size()) { |
3111 | 14.9M | if (stack.size() == initial_stack_size + 1) { |
3112 | | // Just pop, evaluate was invoked by runInvariants. |
3113 | 18.1k | break; |
3114 | 18.1k | } |
3115 | 14.9M | stack.pop(); |
3116 | 14.9M | Frame &f2 = stack.top(); |
3117 | 14.9M | const auto &ast = *static_cast<const Index *>(f2.ast); |
3118 | 14.9M | ast_ = ast.index; |
3119 | 14.9M | goto recurse; |
3120 | 14.9M | } |
3121 | 31.6k | auto *thunk = f.thunks[f.elementId++]; |
3122 | 31.6k | stack.newCall(f.location, thunk, thunk->self, thunk->offset, thunk->upValues); |
3123 | 31.6k | ast_ = thunk->body; |
3124 | 31.6k | goto recurse; |
3125 | 15.0M | } break; |
3126 | | |
3127 | 6.89M | case FRAME_LOCAL: { |
3128 | | // Result of execution is in scratch already. |
3129 | 6.89M | } break; |
3130 | | |
3131 | 3.69M | case FRAME_OBJECT: { |
3132 | 3.69M | const auto &ast = *static_cast<const DesugaredObject *>(f.ast); |
3133 | 3.69M | if (scratch.t != Value::NULL_TYPE) { |
3134 | 3.69M | if (scratch.t != Value::STRING) { |
3135 | 23 | throw makeError(ast.location, "field name was not a string."); |
3136 | 23 | } |
3137 | 3.69M | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
3138 | 3.69M | const Identifier *fid = alloc->makeIdentifier(fname); |
3139 | 3.69M | if (f.objectFields.find(fid) != f.objectFields.end()) { |
3140 | 3 | std::string msg = |
3141 | 3 | "duplicate field name: \"" + encode_utf8(fname) + "\""; |
3142 | 3 | throw makeError(ast.location, msg); |
3143 | 3 | } |
3144 | 3.69M | f.objectFields[fid].hide = f.fit->hide; |
3145 | 3.69M | f.objectFields[fid].body = f.fit->body; |
3146 | 3.69M | } |
3147 | 3.69M | f.fit++; |
3148 | 3.69M | if (f.fit != ast.fields.end()) { |
3149 | 2.29M | ast_ = f.fit->name; |
3150 | 2.29M | goto recurse; |
3151 | 2.29M | } else { |
3152 | 1.40M | auto env = capture(ast.freeVariables); |
3153 | 1.40M | scratch = makeObject<HeapSimpleObject>(env, f.objectFields, ast.asserts); |
3154 | 1.40M | } |
3155 | 3.69M | } break; |
3156 | | |
3157 | 1.40M | case FRAME_OBJECT_COMP_ARRAY: { |
3158 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
3159 | 0 | const Value &arr_v = scratch; |
3160 | 0 | if (scratch.t != Value::ARRAY) { |
3161 | 0 | throw makeError(ast.location, |
3162 | 0 | "object comprehension needs array, got " + type_str(arr_v)); |
3163 | 0 | } |
3164 | 0 | const auto *arr = static_cast<const HeapArray *>(arr_v.v.h); |
3165 | 0 | if (arr->elements.size() == 0) { |
3166 | | // Degenerate case. Just create the object now. |
3167 | 0 | scratch = makeObject<HeapComprehensionObject>( |
3168 | 0 | BindingFrame{}, ast.value, ast.id, BindingFrame{}); |
3169 | 0 | } else { |
3170 | 0 | f.kind = FRAME_OBJECT_COMP_ELEMENT; |
3171 | 0 | f.val = scratch; |
3172 | 0 | f.bindings[ast.id] = arr->elements[0]; |
3173 | 0 | f.elementId = 0; |
3174 | 0 | ast_ = ast.field; |
3175 | 0 | goto recurse; |
3176 | 0 | } |
3177 | 0 | } break; |
3178 | | |
3179 | 0 | case FRAME_OBJECT_COMP_ELEMENT: { |
3180 | 0 | const auto &ast = *static_cast<const ObjectComprehensionSimple *>(f.ast); |
3181 | 0 | const auto *arr = static_cast<const HeapArray *>(f.val.v.h); |
3182 | 0 | if (scratch.t != Value::NULL_TYPE) { |
3183 | 0 | if (scratch.t != Value::STRING) { |
3184 | 0 | std::stringstream ss; |
3185 | 0 | ss << "field must be string, got: " << type_str(scratch); |
3186 | 0 | throw makeError(ast.location, ss.str()); |
3187 | 0 | } |
3188 | 0 | const auto &fname = static_cast<const HeapString *>(scratch.v.h)->value; |
3189 | 0 | const Identifier *fid = alloc->makeIdentifier(fname); |
3190 | 0 | if (f.elements.find(fid) != f.elements.end()) { |
3191 | 0 | throw makeError(ast.location, |
3192 | 0 | "duplicate field name: \"" + encode_utf8(fname) + "\""); |
3193 | 0 | } |
3194 | 0 | f.elements[fid] = arr->elements[f.elementId]; |
3195 | 0 | } |
3196 | 0 | f.elementId++; |
3197 | |
|
3198 | 0 | if (f.elementId == arr->elements.size()) { |
3199 | 0 | auto env = capture(ast.freeVariables); |
3200 | 0 | scratch = |
3201 | 0 | makeObject<HeapComprehensionObject>(env, ast.value, ast.id, f.elements); |
3202 | 0 | } else { |
3203 | 0 | f.bindings[ast.id] = arr->elements[f.elementId]; |
3204 | 0 | ast_ = ast.field; |
3205 | 0 | goto recurse; |
3206 | 0 | } |
3207 | 0 | } break; |
3208 | | |
3209 | 2.03M | case FRAME_STRING_CONCAT: { |
3210 | 2.03M | const auto &ast = *static_cast<const Binary *>(f.ast); |
3211 | 2.03M | const Value &lhs = stack.top().val; |
3212 | 2.03M | UString output; |
3213 | 2.03M | if (lhs.t == Value::STRING) { |
3214 | 1.80M | output.append(static_cast<const HeapString *>(lhs.v.h)->value); |
3215 | 1.80M | } else { |
3216 | 226k | scratch = lhs; |
3217 | 226k | output.append(toString(ast.left->location)); |
3218 | 226k | } |
3219 | 2.03M | const Value &rhs = stack.top().val2; |
3220 | 2.03M | if (rhs.t == Value::STRING) { |
3221 | 1.75M | output.append(static_cast<const HeapString *>(rhs.v.h)->value); |
3222 | 1.75M | } else { |
3223 | 281k | scratch = rhs; |
3224 | 281k | output.append(toString(ast.right->location)); |
3225 | 281k | } |
3226 | 2.03M | scratch = makeString(output); |
3227 | 2.03M | } break; |
3228 | | |
3229 | 2.79M | case FRAME_UNARY: { |
3230 | 2.79M | const auto &ast = *static_cast<const Unary *>(f.ast); |
3231 | 2.79M | switch (scratch.t) { |
3232 | 2.62M | case Value::BOOLEAN: |
3233 | 2.62M | if (ast.op == UOP_NOT) { |
3234 | 2.62M | scratch = makeBoolean(!scratch.v.b); |
3235 | 2.62M | } else { |
3236 | 3 | throw makeError(ast.location, |
3237 | 3 | "unary operator " + uop_string(ast.op) + |
3238 | 3 | " does not operate on booleans."); |
3239 | 3 | } |
3240 | 2.62M | break; |
3241 | | |
3242 | 2.62M | case Value::NUMBER: |
3243 | 171k | switch (ast.op) { |
3244 | 13.6k | case UOP_PLUS: break; |
3245 | | |
3246 | 145k | case UOP_MINUS: scratch = makeNumber(-scratch.v.d); break; |
3247 | | |
3248 | 12.0k | case UOP_BITWISE_NOT: |
3249 | 12.0k | scratch = makeNumber(~(long)(scratch.v.d)); |
3250 | 12.0k | break; |
3251 | | |
3252 | 29 | default: |
3253 | 29 | throw makeError(ast.location, |
3254 | 29 | "unary operator " + uop_string(ast.op) + |
3255 | 29 | " does not operate on numbers."); |
3256 | 171k | } |
3257 | 171k | break; |
3258 | | |
3259 | 171k | default: |
3260 | 174 | throw makeError(ast.location, |
3261 | 174 | "unary operator " + uop_string(ast.op) + |
3262 | 174 | " does not operate on type " + type_str(scratch)); |
3263 | 2.79M | } |
3264 | 2.79M | } break; |
3265 | | |
3266 | 2.79M | case FRAME_BUILTIN_JOIN_STRINGS: { |
3267 | 0 | joinString(f.first, f.str, f.val, f.elementId, scratch); |
3268 | 0 | f.elementId++; |
3269 | 0 | auto *ast = joinStrings(); |
3270 | 0 | if (ast != nullptr) { |
3271 | 0 | ast_ = ast; |
3272 | 0 | goto recurse; |
3273 | 0 | } |
3274 | 0 | } break; |
3275 | | |
3276 | 0 | case FRAME_BUILTIN_JOIN_ARRAYS: { |
3277 | 0 | joinArray(f.first, f.thunks, f.val, f.elementId, scratch); |
3278 | 0 | f.elementId++; |
3279 | 0 | auto *ast = joinArrays(); |
3280 | 0 | if (ast != nullptr) { |
3281 | 0 | ast_ = ast; |
3282 | 0 | goto recurse; |
3283 | 0 | } |
3284 | 0 | } break; |
3285 | | |
3286 | 0 | case FRAME_BUILTIN_DECODE_UTF8: { |
3287 | 0 | auto *ast = decodeUTF8(); |
3288 | 0 | if (ast != nullptr) { |
3289 | 0 | ast_ = ast; |
3290 | 0 | goto recurse; |
3291 | 0 | } |
3292 | 0 | } break; |
3293 | | |
3294 | 0 | default: |
3295 | 0 | std::cerr << "INTERNAL ERROR: Unknown FrameKind: " << f.kind << std::endl; |
3296 | 0 | std::abort(); |
3297 | 203M | } |
3298 | | |
3299 | 89.5M | popframe:; |
3300 | | |
3301 | 89.5M | stack.pop(); |
3302 | | |
3303 | 105M | replaceframe:; |
3304 | 105M | } |
3305 | 107M | } |
3306 | | |
3307 | | /** Manifest the scratch value by evaluating any remaining fields, and then convert to JSON. |
3308 | | * |
3309 | | * This can trigger a garbage collection cycle. Be sure to stash any objects that aren't |
3310 | | * reachable via the stack or heap. |
3311 | | * |
3312 | | * \param multiline If true, will print objects and arrays in an indented fashion. |
3313 | | */ |
3314 | | UString manifestJson(const LocationRange &loc, bool multiline, const UString &indent) |
3315 | 10.2M | { |
3316 | | // Printing fields means evaluating and binding them, which can trigger |
3317 | | // garbage collection. |
3318 | | |
3319 | 10.2M | UStringStream ss; |
3320 | 10.2M | switch (scratch.t) { |
3321 | 412k | case Value::ARRAY: { |
3322 | 412k | HeapArray *arr = static_cast<HeapArray *>(scratch.v.h); |
3323 | 412k | if (arr->elements.size() == 0) { |
3324 | 103k | ss << U"[ ]"; |
3325 | 308k | } else { |
3326 | 308k | const char32_t *prefix = multiline ? U"[\n" : U"["; |
3327 | 308k | UString indent2 = multiline ? indent + U" " : indent; |
3328 | 7.57M | for (auto *thunk : arr->elements) { |
3329 | 7.57M | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3330 | 7.57M | if (thunk->filled) { |
3331 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3332 | | // Keep arr alive when scratch is overwritten |
3333 | 0 | stack.top().val = scratch; |
3334 | 0 | scratch = thunk->content; |
3335 | 7.57M | } else { |
3336 | 7.57M | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3337 | | // Keep arr alive when scratch is overwritten |
3338 | 7.57M | stack.top().val = scratch; |
3339 | 7.57M | evaluate(thunk->body, stack.size()); |
3340 | 7.57M | } |
3341 | 7.57M | auto element = manifestJson(tloc, multiline, indent2); |
3342 | | // Restore scratch |
3343 | 7.57M | scratch = stack.top().val; |
3344 | 7.57M | stack.pop(); |
3345 | 7.57M | ss << prefix << indent2 << element; |
3346 | 7.57M | prefix = multiline ? U",\n" : U", "; |
3347 | 7.57M | } |
3348 | 308k | ss << (multiline ? U"\n" : U"") << indent << U"]"; |
3349 | 308k | } |
3350 | 412k | } break; |
3351 | | |
3352 | 137k | case Value::BOOLEAN: ss << (scratch.v.b ? U"true" : U"false"); break; |
3353 | | |
3354 | 8.39M | case Value::NUMBER: ss << decode_utf8(jsonnet_unparse_number(scratch.v.d)); break; |
3355 | | |
3356 | 26 | case Value::FUNCTION: |
3357 | 26 | throw makeError(loc, "couldn't manifest function in JSON output."); |
3358 | | |
3359 | 5.06k | case Value::NULL_TYPE: ss << U"null"; break; |
3360 | | |
3361 | 1.16M | case Value::OBJECT: { |
3362 | 1.16M | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3363 | 1.16M | runInvariants(loc, obj); |
3364 | | // Using std::map has the useful side-effect of ordering the fields |
3365 | | // alphabetically. |
3366 | 1.16M | std::map<UString, const Identifier *> fields; |
3367 | 1.56M | for (const auto &f : objectFields(obj, true)) { |
3368 | 1.56M | fields[f->name] = f; |
3369 | 1.56M | } |
3370 | 1.16M | if (fields.size() == 0) { |
3371 | 322k | ss << U"{ }"; |
3372 | 843k | } else { |
3373 | 843k | UString indent2 = multiline ? indent + U" " : indent; |
3374 | 843k | const char32_t *prefix = multiline ? U"{\n" : U"{"; |
3375 | 1.45M | for (const auto &f : fields) { |
3376 | | // pushes FRAME_CALL |
3377 | 1.45M | const AST *body = objectIndex(loc, obj, f.second, 0); |
3378 | 1.45M | stack.top().val = scratch; |
3379 | 1.45M | evaluate(body, stack.size()); |
3380 | 1.45M | auto vstr = manifestJson(body->location, multiline, indent2); |
3381 | | // Reset scratch so that the object we're manifesting doesn't |
3382 | | // get GC'd. |
3383 | 1.45M | scratch = stack.top().val; |
3384 | 1.45M | stack.pop(); |
3385 | 1.45M | ss << prefix << indent2 << jsonnet_string_unparse(f.first, false) << U": " |
3386 | 1.45M | << vstr; |
3387 | 1.45M | prefix = multiline ? U",\n" : U", "; |
3388 | 1.45M | } |
3389 | 843k | ss << (multiline ? U"\n" : U"") << indent << U"}"; |
3390 | 843k | } |
3391 | 1.16M | } break; |
3392 | | |
3393 | 128k | case Value::STRING: { |
3394 | 128k | const UString &str = static_cast<HeapString *>(scratch.v.h)->value; |
3395 | 128k | ss << jsonnet_string_unparse(str, false); |
3396 | 128k | } break; |
3397 | 10.2M | } |
3398 | 9.90M | return ss.str(); |
3399 | 10.2M | } |
3400 | | |
3401 | | UString manifestString(const LocationRange &loc) |
3402 | 0 | { |
3403 | 0 | if (scratch.t != Value::STRING) { |
3404 | 0 | std::stringstream ss; |
3405 | 0 | ss << "expected string result, got: " << type_str(scratch.t); |
3406 | 0 | throw makeError(loc, ss.str()); |
3407 | 0 | } |
3408 | 0 | return static_cast<HeapString *>(scratch.v.h)->value; |
3409 | 0 | } |
3410 | | |
3411 | | StrMap manifestMulti(bool string) |
3412 | 1.79k | { |
3413 | 1.79k | StrMap r; |
3414 | 1.79k | LocationRange loc("During manifestation"); |
3415 | 1.79k | if (scratch.t != Value::OBJECT) { |
3416 | 736 | std::stringstream ss; |
3417 | 736 | ss << "multi mode: top-level object was a " << type_str(scratch.t) << ", " |
3418 | 736 | << "should be an object whose keys are filenames and values hold " |
3419 | 736 | << "the JSON for that file."; |
3420 | 736 | throw makeError(loc, ss.str()); |
3421 | 736 | } |
3422 | 1.06k | auto *obj = static_cast<HeapObject *>(scratch.v.h); |
3423 | 1.06k | runInvariants(loc, obj); |
3424 | 1.06k | std::map<UString, const Identifier *> fields; |
3425 | 4.62k | for (const auto &f : objectFields(obj, true)) { |
3426 | 4.62k | fields[f->name] = f; |
3427 | 4.62k | } |
3428 | 4.36k | for (const auto &f : fields) { |
3429 | | // pushes FRAME_CALL |
3430 | 4.36k | const AST *body = objectIndex(loc, obj, f.second, 0); |
3431 | 4.36k | stack.top().val = scratch; |
3432 | 4.36k | evaluate(body, stack.size()); |
3433 | 4.36k | auto vstr = |
3434 | 4.36k | string ? manifestString(body->location) : manifestJson(body->location, true, U""); |
3435 | | // Reset scratch so that the object we're manifesting doesn't |
3436 | | // get GC'd. |
3437 | 4.36k | scratch = stack.top().val; |
3438 | 4.36k | stack.pop(); |
3439 | 4.36k | r[encode_utf8(f.first)] = encode_utf8(vstr); |
3440 | 4.36k | } |
3441 | 1.06k | return r; |
3442 | 1.79k | } |
3443 | | |
3444 | | std::vector<std::string> manifestStream(bool string) |
3445 | 1.49k | { |
3446 | 1.49k | std::vector<std::string> r; |
3447 | 1.49k | LocationRange loc("During manifestation"); |
3448 | 1.49k | if (scratch.t != Value::ARRAY) { |
3449 | 929 | std::stringstream ss; |
3450 | 929 | ss << "stream mode: top-level object was a " << type_str(scratch.t) << ", " |
3451 | 929 | << "should be an array whose elements hold " |
3452 | 929 | << "the JSON for each document in the stream."; |
3453 | 929 | throw makeError(loc, ss.str()); |
3454 | 929 | } |
3455 | 561 | auto *arr = static_cast<HeapArray *>(scratch.v.h); |
3456 | 736k | for (auto *thunk : arr->elements) { |
3457 | 736k | LocationRange tloc = thunk->body == nullptr ? loc : thunk->body->location; |
3458 | 736k | if (thunk->filled) { |
3459 | 0 | stack.newCall(loc, thunk, nullptr, 0, BindingFrame{}); |
3460 | | // Keep arr alive when scratch is overwritten |
3461 | 0 | stack.top().val = scratch; |
3462 | 0 | scratch = thunk->content; |
3463 | 736k | } else { |
3464 | 736k | stack.newCall(loc, thunk, thunk->self, thunk->offset, thunk->upValues); |
3465 | | // Keep arr alive when scratch is overwritten |
3466 | 736k | stack.top().val = scratch; |
3467 | 736k | evaluate(thunk->body, stack.size()); |
3468 | 736k | } |
3469 | 736k | UString element = string ? manifestString(tloc) : manifestJson(tloc, true, U""); |
3470 | 736k | scratch = stack.top().val; |
3471 | 736k | stack.pop(); |
3472 | 736k | r.push_back(encode_utf8(element)); |
3473 | 736k | } |
3474 | 561 | return r; |
3475 | 1.49k | } |
3476 | | |
3477 | | static void handleRapidYamlError(const char* inner_msg, size_t length, ryml::Location loc, void * /* unused: userdata */) |
3478 | 0 | { |
3479 | 0 | std::ostringstream msg; |
3480 | 0 | msg << "YAML error: " << loc.line << ":"; |
3481 | 0 | if (loc.col) { |
3482 | 0 | msg << loc.col << ":"; |
3483 | 0 | } else if (loc.offset) { |
3484 | 0 | msg << loc.offset << ":"; |
3485 | 0 | } |
3486 | 0 | msg << " " << std::string_view(inner_msg, length); |
3487 | 0 | throw RapidYamlError(msg.str()); |
3488 | 0 | } |
3489 | | }; |
3490 | | |
3491 | | } // namespace |
3492 | | |
3493 | | std::string jsonnet_vm_execute(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3494 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3495 | | const VmNativeCallbackMap &natives, |
3496 | | JsonnetImportCallback *import_callback, void *ctx, |
3497 | | bool string_output) |
3498 | 0 | { |
3499 | 0 | Interpreter vm(alloc, |
3500 | 0 | ext_vars, |
3501 | 0 | max_stack, |
3502 | 0 | gc_min_objects, |
3503 | 0 | gc_growth_trigger, |
3504 | 0 | natives, |
3505 | 0 | import_callback, |
3506 | 0 | ctx); |
3507 | 0 | vm.evaluate(ast, 0); |
3508 | 0 | if (string_output) { |
3509 | 0 | return encode_utf8(vm.manifestString(LocationRange("During manifestation"))); |
3510 | 0 | } else { |
3511 | 0 | return encode_utf8(vm.manifestJson(LocationRange("During manifestation"), true, U"")); |
3512 | 0 | } |
3513 | 0 | } |
3514 | | |
3515 | | StrMap jsonnet_vm_execute_multi(Allocator *alloc, const AST *ast, const ExtMap &ext_vars, |
3516 | | unsigned max_stack, double gc_min_objects, double gc_growth_trigger, |
3517 | | const VmNativeCallbackMap &natives, |
3518 | | JsonnetImportCallback *import_callback, void *ctx, |
3519 | | bool string_output) |
3520 | 2.86k | { |
3521 | 2.86k | Interpreter vm(alloc, |
3522 | 2.86k | ext_vars, |
3523 | 2.86k | max_stack, |
3524 | 2.86k | gc_min_objects, |
3525 | 2.86k | gc_growth_trigger, |
3526 | 2.86k | natives, |
3527 | 2.86k | import_callback, |
3528 | 2.86k | ctx); |
3529 | 2.86k | vm.evaluate(ast, 0); |
3530 | 2.86k | return vm.manifestMulti(string_output); |
3531 | 2.86k | } |
3532 | | |
3533 | | std::vector<std::string> jsonnet_vm_execute_stream(Allocator *alloc, const AST *ast, |
3534 | | const ExtMap &ext_vars, unsigned max_stack, |
3535 | | double gc_min_objects, double gc_growth_trigger, |
3536 | | const VmNativeCallbackMap &natives, |
3537 | | JsonnetImportCallback *import_callback, |
3538 | | void *ctx, bool string_output) |
3539 | 3.03k | { |
3540 | 3.03k | Interpreter vm(alloc, |
3541 | 3.03k | ext_vars, |
3542 | 3.03k | max_stack, |
3543 | 3.03k | gc_min_objects, |
3544 | 3.03k | gc_growth_trigger, |
3545 | 3.03k | natives, |
3546 | 3.03k | import_callback, |
3547 | 3.03k | ctx); |
3548 | 3.03k | vm.evaluate(ast, 0); |
3549 | 3.03k | return vm.manifestStream(string_output); |
3550 | 3.03k | } |
3551 | | |
3552 | | } // namespace jsonnet::internal |