zexus 1.8.2 → 1.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -64
- package/package.json +1 -1
- package/rust_core/Cargo.lock +1 -1
- package/src/zexus/__init__.py +1 -1
- package/src/zexus/builtin_modules.py +50 -13
- package/src/zexus/cli/main.py +46 -1
- package/src/zexus/cli/zpm.py +1 -1
- package/src/zexus/evaluator/bytecode_compiler.py +11 -2
- package/src/zexus/evaluator/core.py +4 -1
- package/src/zexus/evaluator/expressions.py +11 -2
- package/src/zexus/evaluator/functions.py +72 -0
- package/src/zexus/evaluator/resource_limiter.py +1 -1
- package/src/zexus/evaluator/statements.py +44 -4
- package/src/zexus/kernel/__init__.py +34 -0
- package/src/zexus/kernel/hooks.py +276 -0
- package/src/zexus/kernel/registry.py +203 -0
- package/src/zexus/kernel/zir/__init__.py +145 -0
- package/src/zexus/lexer.py +7 -0
- package/src/zexus/object.py +28 -5
- package/src/zexus/parser/parser.py +53 -11
- package/src/zexus/parser/strategy_context.py +179 -10
- package/src/zexus/security.py +26 -2
- package/src/zexus/stdlib/blockchain.py +84 -0
- package/src/zexus/stdlib/http_server.py +2 -2
- package/src/zexus/stdlib/math.py +25 -17
- package/src/zexus/stdlib_integration.py +119 -2
- package/src/zexus/type_checker.py +17 -12
- package/src/zexus/vm/compiler.py +57 -6
- package/src/zexus/vm/fastops.c +4704 -1263
- package/src/zexus/vm/fastops.cpython-312-x86_64-linux-gnu.so +0 -0
- package/src/zexus/vm/fastops.pyx +81 -3
- package/src/zexus/vm/optimizer.py +65 -27
- package/src/zexus/vm/vm.py +871 -98
- package/src/zexus/zexus_ast.py +4 -1
- package/src/zexus/zpm/package_manager.py +1 -1
- package/src/zexus.egg-info/PKG-INFO +90 -65
- package/src/zexus.egg-info/SOURCES.txt +51 -0
package/src/zexus/vm/vm.py
CHANGED
|
@@ -19,6 +19,7 @@ import threading
|
|
|
19
19
|
import importlib
|
|
20
20
|
import hashlib
|
|
21
21
|
import types
|
|
22
|
+
import warnings
|
|
22
23
|
from typing import List, Any, Dict, Tuple, Optional, Union, Callable
|
|
23
24
|
from enum import Enum
|
|
24
25
|
|
|
@@ -34,6 +35,36 @@ from ..object import (
|
|
|
34
35
|
DateTime as ZDateTime,
|
|
35
36
|
)
|
|
36
37
|
|
|
38
|
+
# ==================== VM Warning / Diagnostics ====================
|
|
39
|
+
|
|
40
|
+
# Controlled by ZEXUS_VM_WARNINGS env var: "all", "errors", "none" (default: "errors")
|
|
41
|
+
_VM_WARN_LEVEL = os.environ.get("ZEXUS_VM_WARNINGS", "errors").lower()
|
|
42
|
+
|
|
43
|
+
def _vm_warn(category: str, message: str, exc: Exception = None):
|
|
44
|
+
"""Emit a VM diagnostic warning instead of silently swallowing errors.
|
|
45
|
+
|
|
46
|
+
When ZEXUS_VM_WARNINGS=all → print all warnings to stderr
|
|
47
|
+
When ZEXUS_VM_WARNINGS=errors → only print on error (default)
|
|
48
|
+
When ZEXUS_VM_WARNINGS=none → fully silent (legacy behaviour)
|
|
49
|
+
"""
|
|
50
|
+
if _VM_WARN_LEVEL == "none":
|
|
51
|
+
return
|
|
52
|
+
if _VM_WARN_LEVEL == "all" or (exc is not None and _VM_WARN_LEVEL == "errors"):
|
|
53
|
+
detail = f": {exc}" if exc else ""
|
|
54
|
+
print(f"[VM WARN] {category}{detail} — {message}", file=sys.stderr)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class VMRuntimeError(Exception):
|
|
58
|
+
"""Proper Python exception for VM runtime errors (require/verify failures, etc.).
|
|
59
|
+
|
|
60
|
+
ZEvaluationError from object.py inherits from Object, not Exception,
|
|
61
|
+
so it cannot be used with Python's raise statement.
|
|
62
|
+
"""
|
|
63
|
+
def __init__(self, message):
|
|
64
|
+
super().__init__(message)
|
|
65
|
+
self.zexus_message = message
|
|
66
|
+
|
|
67
|
+
|
|
37
68
|
# ==================== Backend / Optional Imports ====================
|
|
38
69
|
|
|
39
70
|
# JIT Compiler
|
|
@@ -148,6 +179,257 @@ except Exception:
|
|
|
148
179
|
# Sentinel returned when Rust VM signals needs_fallback
|
|
149
180
|
_RUST_VM_FALLBACK_SENTINEL = object()
|
|
150
181
|
|
|
182
|
+
# ==================== Zexus Method Dispatch Tables ====================
|
|
183
|
+
# These provide Zexus-specific methods on plain Python dicts/lists/strings
|
|
184
|
+
# that the VM creates via BUILD_MAP/BUILD_LIST/LOAD_CONST.
|
|
185
|
+
|
|
186
|
+
def _dict_has(target, args):
|
|
187
|
+
if not args:
|
|
188
|
+
return False
|
|
189
|
+
key = args[0]
|
|
190
|
+
return key in target
|
|
191
|
+
|
|
192
|
+
def _dict_keys(target, args):
|
|
193
|
+
return list(target.keys())
|
|
194
|
+
|
|
195
|
+
def _dict_values(target, args):
|
|
196
|
+
return list(target.values())
|
|
197
|
+
|
|
198
|
+
def _dict_entries(target, args):
|
|
199
|
+
return [[k, v] for k, v in target.items()]
|
|
200
|
+
|
|
201
|
+
def _dict_size(target, args):
|
|
202
|
+
return len(target)
|
|
203
|
+
|
|
204
|
+
def _dict_delete(target, args):
|
|
205
|
+
if args and args[0] in target:
|
|
206
|
+
del target[args[0]]
|
|
207
|
+
return True
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def _dict_contains(target, args):
|
|
211
|
+
if not args:
|
|
212
|
+
return False
|
|
213
|
+
return args[0] in target
|
|
214
|
+
|
|
215
|
+
_DICT_METHODS = {
|
|
216
|
+
"has": _dict_has,
|
|
217
|
+
"keys": _dict_keys,
|
|
218
|
+
"values": _dict_values,
|
|
219
|
+
"entries": _dict_entries,
|
|
220
|
+
"size": _dict_size,
|
|
221
|
+
"length": _dict_size,
|
|
222
|
+
"count": _dict_size,
|
|
223
|
+
"delete": _dict_delete,
|
|
224
|
+
"remove": _dict_delete,
|
|
225
|
+
"contains": _dict_contains,
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _list_push(target, args, vm=None):
|
|
230
|
+
if args:
|
|
231
|
+
target.append(args[0])
|
|
232
|
+
return target
|
|
233
|
+
|
|
234
|
+
def _list_pop(target, args, vm=None):
|
|
235
|
+
if target:
|
|
236
|
+
return target.pop()
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
def _list_count(target, args, vm=None):
|
|
240
|
+
if args:
|
|
241
|
+
return target.count(args[0])
|
|
242
|
+
return len(target)
|
|
243
|
+
|
|
244
|
+
def _list_length(target, args, vm=None):
|
|
245
|
+
return len(target)
|
|
246
|
+
|
|
247
|
+
def _list_contains(target, args, vm=None):
|
|
248
|
+
if not args:
|
|
249
|
+
return False
|
|
250
|
+
val = args[0]
|
|
251
|
+
# Try direct comparison, then unwrap
|
|
252
|
+
if val in target:
|
|
253
|
+
return True
|
|
254
|
+
if hasattr(val, 'value'):
|
|
255
|
+
return val.value in target
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
def _list_is_empty(target, args, vm=None):
|
|
259
|
+
return len(target) == 0
|
|
260
|
+
|
|
261
|
+
def _list_first(target, args, vm=None):
|
|
262
|
+
return target[0] if target else None
|
|
263
|
+
|
|
264
|
+
def _list_last(target, args, vm=None):
|
|
265
|
+
return target[-1] if target else None
|
|
266
|
+
|
|
267
|
+
def _list_reverse(target, args, vm=None):
|
|
268
|
+
return list(reversed(target))
|
|
269
|
+
|
|
270
|
+
def _list_sort(target, args, vm=None):
|
|
271
|
+
try:
|
|
272
|
+
return sorted(target)
|
|
273
|
+
except TypeError:
|
|
274
|
+
return target
|
|
275
|
+
|
|
276
|
+
def _list_join(target, args, vm=None):
|
|
277
|
+
sep = args[0] if args else ""
|
|
278
|
+
return sep.join(str(x) for x in target)
|
|
279
|
+
|
|
280
|
+
def _list_indexOf(target, args, vm=None):
|
|
281
|
+
if not args:
|
|
282
|
+
return -1
|
|
283
|
+
try:
|
|
284
|
+
return target.index(args[0])
|
|
285
|
+
except ValueError:
|
|
286
|
+
return -1
|
|
287
|
+
|
|
288
|
+
def _list_includes(target, args, vm=None):
|
|
289
|
+
return _list_contains(target, args, vm)
|
|
290
|
+
|
|
291
|
+
def _list_slice(target, args, vm=None):
|
|
292
|
+
start = args[0] if len(args) > 0 else 0
|
|
293
|
+
end = args[1] if len(args) > 1 else len(target)
|
|
294
|
+
return target[start:end]
|
|
295
|
+
|
|
296
|
+
def _list_flatten(target, args, vm=None):
|
|
297
|
+
result = []
|
|
298
|
+
for item in target:
|
|
299
|
+
if isinstance(item, list):
|
|
300
|
+
result.extend(item)
|
|
301
|
+
else:
|
|
302
|
+
result.append(item)
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
def _list_map(target, args, vm=None):
|
|
306
|
+
if not args or not vm:
|
|
307
|
+
return target
|
|
308
|
+
fn = args[0]
|
|
309
|
+
return [vm._invoke_callable_sync(fn, [item]) for item in target]
|
|
310
|
+
|
|
311
|
+
def _list_filter(target, args, vm=None):
|
|
312
|
+
if not args or not vm:
|
|
313
|
+
return target
|
|
314
|
+
fn = args[0]
|
|
315
|
+
return [item for item in target if vm._invoke_callable_sync(fn, [item])]
|
|
316
|
+
|
|
317
|
+
def _list_reduce(target, args, vm=None):
|
|
318
|
+
if not args or not vm:
|
|
319
|
+
return None
|
|
320
|
+
fn = args[0]
|
|
321
|
+
acc = args[1] if len(args) > 1 else (target[0] if target else None)
|
|
322
|
+
items = target if len(args) > 1 else target[1:]
|
|
323
|
+
for item in items:
|
|
324
|
+
acc = vm._invoke_callable_sync(fn, [acc, item])
|
|
325
|
+
return acc
|
|
326
|
+
|
|
327
|
+
_LIST_METHODS = {
|
|
328
|
+
"push": _list_push,
|
|
329
|
+
"append": _list_push,
|
|
330
|
+
"pop": _list_pop,
|
|
331
|
+
"count": _list_count,
|
|
332
|
+
"length": _list_length,
|
|
333
|
+
"size": _list_length,
|
|
334
|
+
"contains": _list_contains,
|
|
335
|
+
"includes": _list_includes,
|
|
336
|
+
"is_empty": _list_is_empty,
|
|
337
|
+
"isEmpty": _list_is_empty,
|
|
338
|
+
"first": _list_first,
|
|
339
|
+
"last": _list_last,
|
|
340
|
+
"reverse": _list_reverse,
|
|
341
|
+
"sort": _list_sort,
|
|
342
|
+
"join": _list_join,
|
|
343
|
+
"indexOf": _list_indexOf,
|
|
344
|
+
"index_of": _list_indexOf,
|
|
345
|
+
"slice": _list_slice,
|
|
346
|
+
"flatten": _list_flatten,
|
|
347
|
+
"map": _list_map,
|
|
348
|
+
"filter": _list_filter,
|
|
349
|
+
"reduce": _list_reduce,
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _str_contains(target, args):
|
|
354
|
+
return args[0] in target if args else False
|
|
355
|
+
|
|
356
|
+
def _str_startsWith(target, args):
|
|
357
|
+
return target.startswith(args[0]) if args else False
|
|
358
|
+
|
|
359
|
+
def _str_endsWith(target, args):
|
|
360
|
+
return target.endswith(args[0]) if args else False
|
|
361
|
+
|
|
362
|
+
def _str_toUpperCase(target, args):
|
|
363
|
+
return target.upper()
|
|
364
|
+
|
|
365
|
+
def _str_toLowerCase(target, args):
|
|
366
|
+
return target.lower()
|
|
367
|
+
|
|
368
|
+
def _str_trim(target, args):
|
|
369
|
+
return target.strip()
|
|
370
|
+
|
|
371
|
+
def _str_split(target, args):
|
|
372
|
+
sep = args[0] if args else " "
|
|
373
|
+
return target.split(sep)
|
|
374
|
+
|
|
375
|
+
def _str_indexOf(target, args):
|
|
376
|
+
if not args:
|
|
377
|
+
return -1
|
|
378
|
+
return target.find(args[0])
|
|
379
|
+
|
|
380
|
+
def _str_length(target, args):
|
|
381
|
+
return len(target)
|
|
382
|
+
|
|
383
|
+
def _str_replace(target, args):
|
|
384
|
+
if len(args) >= 2:
|
|
385
|
+
return target.replace(args[0], args[1])
|
|
386
|
+
return target
|
|
387
|
+
|
|
388
|
+
def _str_substring(target, args):
|
|
389
|
+
start = args[0] if len(args) > 0 else 0
|
|
390
|
+
end = args[1] if len(args) > 1 else len(target)
|
|
391
|
+
return target[start:end]
|
|
392
|
+
|
|
393
|
+
def _str_charAt(target, args):
|
|
394
|
+
if args and 0 <= args[0] < len(target):
|
|
395
|
+
return target[args[0]]
|
|
396
|
+
return ""
|
|
397
|
+
|
|
398
|
+
def _str_repeat(target, args):
|
|
399
|
+
n = args[0] if args else 1
|
|
400
|
+
return target * int(n)
|
|
401
|
+
|
|
402
|
+
_STR_METHODS = {
|
|
403
|
+
"contains": _str_contains,
|
|
404
|
+
"includes": _str_contains,
|
|
405
|
+
"startsWith": _str_startsWith,
|
|
406
|
+
"starts_with": _str_startsWith,
|
|
407
|
+
"endsWith": _str_endsWith,
|
|
408
|
+
"ends_with": _str_endsWith,
|
|
409
|
+
"toUpperCase": _str_toUpperCase,
|
|
410
|
+
"to_upper": _str_toUpperCase,
|
|
411
|
+
"upper": _str_toUpperCase,
|
|
412
|
+
"toLowerCase": _str_toLowerCase,
|
|
413
|
+
"to_lower": _str_toLowerCase,
|
|
414
|
+
"lower": _str_toLowerCase,
|
|
415
|
+
"trim": _str_trim,
|
|
416
|
+
"strip": _str_trim,
|
|
417
|
+
"split": _str_split,
|
|
418
|
+
"indexOf": _str_indexOf,
|
|
419
|
+
"index_of": _str_indexOf,
|
|
420
|
+
"length": _str_length,
|
|
421
|
+
"size": _str_length,
|
|
422
|
+
"replace": _str_replace,
|
|
423
|
+
"substring": _str_substring,
|
|
424
|
+
"slice": _str_substring,
|
|
425
|
+
"charAt": _str_charAt,
|
|
426
|
+
"char_at": _str_charAt,
|
|
427
|
+
"repeat": _str_repeat,
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
# Sentinel for _vm_native_call: returned when no native handler exists
|
|
431
|
+
_VM_NATIVE_MISS = object()
|
|
432
|
+
|
|
151
433
|
# Async Optimizer (Phase 8)
|
|
152
434
|
try:
|
|
153
435
|
from .async_optimizer import AsyncOptimizer, AsyncOptimizationLevel
|
|
@@ -329,9 +611,11 @@ def _fallback_type(args):
|
|
|
329
611
|
|
|
330
612
|
_FALLBACK_BUILTINS = {
|
|
331
613
|
"string": _fallback_string,
|
|
614
|
+
"str": _fallback_string, # alias — users expect str()
|
|
332
615
|
"int": _fallback_int,
|
|
333
616
|
"float": _fallback_float,
|
|
334
617
|
"len": _fallback_len,
|
|
618
|
+
"length": _fallback_len, # alias — users expect length()
|
|
335
619
|
"type": _fallback_type,
|
|
336
620
|
}
|
|
337
621
|
|
|
@@ -365,8 +649,8 @@ class VM:
|
|
|
365
649
|
enable_memory_pool: bool = True,
|
|
366
650
|
pool_max_size: int = 1000,
|
|
367
651
|
enable_peephole_optimizer: bool = True,
|
|
368
|
-
enable_bytecode_optimizer: bool =
|
|
369
|
-
optimizer_level: int =
|
|
652
|
+
enable_bytecode_optimizer: bool = True,
|
|
653
|
+
optimizer_level: int = 1,
|
|
370
654
|
optimization_level: str = "MODERATE",
|
|
371
655
|
enable_async_optimizer: bool = True,
|
|
372
656
|
async_optimization_level: str = "MODERATE",
|
|
@@ -441,6 +725,17 @@ class VM:
|
|
|
441
725
|
self._call_depth = 0
|
|
442
726
|
self._MAX_CALL_DEPTH = 256
|
|
443
727
|
|
|
728
|
+
# --- High-traffic hardening ---
|
|
729
|
+
# Stack overflow protection: limit operand stack depth
|
|
730
|
+
self._MAX_STACK_DEPTH = int(os.environ.get("ZEXUS_VM_MAX_STACK", "50000"))
|
|
731
|
+
# Execution timeout (seconds, 0 = unlimited)
|
|
732
|
+
self._exec_timeout = float(os.environ.get("ZEXUS_VM_TIMEOUT", "0"))
|
|
733
|
+
self._exec_start_time = 0.0
|
|
734
|
+
# VM pool ceiling (prevents unbounded memory growth under load)
|
|
735
|
+
self._VM_POOL_MAX = int(os.environ.get("ZEXUS_VM_POOL_MAX", "500"))
|
|
736
|
+
# Opcode execution hard-cap (prevents runaway programs; 0 = gas limit only)
|
|
737
|
+
self._MAX_OPCODES = int(os.environ.get("ZEXUS_VM_MAX_OPCODES", "0"))
|
|
738
|
+
|
|
444
739
|
# --- Rust VM Adaptive Routing (Phase 3 + Phase 6) ---
|
|
445
740
|
self._rust_vm_available = _RUST_VM_AVAILABLE
|
|
446
741
|
self._rust_vm_executor = _RustVMExecutor() if _RUST_VM_AVAILABLE else None
|
|
@@ -670,7 +965,8 @@ class VM:
|
|
|
670
965
|
def _return_vm_to_pool(self, vm) -> None:
|
|
671
966
|
"""Return a child VM to the pool for reuse."""
|
|
672
967
|
if hasattr(self, "_vm_pool") and self._vm_pool is not None:
|
|
673
|
-
|
|
968
|
+
pool_max = getattr(self, '_VM_POOL_MAX', 500)
|
|
969
|
+
if len(self._vm_pool) < pool_max:
|
|
674
970
|
# MEDIUM (M14): Scrub references that could leak state across pooled VMs.
|
|
675
971
|
try:
|
|
676
972
|
vm.env = None
|
|
@@ -1483,14 +1779,14 @@ class VM:
|
|
|
1483
1779
|
op_name = op.name if hasattr(op, "name") else op
|
|
1484
1780
|
normalized_for_opt.append((str(op_name), operand))
|
|
1485
1781
|
instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
|
|
1486
|
-
except Exception:
|
|
1487
|
-
pass
|
|
1782
|
+
except Exception as _e:
|
|
1783
|
+
_vm_warn("OPTIMIZER", "Bytecode optimizer pass failed (compile path)", _e)
|
|
1488
1784
|
|
|
1489
1785
|
if self.enable_peephole_optimizer and self.peephole_optimizer:
|
|
1490
1786
|
try:
|
|
1491
1787
|
instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
|
|
1492
|
-
except Exception:
|
|
1493
|
-
pass
|
|
1788
|
+
except Exception as _e:
|
|
1789
|
+
_vm_warn("OPTIMIZER", "Peephole optimizer pass failed (compile path)", _e)
|
|
1494
1790
|
|
|
1495
1791
|
normalized: List[Tuple[Any, Any]] = []
|
|
1496
1792
|
for instr in instrs:
|
|
@@ -1509,8 +1805,8 @@ class VM:
|
|
|
1509
1805
|
ssa_program = self.ssa_converter.convert_to_ssa(instrs)
|
|
1510
1806
|
ssa_instrs = destruct_ssa(ssa_program)
|
|
1511
1807
|
instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
|
|
1512
|
-
except Exception:
|
|
1513
|
-
pass
|
|
1808
|
+
except Exception as _e:
|
|
1809
|
+
_vm_warn("OPTIMIZER", "SSA conversion pass failed (compile path)", _e)
|
|
1514
1810
|
|
|
1515
1811
|
mapped: List[Tuple[Any, Any]] = []
|
|
1516
1812
|
for op, operand in instrs:
|
|
@@ -1995,8 +2291,8 @@ class VM:
|
|
|
1995
2291
|
return _fastops.execute(instrs, consts, self.env, self.builtins, self._closure_cells)
|
|
1996
2292
|
except NotImplementedError:
|
|
1997
2293
|
pass
|
|
1998
|
-
except Exception:
|
|
1999
|
-
|
|
2294
|
+
except Exception as _e:
|
|
2295
|
+
_vm_warn("FASTOPS", "Cython fast-path execution failed, falling back to interpreter", _e)
|
|
2000
2296
|
|
|
2001
2297
|
# Rust VM adaptive routing (Phase 3) — delegate to Rust when
|
|
2002
2298
|
# the program is large enough to amortise serialisation overhead.
|
|
@@ -2067,7 +2363,16 @@ class VM:
|
|
|
2067
2363
|
_gas_light_cost = self.gas_light_cost if _gas_light else 0
|
|
2068
2364
|
_gas_consume_light = _gas.consume_light if _gas else None
|
|
2069
2365
|
|
|
2366
|
+
# --- High-traffic hardening caches ---
|
|
2367
|
+
_exec_timeout = self._exec_timeout
|
|
2368
|
+
_max_stack = self._MAX_STACK_DEPTH
|
|
2369
|
+
_max_opcodes = self._MAX_OPCODES
|
|
2370
|
+
_total_ops = 0
|
|
2371
|
+
if _exec_timeout > 0:
|
|
2372
|
+
self._exec_start_time = time.monotonic()
|
|
2373
|
+
|
|
2070
2374
|
while ip < instr_count:
|
|
2375
|
+
try:
|
|
2071
2376
|
op_name, operand = instrs[ip]
|
|
2072
2377
|
ip += 1
|
|
2073
2378
|
|
|
@@ -2082,6 +2387,25 @@ class VM:
|
|
|
2082
2387
|
from .gas_metering import OutOfGasError
|
|
2083
2388
|
raise OutOfGasError(_gas.gas_used, _gas.gas_limit, op_name)
|
|
2084
2389
|
|
|
2390
|
+
# --- Hardening checks (every 4096 ops to amortize cost) ---
|
|
2391
|
+
_total_ops += 1
|
|
2392
|
+
if (_total_ops & 0xFFF) == 0:
|
|
2393
|
+
if _max_stack and len(stack) > _max_stack:
|
|
2394
|
+
raise VMRuntimeError(
|
|
2395
|
+
f"Stack overflow: depth {len(stack)} exceeds limit {_max_stack}. "
|
|
2396
|
+
"Possible infinite recursion or deeply nested expression."
|
|
2397
|
+
)
|
|
2398
|
+
if _exec_timeout > 0 and (time.monotonic() - self._exec_start_time) > _exec_timeout:
|
|
2399
|
+
raise VMRuntimeError(
|
|
2400
|
+
f"Execution timeout: exceeded {_exec_timeout}s limit. "
|
|
2401
|
+
"Set ZEXUS_VM_TIMEOUT=0 to disable."
|
|
2402
|
+
)
|
|
2403
|
+
if _max_opcodes > 0 and _total_ops > _max_opcodes:
|
|
2404
|
+
raise VMRuntimeError(
|
|
2405
|
+
f"Opcode limit exceeded: {_total_ops} > {_max_opcodes}. "
|
|
2406
|
+
"Set ZEXUS_VM_MAX_OPCODES=0 to disable."
|
|
2407
|
+
)
|
|
2408
|
+
|
|
2085
2409
|
if trace_interval > 0:
|
|
2086
2410
|
trace_counter += 1
|
|
2087
2411
|
if trace_counter % trace_interval == 0:
|
|
@@ -2144,7 +2468,13 @@ class VM:
|
|
|
2144
2468
|
a = stack_pop() if stack else 0
|
|
2145
2469
|
if hasattr(a, 'value'): a = a.value
|
|
2146
2470
|
if hasattr(b, 'value'): b = b.value
|
|
2147
|
-
|
|
2471
|
+
if b == 0:
|
|
2472
|
+
raise VMRuntimeError("Division by zero")
|
|
2473
|
+
if isinstance(a, int) and isinstance(b, int) and not isinstance(a, bool) and not isinstance(b, bool):
|
|
2474
|
+
result_val = a // b if a % b == 0 else a / b
|
|
2475
|
+
else:
|
|
2476
|
+
result_val = a / b
|
|
2477
|
+
stack_append(result_val)
|
|
2148
2478
|
elif op_name == "MOD":
|
|
2149
2479
|
b = stack_pop() if stack else 1
|
|
2150
2480
|
a = stack_pop() if stack else 0
|
|
@@ -2231,8 +2561,8 @@ class VM:
|
|
|
2231
2561
|
mod_name = const(operand)
|
|
2232
2562
|
alias, names, is_named = "", [], False
|
|
2233
2563
|
self._execute_import(mod_name, alias=alias or "", names=names, is_named=bool(is_named))
|
|
2234
|
-
except Exception:
|
|
2235
|
-
|
|
2564
|
+
except Exception as _e:
|
|
2565
|
+
_vm_warn("IMPORT", f"Import of '{mod_name}' failed silently", _e)
|
|
2236
2566
|
elif op_name == "EXPORT":
|
|
2237
2567
|
# vm/compiler.py emits: LOAD_NAME <name_idx>; EXPORT <name_idx>
|
|
2238
2568
|
try:
|
|
@@ -2252,13 +2582,13 @@ class VM:
|
|
|
2252
2582
|
if callable(export_fn):
|
|
2253
2583
|
try:
|
|
2254
2584
|
export_fn(name, value)
|
|
2255
|
-
except Exception:
|
|
2256
|
-
|
|
2585
|
+
except Exception as _e:
|
|
2586
|
+
_vm_warn("EXPORT", f"export_fn('{name}') call failed", _e)
|
|
2257
2587
|
else:
|
|
2258
2588
|
self.env[name] = value
|
|
2259
2589
|
self._bump_env_version(name, value)
|
|
2260
|
-
except Exception:
|
|
2261
|
-
|
|
2590
|
+
except Exception as _e:
|
|
2591
|
+
_vm_warn("EXPORT", f"Export of '{name}' failed silently", _e)
|
|
2262
2592
|
elif op_name == "RETURN":
|
|
2263
2593
|
return stack_pop() if stack else None
|
|
2264
2594
|
elif op_name == "BUILD_LIST":
|
|
@@ -2267,11 +2597,15 @@ class VM:
|
|
|
2267
2597
|
stack_append(elements)
|
|
2268
2598
|
elif op_name == "BUILD_MAP":
|
|
2269
2599
|
count = operand if operand is not None else 0
|
|
2270
|
-
|
|
2600
|
+
pairs = []
|
|
2271
2601
|
for _ in range(count):
|
|
2272
2602
|
val = stack_pop()
|
|
2273
2603
|
key = stack_pop()
|
|
2274
|
-
|
|
2604
|
+
pairs.append((key, val))
|
|
2605
|
+
pairs.reverse()
|
|
2606
|
+
result = {}
|
|
2607
|
+
for k, v in pairs:
|
|
2608
|
+
result[k] = v
|
|
2275
2609
|
stack_append(result)
|
|
2276
2610
|
elif op_name == "INDEX":
|
|
2277
2611
|
idx = stack_pop()
|
|
@@ -2281,10 +2615,78 @@ class VM:
|
|
|
2281
2615
|
stack_append(obj.get(idx))
|
|
2282
2616
|
elif isinstance(obj, ZMap):
|
|
2283
2617
|
stack_append(obj.get(idx))
|
|
2618
|
+
elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
|
|
2619
|
+
# EntityInstance — access fields via .get() or .data dict
|
|
2620
|
+
idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
|
|
2621
|
+
val = obj.get(idx_str) if idx_str else None
|
|
2622
|
+
stack_append(self._unwrap_after_builtin(val) if val is not None else None)
|
|
2623
|
+
elif isinstance(obj, dict) and isinstance(idx, int) and not isinstance(idx, bool):
|
|
2624
|
+
keys = list(obj.keys())
|
|
2625
|
+
if 0 <= idx < len(keys):
|
|
2626
|
+
stack_append(obj[keys[idx]])
|
|
2627
|
+
else:
|
|
2628
|
+
stack_append(None)
|
|
2284
2629
|
else:
|
|
2285
2630
|
stack_append(obj[idx] if obj is not None else None)
|
|
2286
2631
|
except (IndexError, KeyError, TypeError):
|
|
2287
2632
|
stack_append(None)
|
|
2633
|
+
elif op_name == "FOR_ITER":
|
|
2634
|
+
# FOR_ITER var_count: pop counter, pop iterable, push values
|
|
2635
|
+
# var_count=1: single variable (element for list, key for dict)
|
|
2636
|
+
# var_count=2: two variables (index/key + element/value)
|
|
2637
|
+
var_count = operand if operand else 1
|
|
2638
|
+
idx = stack_pop() if stack else 0
|
|
2639
|
+
obj = stack_pop() if stack else None
|
|
2640
|
+
try:
|
|
2641
|
+
if isinstance(obj, dict):
|
|
2642
|
+
keys = list(obj.keys())
|
|
2643
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
2644
|
+
key = keys[idx]
|
|
2645
|
+
if var_count == 2:
|
|
2646
|
+
stack_append(key) # key (below)
|
|
2647
|
+
stack_append(obj[key]) # value (top)
|
|
2648
|
+
else:
|
|
2649
|
+
stack_append(key) # single var = key
|
|
2650
|
+
else:
|
|
2651
|
+
for _ in range(var_count):
|
|
2652
|
+
stack_append(None)
|
|
2653
|
+
elif isinstance(obj, ZMap):
|
|
2654
|
+
keys = list(obj.pairs.keys())
|
|
2655
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
2656
|
+
key = keys[idx]
|
|
2657
|
+
if var_count == 2:
|
|
2658
|
+
stack_append(key)
|
|
2659
|
+
stack_append(obj.pairs[key])
|
|
2660
|
+
else:
|
|
2661
|
+
stack_append(key)
|
|
2662
|
+
else:
|
|
2663
|
+
for _ in range(var_count):
|
|
2664
|
+
stack_append(None)
|
|
2665
|
+
elif isinstance(obj, (list, ZList)):
|
|
2666
|
+
elems = obj.elements if isinstance(obj, ZList) else obj
|
|
2667
|
+
if isinstance(idx, int) and 0 <= idx < len(elems):
|
|
2668
|
+
if var_count == 2:
|
|
2669
|
+
stack_append(idx) # index (below)
|
|
2670
|
+
stack_append(elems[idx]) # element (top)
|
|
2671
|
+
else:
|
|
2672
|
+
stack_append(elems[idx]) # single var = element
|
|
2673
|
+
else:
|
|
2674
|
+
for _ in range(var_count):
|
|
2675
|
+
stack_append(None)
|
|
2676
|
+
else:
|
|
2677
|
+
# Fallback: treat as indexable
|
|
2678
|
+
try:
|
|
2679
|
+
if var_count == 2:
|
|
2680
|
+
stack_append(idx)
|
|
2681
|
+
stack_append(obj[idx] if obj is not None else None)
|
|
2682
|
+
else:
|
|
2683
|
+
stack_append(obj[idx] if obj is not None else None)
|
|
2684
|
+
except Exception:
|
|
2685
|
+
for _ in range(var_count):
|
|
2686
|
+
stack_append(None)
|
|
2687
|
+
except Exception:
|
|
2688
|
+
for _ in range(var_count):
|
|
2689
|
+
stack_append(None)
|
|
2288
2690
|
elif op_name == "SLICE":
|
|
2289
2691
|
end = stack_pop() if stack else None
|
|
2290
2692
|
start = stack_pop() if stack else None
|
|
@@ -2321,6 +2723,11 @@ class VM:
|
|
|
2321
2723
|
name_idx, arg_count = operand
|
|
2322
2724
|
func_name = const(name_idx)
|
|
2323
2725
|
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
2726
|
+
# --- Fast-path: native VM builtins for common list/type ops ---
|
|
2727
|
+
res = self._vm_native_call(func_name, args)
|
|
2728
|
+
if res is not _VM_NATIVE_MISS:
|
|
2729
|
+
stack_append(res)
|
|
2730
|
+
continue
|
|
2324
2731
|
fn = resolve(func_name) or builtins.get(func_name)
|
|
2325
2732
|
if fn is None:
|
|
2326
2733
|
res = self._call_fallback_builtin(func_name, args)
|
|
@@ -2369,7 +2776,7 @@ class VM:
|
|
|
2369
2776
|
norm_key = str(key)
|
|
2370
2777
|
existing = target.pairs.get(norm_key)
|
|
2371
2778
|
if existing is not None and existing.__class__.__name__ == 'SealedObject':
|
|
2372
|
-
raise
|
|
2779
|
+
raise VMRuntimeError(f"Cannot modify sealed map key: {key}")
|
|
2373
2780
|
target.pairs[norm_key] = args[1]
|
|
2374
2781
|
result = args[1]
|
|
2375
2782
|
elif isinstance(target, ZList) and len(args) >= 2:
|
|
@@ -2383,12 +2790,22 @@ class VM:
|
|
|
2383
2790
|
result = target.get(args[0])
|
|
2384
2791
|
elif isinstance(target, dict) and args:
|
|
2385
2792
|
result = target.get(args[0])
|
|
2793
|
+
# ── Zexus dict/map methods ──
|
|
2794
|
+
elif isinstance(target, dict) and method_name in _DICT_METHODS:
|
|
2795
|
+
result = _DICT_METHODS[method_name](target, args)
|
|
2796
|
+
# ── Zexus list methods ──
|
|
2797
|
+
elif isinstance(target, list) and method_name in _LIST_METHODS:
|
|
2798
|
+
result = _LIST_METHODS[method_name](target, args, self)
|
|
2799
|
+
# ── Zexus string methods ──
|
|
2800
|
+
elif isinstance(target, str) and method_name in _STR_METHODS:
|
|
2801
|
+
result = _STR_METHODS[method_name](target, args)
|
|
2386
2802
|
elif hasattr(target, "call_method"):
|
|
2387
2803
|
wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
2388
2804
|
try:
|
|
2389
2805
|
from .. import security as _security
|
|
2390
2806
|
_security._set_vm_action_context(True)
|
|
2391
|
-
except Exception:
|
|
2807
|
+
except Exception as _e:
|
|
2808
|
+
_vm_warn("SECURITY", "Failed to set VM action context (pre-call)", _e)
|
|
2392
2809
|
_security = None
|
|
2393
2810
|
try:
|
|
2394
2811
|
result = target.call_method(method_name, wrapped_args)
|
|
@@ -2396,8 +2813,8 @@ class VM:
|
|
|
2396
2813
|
if _security is not None:
|
|
2397
2814
|
try:
|
|
2398
2815
|
_security._set_vm_action_context(False)
|
|
2399
|
-
except Exception:
|
|
2400
|
-
|
|
2816
|
+
except Exception as _e:
|
|
2817
|
+
_vm_warn("SECURITY", "Failed to clear VM action context (post-call)", _e)
|
|
2401
2818
|
else:
|
|
2402
2819
|
attr = self._get_cached_method(target, method_name)
|
|
2403
2820
|
if callable(attr):
|
|
@@ -2417,7 +2834,8 @@ class VM:
|
|
|
2417
2834
|
result = candidate
|
|
2418
2835
|
else:
|
|
2419
2836
|
result = attr
|
|
2420
|
-
except Exception:
|
|
2837
|
+
except Exception as _e:
|
|
2838
|
+
_vm_warn("CALL_METHOD", f"Method '{method_name}' call failed on {type(target).__name__}", _e)
|
|
2421
2839
|
result = None
|
|
2422
2840
|
stack_append(self._unwrap_after_builtin(result))
|
|
2423
2841
|
elif op_name == "PRINT":
|
|
@@ -2438,17 +2856,25 @@ class VM:
|
|
|
2438
2856
|
stack_append(obj.get(key))
|
|
2439
2857
|
elif isinstance(obj, dict):
|
|
2440
2858
|
stack_append(obj.get(attr_name))
|
|
2859
|
+
elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
|
|
2860
|
+
# EntityInstance — access fields via .data dict
|
|
2861
|
+
stack_append(obj.data.get(attr_name, getattr(obj, attr_name, None)))
|
|
2441
2862
|
elif hasattr(obj, 'get') and hasattr(obj, 'set') and callable(getattr(obj, 'get', None)):
|
|
2442
2863
|
# Contract-like objects (e.g., SmartContract) expose state via get/set.
|
|
2443
2864
|
stack_append(obj.get(attr_name))
|
|
2444
2865
|
else:
|
|
2445
2866
|
stack_append(getattr(obj, attr_name, None))
|
|
2446
|
-
except Exception:
|
|
2867
|
+
except Exception as _e:
|
|
2868
|
+
_vm_warn("GET_ATTR", f"Attribute '{attr_name}' access failed on {type(obj).__name__}", _e)
|
|
2447
2869
|
stack_append(None)
|
|
2448
2870
|
elif op_name == "DEFINE_CONTRACT":
|
|
2449
2871
|
contract_obj = self._build_smart_contract(operand, stack, stack_pop, const, env)
|
|
2450
2872
|
stack_append(contract_obj)
|
|
2451
2873
|
|
|
2874
|
+
elif op_name == "DEFINE_ENTITY":
|
|
2875
|
+
entity_obj = self._build_entity_definition(operand, stack, stack_pop, const)
|
|
2876
|
+
stack_append(entity_obj)
|
|
2877
|
+
|
|
2452
2878
|
# --- Blockchain / TX opcodes (sync-safe) ---
|
|
2453
2879
|
|
|
2454
2880
|
elif op_name == "HASH_BLOCK":
|
|
@@ -2585,7 +3011,7 @@ class VM:
|
|
|
2585
3011
|
if env.get("_in_transaction", False):
|
|
2586
3012
|
env["_blockchain_state"] = dict(env.get("_tx_snapshot", {}))
|
|
2587
3013
|
env["_in_transaction"] = False
|
|
2588
|
-
raise
|
|
3014
|
+
raise VMRuntimeError(
|
|
2589
3015
|
f"Out of gas: required {amount}, remaining {current}")
|
|
2590
3016
|
env["_gas_remaining"] = new_gas
|
|
2591
3017
|
|
|
@@ -2600,7 +3026,7 @@ class VM:
|
|
|
2600
3026
|
env["_in_transaction"] = False
|
|
2601
3027
|
env["_tx_pending_state"] = {}
|
|
2602
3028
|
env.pop("_tx_snapshot", None)
|
|
2603
|
-
raise
|
|
3029
|
+
raise VMRuntimeError(f"Requirement failed: {message}")
|
|
2604
3030
|
|
|
2605
3031
|
elif op_name == "LEDGER_APPEND":
|
|
2606
3032
|
entry = stack_pop()
|
|
@@ -2628,7 +3054,7 @@ class VM:
|
|
|
2628
3054
|
ip = handler_ip
|
|
2629
3055
|
else:
|
|
2630
3056
|
msg = exc.value if hasattr(exc, 'value') else exc
|
|
2631
|
-
raise
|
|
3057
|
+
raise VMRuntimeError(str(msg))
|
|
2632
3058
|
|
|
2633
3059
|
elif op_name == "ENABLE_ERROR_MODE":
|
|
2634
3060
|
env["_continue_on_error"] = True
|
|
@@ -2669,11 +3095,11 @@ class VM:
|
|
|
2669
3095
|
if owner is not None:
|
|
2670
3096
|
ov = owner.value if hasattr(owner, 'value') else str(owner)
|
|
2671
3097
|
if caller and caller != ov:
|
|
2672
|
-
raise
|
|
3098
|
+
raise VMRuntimeError(f"Access denied: '{r_key}' restricted to owner only")
|
|
2673
3099
|
elif isinstance(rv, (list, tuple)):
|
|
2674
3100
|
allowed = [a.value if hasattr(a, 'value') else str(a) for a in rv]
|
|
2675
3101
|
if caller and caller not in allowed:
|
|
2676
|
-
raise
|
|
3102
|
+
raise VMRuntimeError(f"Access denied: '{r_key}' restricted to allowed addresses")
|
|
2677
3103
|
|
|
2678
3104
|
elif op_name == "SPAWN":
|
|
2679
3105
|
# Sync SPAWN: schedule the function to run in a background thread
|
|
@@ -2720,6 +3146,19 @@ class VM:
|
|
|
2720
3146
|
else:
|
|
2721
3147
|
# Truly unknown op — fallback to async path
|
|
2722
3148
|
return self._run_coroutine_sync(self._run_stack_bytecode(bytecode, debug))
|
|
3149
|
+
except Exception as _sync_exc:
|
|
3150
|
+
# Route Python exceptions through the Zexus try/catch stack
|
|
3151
|
+
_ts = env.get("_try_stack_sync", [])
|
|
3152
|
+
if _ts:
|
|
3153
|
+
_handler_ip = _ts.pop()
|
|
3154
|
+
_exc_msg = _sync_exc.args[0] if _sync_exc.args else str(_sync_exc)
|
|
3155
|
+
stack_append(str(_exc_msg))
|
|
3156
|
+
ip = _handler_ip
|
|
3157
|
+
continue
|
|
3158
|
+
elif env.get("_continue_on_error", False):
|
|
3159
|
+
env.setdefault("_errors", []).append(str(_sync_exc))
|
|
3160
|
+
else:
|
|
3161
|
+
raise
|
|
2723
3162
|
|
|
2724
3163
|
return stack_pop() if stack else None
|
|
2725
3164
|
|
|
@@ -2827,6 +3266,93 @@ class VM:
|
|
|
2827
3266
|
|
|
2828
3267
|
return contract
|
|
2829
3268
|
|
|
3269
|
+
def _build_entity_definition(self, operand, stack, stack_pop, const):
|
|
3270
|
+
"""Create a real EntityDefinition from DEFINE_ENTITY bytecode.
|
|
3271
|
+
|
|
3272
|
+
This mirrors the interpreter's eval_entity_statement logic so that
|
|
3273
|
+
entity construction (``User("Alice", 30)``) works correctly on the VM.
|
|
3274
|
+
|
|
3275
|
+
Stack layout at DEFINE_ENTITY execution (top → bottom):
|
|
3276
|
+
prop_name_N, prop_default_N ← from compiler's property loop
|
|
3277
|
+
...
|
|
3278
|
+
prop_name_1, prop_default_1
|
|
3279
|
+
entity_name
|
|
3280
|
+
"""
|
|
3281
|
+
from ..object import EntityDefinition, Null as ObjNull
|
|
3282
|
+
|
|
3283
|
+
member_count = operand or 0
|
|
3284
|
+
|
|
3285
|
+
# Pop properties (name, default_value pairs) — pushed in order, so
|
|
3286
|
+
# we pop in reverse and then reverse again to restore ordering.
|
|
3287
|
+
raw_members = []
|
|
3288
|
+
for _ in range(member_count):
|
|
3289
|
+
key_obj = stack_pop() # property name string constant
|
|
3290
|
+
val_obj = stack_pop() # default value (or None / method)
|
|
3291
|
+
key_str = key_obj.value if hasattr(key_obj, 'value') else str(key_obj) if key_obj is not None else ""
|
|
3292
|
+
raw_members.append((key_str, val_obj))
|
|
3293
|
+
raw_members.reverse() # restore push order
|
|
3294
|
+
|
|
3295
|
+
# Pop entity name
|
|
3296
|
+
name_obj = stack_pop()
|
|
3297
|
+
entity_name = name_obj.value if hasattr(name_obj, 'value') else str(name_obj) if name_obj is not None else "UnknownEntity"
|
|
3298
|
+
|
|
3299
|
+
# Build properties dict in the format EntityDefinition expects:
|
|
3300
|
+
# { prop_name: { "type": "any", "default_value": <wrapped_value> } }
|
|
3301
|
+
properties = {}
|
|
3302
|
+
methods = {}
|
|
3303
|
+
ZAction_cls, ZLambda_cls = _get_action_types()
|
|
3304
|
+
|
|
3305
|
+
for key, val in raw_members:
|
|
3306
|
+
# If the value is an Action/Lambda, it's a method
|
|
3307
|
+
if ZAction_cls is not None and isinstance(val, (ZAction_cls, ZLambda_cls)):
|
|
3308
|
+
methods[key] = val
|
|
3309
|
+
elif isinstance(val, dict) and val.get("type") == "function":
|
|
3310
|
+
# Function descriptors compiled by the VM
|
|
3311
|
+
methods[key] = val
|
|
3312
|
+
else:
|
|
3313
|
+
wrapped = self._wrap_for_builtin(val) if val is not None else ObjNull()
|
|
3314
|
+
properties[key] = {"type": "any", "default_value": wrapped}
|
|
3315
|
+
|
|
3316
|
+
entity_def = EntityDefinition(entity_name, properties, methods)
|
|
3317
|
+
return entity_def
|
|
3318
|
+
|
|
3319
|
+
def _construct_entity(self, entity_def, args):
|
|
3320
|
+
"""Construct an EntityInstance from an EntityDefinition + call args.
|
|
3321
|
+
|
|
3322
|
+
Mirrors the interpreter's apply_function logic for entity construction:
|
|
3323
|
+
- Single Map arg → use as field values
|
|
3324
|
+
- Positional args → map to property names in order
|
|
3325
|
+
"""
|
|
3326
|
+
from ..object import EntityDefinition, Map as ObjMap, String as ObjString
|
|
3327
|
+
|
|
3328
|
+
values = {}
|
|
3329
|
+
|
|
3330
|
+
# If single arg is a Map or dict (handles Entity{field: value} syntax)
|
|
3331
|
+
if len(args) == 1 and isinstance(args[0], (ZMap, ObjMap, dict)):
|
|
3332
|
+
map_arg = args[0]
|
|
3333
|
+
if isinstance(map_arg, dict):
|
|
3334
|
+
pairs = map_arg
|
|
3335
|
+
else:
|
|
3336
|
+
pairs = map_arg.pairs if hasattr(map_arg, 'pairs') else {}
|
|
3337
|
+
for key, value in pairs.items():
|
|
3338
|
+
key_str = key.value if hasattr(key, 'value') else str(key)
|
|
3339
|
+
values[key_str] = self._wrap_for_builtin(value)
|
|
3340
|
+
else:
|
|
3341
|
+
# Positional args → map to property names
|
|
3342
|
+
if hasattr(entity_def, 'get_all_properties'):
|
|
3343
|
+
all_props = entity_def.get_all_properties()
|
|
3344
|
+
prop_names = list(all_props.keys())
|
|
3345
|
+
elif isinstance(entity_def.properties, dict):
|
|
3346
|
+
prop_names = list(entity_def.properties.keys())
|
|
3347
|
+
else:
|
|
3348
|
+
prop_names = [p.get('name', '') if isinstance(p, dict) else str(p) for p in (entity_def.properties or [])]
|
|
3349
|
+
|
|
3350
|
+
for i, arg in enumerate(args):
|
|
3351
|
+
if i < len(prop_names):
|
|
3352
|
+
values[prop_names[i]] = self._wrap_for_builtin(arg)
|
|
3353
|
+
|
|
3354
|
+
return entity_def.create_instance(values)
|
|
3355
|
+
|
|
2830
3356
|
def _invoke_callable_sync(self, fn, args):
|
|
2831
3357
|
"""Synchronous callable invocation for fast dispatch."""
|
|
2832
3358
|
if fn is None:
|
|
@@ -2847,6 +3373,11 @@ class VM:
|
|
|
2847
3373
|
|
|
2848
3374
|
def _invoke_callable_sync_inner(self, fn, args):
|
|
2849
3375
|
"""Inner implementation of callable invocation."""
|
|
3376
|
+
# --- Entity construction ---
|
|
3377
|
+
from ..object import EntityDefinition as _EntityDef, EntityInstance as _EntityInst
|
|
3378
|
+
if isinstance(fn, _EntityDef):
|
|
3379
|
+
return self._construct_entity(fn, args)
|
|
3380
|
+
|
|
2850
3381
|
real_fn = fn.fn if hasattr(fn, "fn") else fn
|
|
2851
3382
|
ZAction, ZLambda = _get_action_types()
|
|
2852
3383
|
if ZAction is not None and isinstance(real_fn, (ZAction, ZLambda)):
|
|
@@ -2861,7 +3392,8 @@ class VM:
|
|
|
2861
3392
|
action_bytecode = compiler.compile(real_fn.body, optimize=True)
|
|
2862
3393
|
if action_bytecode and not compiler.errors:
|
|
2863
3394
|
real_fn._cached_bytecode = action_bytecode
|
|
2864
|
-
except Exception:
|
|
3395
|
+
except Exception as _e:
|
|
3396
|
+
_vm_warn("COMPILE_ACTION", "Bytecode compilation failed for Action/Lambda — using interpreter fallback", _e)
|
|
2865
3397
|
action_bytecode = None
|
|
2866
3398
|
|
|
2867
3399
|
if action_bytecode:
|
|
@@ -2884,7 +3416,8 @@ class VM:
|
|
|
2884
3416
|
call_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
2885
3417
|
result = self._action_evaluator.apply_function(real_fn, call_args)
|
|
2886
3418
|
return self._unwrap_after_builtin(result)
|
|
2887
|
-
except Exception:
|
|
3419
|
+
except Exception as _e:
|
|
3420
|
+
_vm_warn("CALL_ACTION_FALLBACK", "Action/Lambda interpreter fallback failed", _e)
|
|
2888
3421
|
return None
|
|
2889
3422
|
if callable(real_fn) and not _iscoroutinefunction(real_fn):
|
|
2890
3423
|
try:
|
|
@@ -2892,13 +3425,14 @@ class VM:
|
|
|
2892
3425
|
call_args = [self._wrap_for_builtin(arg) for arg in args] if wrap_args else list(args)
|
|
2893
3426
|
result = real_fn(*call_args)
|
|
2894
3427
|
return self._unwrap_after_builtin(result) if wrap_args else result
|
|
2895
|
-
except Exception:
|
|
3428
|
+
except Exception as _e:
|
|
3429
|
+
_vm_warn("CALL_CALLABLE", f"Callable invocation failed: {getattr(real_fn, '__name__', repr(real_fn))}", _e)
|
|
2896
3430
|
return None
|
|
2897
3431
|
if isinstance(fn, dict):
|
|
2898
3432
|
# Function descriptor - execute bytecode
|
|
2899
3433
|
bytecode = fn.get("bytecode")
|
|
2900
3434
|
if bytecode:
|
|
2901
|
-
params = fn.get("parameters"
|
|
3435
|
+
params = fn.get("params") or fn.get("parameters") or []
|
|
2902
3436
|
local_env = {}
|
|
2903
3437
|
for i, p in enumerate(params):
|
|
2904
3438
|
pname = p.get("name") if isinstance(p, dict) else str(p)
|
|
@@ -2939,15 +3473,15 @@ class VM:
|
|
|
2939
3473
|
op_name = op.name if hasattr(op, "name") else op
|
|
2940
3474
|
normalized_for_opt.append((str(op_name), operand))
|
|
2941
3475
|
instrs = self.bytecode_optimizer.optimize(normalized_for_opt, consts)
|
|
2942
|
-
except Exception:
|
|
2943
|
-
pass
|
|
3476
|
+
except Exception as _e:
|
|
3477
|
+
_vm_warn("OPTIMIZER", "Bytecode optimizer pass failed (execute path)", _e)
|
|
2944
3478
|
|
|
2945
3479
|
# Peephole optimization with constant pool awareness
|
|
2946
3480
|
if not fast_single_shot and self.enable_peephole_optimizer and self.peephole_optimizer:
|
|
2947
3481
|
try:
|
|
2948
3482
|
instrs, consts = self.peephole_optimizer.optimize_bytecode(instrs, consts)
|
|
2949
|
-
except Exception:
|
|
2950
|
-
pass
|
|
3483
|
+
except Exception as _e:
|
|
3484
|
+
_vm_warn("OPTIMIZER", "Peephole optimizer pass failed (execute path)", _e)
|
|
2951
3485
|
|
|
2952
3486
|
# Normalize opcodes to names for SSA pipeline and stack dispatch
|
|
2953
3487
|
normalized_instrs: List[Tuple[Any, Any]] = []
|
|
@@ -2970,8 +3504,8 @@ class VM:
|
|
|
2970
3504
|
ssa_program = self.ssa_converter.convert_to_ssa(instrs)
|
|
2971
3505
|
ssa_instrs = destruct_ssa(ssa_program)
|
|
2972
3506
|
instrs, consts = self._normalize_ssa_instructions(ssa_instrs, consts)
|
|
2973
|
-
except Exception:
|
|
2974
|
-
pass
|
|
3507
|
+
except Exception as _e:
|
|
3508
|
+
_vm_warn("OPTIMIZER", "SSA conversion pass failed (execute path)", _e)
|
|
2975
3509
|
|
|
2976
3510
|
# 1. JIT Check (with thread safety)
|
|
2977
3511
|
if self.use_jit and self.jit_compiler:
|
|
@@ -3292,6 +3826,11 @@ class VM:
|
|
|
3292
3826
|
args.reverse()
|
|
3293
3827
|
else:
|
|
3294
3828
|
args = []
|
|
3829
|
+
# ── fast-path for native builtins (push/append/length/str/range) ──
|
|
3830
|
+
native_res = self._vm_native_call(func_name, args)
|
|
3831
|
+
if native_res is not _VM_NATIVE_MISS:
|
|
3832
|
+
stack_append(native_res)
|
|
3833
|
+
return
|
|
3295
3834
|
fn = None
|
|
3296
3835
|
try:
|
|
3297
3836
|
fn = builtins_get(func_name)
|
|
@@ -3442,7 +3981,7 @@ class VM:
|
|
|
3442
3981
|
norm_key = str(key)
|
|
3443
3982
|
existing = target.pairs.get(norm_key)
|
|
3444
3983
|
if existing is not None and existing.__class__.__name__ == 'SealedObject':
|
|
3445
|
-
raise
|
|
3984
|
+
raise VMRuntimeError(f"Cannot modify sealed map key: {key}")
|
|
3446
3985
|
target.pairs[norm_key] = args[1]
|
|
3447
3986
|
result = args[1]
|
|
3448
3987
|
else:
|
|
@@ -3464,21 +4003,27 @@ class VM:
|
|
|
3464
4003
|
result = target.get(args[0])
|
|
3465
4004
|
elif isinstance(target, dict) and args:
|
|
3466
4005
|
result = target.get(args[0])
|
|
4006
|
+
elif isinstance(target, dict) and method_name in _DICT_METHODS:
|
|
4007
|
+
result = _DICT_METHODS[method_name](target, args)
|
|
4008
|
+
elif isinstance(target, list) and method_name in _LIST_METHODS:
|
|
4009
|
+
result = _LIST_METHODS[method_name](target, args)
|
|
4010
|
+
elif isinstance(target, str) and method_name in _STR_METHODS:
|
|
4011
|
+
result = _STR_METHODS[method_name](target, args)
|
|
3467
4012
|
elif hasattr(target, "call_method"):
|
|
3468
4013
|
wrapped_args = [self._wrap_for_builtin(arg) for arg in args]
|
|
3469
4014
|
if _cached_security is not None:
|
|
3470
4015
|
try:
|
|
3471
4016
|
_cached_security._set_vm_action_context(True)
|
|
3472
|
-
except Exception:
|
|
3473
|
-
|
|
4017
|
+
except Exception as _e:
|
|
4018
|
+
_vm_warn("SECURITY", "Failed to set VM action context (async pre-call)", _e)
|
|
3474
4019
|
try:
|
|
3475
4020
|
result = target.call_method(method_name, wrapped_args)
|
|
3476
4021
|
finally:
|
|
3477
4022
|
if _cached_security is not None:
|
|
3478
4023
|
try:
|
|
3479
4024
|
_cached_security._set_vm_action_context(False)
|
|
3480
|
-
except Exception:
|
|
3481
|
-
|
|
4025
|
+
except Exception as _e:
|
|
4026
|
+
_vm_warn("SECURITY", "Failed to clear VM action context (async post-call)", _e)
|
|
3482
4027
|
else:
|
|
3483
4028
|
attr = self._get_cached_method(target, method_name)
|
|
3484
4029
|
if callable(attr):
|
|
@@ -3518,7 +4063,7 @@ class VM:
|
|
|
3518
4063
|
|
|
3519
4064
|
def _op_load_const(idx):
|
|
3520
4065
|
value = const(idx)
|
|
3521
|
-
if self.integer_pool and isinstance(value, int):
|
|
4066
|
+
if self.integer_pool and isinstance(value, int) and not isinstance(value, bool):
|
|
3522
4067
|
value = self.integer_pool.get(value)
|
|
3523
4068
|
elif self.string_pool and isinstance(value, str):
|
|
3524
4069
|
value = self.string_pool.get(value)
|
|
@@ -3642,11 +4187,15 @@ class VM:
|
|
|
3642
4187
|
|
|
3643
4188
|
def _op_build_map(count):
|
|
3644
4189
|
total = count if count is not None else 0
|
|
3645
|
-
|
|
4190
|
+
pairs = []
|
|
3646
4191
|
for _ in range(total):
|
|
3647
4192
|
val = stack_pop() if stack else None
|
|
3648
4193
|
key = stack_pop() if stack else None
|
|
3649
|
-
|
|
4194
|
+
pairs.append((key, val))
|
|
4195
|
+
pairs.reverse()
|
|
4196
|
+
result = {}
|
|
4197
|
+
for k, v in pairs:
|
|
4198
|
+
result[k] = v
|
|
3650
4199
|
stack_append(result)
|
|
3651
4200
|
|
|
3652
4201
|
def _op_index(_):
|
|
@@ -3662,6 +4211,30 @@ class VM:
|
|
|
3662
4211
|
stack.append(obj.get(key))
|
|
3663
4212
|
elif isinstance(obj, ZString):
|
|
3664
4213
|
stack.append(obj[idx])
|
|
4214
|
+
elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
|
|
4215
|
+
# EntityInstance field access
|
|
4216
|
+
idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
|
|
4217
|
+
if idx_str:
|
|
4218
|
+
val = obj.get(idx_str)
|
|
4219
|
+
# Unwrap Zexus objects to Python primitives for consistency
|
|
4220
|
+
if hasattr(val, 'value'):
|
|
4221
|
+
stack.append(val.value)
|
|
4222
|
+
else:
|
|
4223
|
+
stack.append(val)
|
|
4224
|
+
else:
|
|
4225
|
+
stack.append(None)
|
|
4226
|
+
elif isinstance(obj, dict):
|
|
4227
|
+
raw_idx = idx.value if hasattr(idx, 'value') else idx
|
|
4228
|
+
if raw_idx in obj:
|
|
4229
|
+
stack.append(obj[raw_idx])
|
|
4230
|
+
elif isinstance(raw_idx, int) and not isinstance(raw_idx, bool):
|
|
4231
|
+
keys = list(obj.keys())
|
|
4232
|
+
if 0 <= raw_idx < len(keys):
|
|
4233
|
+
stack.append(obj[keys[raw_idx]])
|
|
4234
|
+
else:
|
|
4235
|
+
stack.append(None)
|
|
4236
|
+
else:
|
|
4237
|
+
stack.append(None)
|
|
3665
4238
|
else:
|
|
3666
4239
|
# Fallback
|
|
3667
4240
|
if obj is None:
|
|
@@ -3726,8 +4299,8 @@ class VM:
|
|
|
3726
4299
|
env_dir = env_get("__DIR__", None)
|
|
3727
4300
|
if env_dir:
|
|
3728
4301
|
sandbox = _os.path.realpath(str(env_dir))
|
|
3729
|
-
except Exception:
|
|
3730
|
-
|
|
4302
|
+
except Exception as _e:
|
|
4303
|
+
_vm_warn("SANDBOX", "Failed to resolve sandbox __DIR__ for READ", _e)
|
|
3731
4304
|
|
|
3732
4305
|
if isinstance(path, str) and '\x00' not in path:
|
|
3733
4306
|
candidate = path
|
|
@@ -3767,7 +4340,7 @@ class VM:
|
|
|
3767
4340
|
"ADD": _op_add,
|
|
3768
4341
|
"SUB": _binary_op(lambda a, b: a - b),
|
|
3769
4342
|
"MUL": _binary_op(lambda a, b: a * b),
|
|
3770
|
-
"DIV": _binary_op(
|
|
4343
|
+
"DIV": _binary_op(_vm_div),
|
|
3771
4344
|
"MOD": _binary_op(lambda a, b: a % b if b != 0 else 0),
|
|
3772
4345
|
"POW": _binary_op(lambda a, b: _safe_pow(a, b)),
|
|
3773
4346
|
"NEG": _op_neg,
|
|
@@ -4273,29 +4846,74 @@ class VM:
|
|
|
4273
4846
|
stack.append(ok)
|
|
4274
4847
|
|
|
4275
4848
|
elif op_name == "FOR_ITER":
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
if
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4849
|
+
var_count = operand if operand else 1
|
|
4850
|
+
idx = stack.pop() if stack else 0
|
|
4851
|
+
obj = stack.pop() if stack else None
|
|
4852
|
+
try:
|
|
4853
|
+
if isinstance(obj, dict):
|
|
4854
|
+
keys = list(obj.keys())
|
|
4855
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
4856
|
+
key = keys[idx]
|
|
4857
|
+
if var_count == 2:
|
|
4858
|
+
stack.append(key)
|
|
4859
|
+
stack.append(obj[key])
|
|
4860
|
+
else:
|
|
4861
|
+
stack.append(key)
|
|
4862
|
+
else:
|
|
4863
|
+
for _ in range(var_count):
|
|
4864
|
+
stack.append(None)
|
|
4865
|
+
elif isinstance(obj, ZMap):
|
|
4866
|
+
keys = list(obj.pairs.keys())
|
|
4867
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
4868
|
+
key = keys[idx]
|
|
4869
|
+
if var_count == 2:
|
|
4870
|
+
stack.append(key)
|
|
4871
|
+
stack.append(obj.pairs[key])
|
|
4872
|
+
else:
|
|
4873
|
+
stack.append(key)
|
|
4874
|
+
else:
|
|
4875
|
+
for _ in range(var_count):
|
|
4876
|
+
stack.append(None)
|
|
4877
|
+
elif isinstance(obj, (list, ZList)):
|
|
4878
|
+
elems = obj.elements if isinstance(obj, ZList) else obj
|
|
4879
|
+
if isinstance(idx, int) and 0 <= idx < len(elems):
|
|
4880
|
+
if var_count == 2:
|
|
4881
|
+
stack.append(idx)
|
|
4882
|
+
stack.append(elems[idx])
|
|
4883
|
+
else:
|
|
4884
|
+
stack.append(elems[idx])
|
|
4885
|
+
else:
|
|
4886
|
+
for _ in range(var_count):
|
|
4887
|
+
stack.append(None)
|
|
4888
|
+
else:
|
|
4889
|
+
try:
|
|
4890
|
+
if var_count == 2:
|
|
4891
|
+
stack.append(idx)
|
|
4892
|
+
stack.append(obj[idx] if obj is not None else None)
|
|
4893
|
+
else:
|
|
4894
|
+
stack.append(obj[idx] if obj is not None else None)
|
|
4895
|
+
except Exception:
|
|
4896
|
+
for _ in range(var_count):
|
|
4897
|
+
stack.append(None)
|
|
4898
|
+
except Exception:
|
|
4899
|
+
for _ in range(var_count):
|
|
4900
|
+
stack.append(None)
|
|
4288
4901
|
|
|
4289
4902
|
elif op_name == "CALL_NAME":
|
|
4290
4903
|
name_idx, arg_count = operand
|
|
4291
4904
|
func_name = const(name_idx)
|
|
4292
4905
|
args = [stack.pop() if stack else None for _ in range(arg_count)][::-1] if arg_count else []
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4906
|
+
# ── fast-path for native builtins ──
|
|
4907
|
+
native_res = self._vm_native_call(func_name, args)
|
|
4908
|
+
if native_res is not _VM_NATIVE_MISS:
|
|
4909
|
+
stack.append(native_res)
|
|
4296
4910
|
else:
|
|
4297
|
-
|
|
4298
|
-
|
|
4911
|
+
fn = _resolve(func_name) or self.builtins.get(func_name)
|
|
4912
|
+
if fn is None:
|
|
4913
|
+
res = self._call_fallback_builtin(func_name, args)
|
|
4914
|
+
else:
|
|
4915
|
+
res = await self._invoke_callable_or_funcdesc(fn, args)
|
|
4916
|
+
stack.append(res)
|
|
4299
4917
|
|
|
4300
4918
|
elif op_name == "CALL_BUILTIN":
|
|
4301
4919
|
name_idx, arg_count = operand if isinstance(operand, (list, tuple)) else (operand, 0)
|
|
@@ -4367,7 +4985,12 @@ class VM:
|
|
|
4367
4985
|
b = stack.pop() if stack else 1; a = stack.pop() if stack else 0
|
|
4368
4986
|
if hasattr(a, 'value'): a = a.value
|
|
4369
4987
|
if hasattr(b, 'value'): b = b.value
|
|
4370
|
-
|
|
4988
|
+
if b == 0:
|
|
4989
|
+
raise VMRuntimeError("Division by zero")
|
|
4990
|
+
if isinstance(a, int) and isinstance(b, int) and not isinstance(a, bool) and not isinstance(b, bool):
|
|
4991
|
+
stack.append(a // b if a % b == 0 else a / b)
|
|
4992
|
+
else:
|
|
4993
|
+
stack.append(a / b)
|
|
4371
4994
|
elif op_name == "MOD":
|
|
4372
4995
|
b = _unwrap(stack.pop() if stack else 1)
|
|
4373
4996
|
a = _unwrap(stack.pop() if stack else 0)
|
|
@@ -4434,11 +5057,15 @@ class VM:
|
|
|
4434
5057
|
stack.append(elements)
|
|
4435
5058
|
elif op_name == "BUILD_MAP":
|
|
4436
5059
|
count = operand if operand is not None else 0
|
|
4437
|
-
|
|
5060
|
+
pairs = []
|
|
4438
5061
|
for _ in range(count):
|
|
4439
5062
|
val = stack.pop() if stack else None
|
|
4440
5063
|
key = stack.pop() if stack else None
|
|
4441
|
-
|
|
5064
|
+
pairs.append((key, val))
|
|
5065
|
+
pairs.reverse()
|
|
5066
|
+
result = {}
|
|
5067
|
+
for k, v in pairs:
|
|
5068
|
+
result[k] = v
|
|
4442
5069
|
stack.append(result)
|
|
4443
5070
|
elif op_name == "BUILD_SET":
|
|
4444
5071
|
count = operand if operand is not None else 0
|
|
@@ -4452,6 +5079,16 @@ class VM:
|
|
|
4452
5079
|
stack.append(obj.get(idx))
|
|
4453
5080
|
elif isinstance(obj, ZMap):
|
|
4454
5081
|
stack.append(obj.get(idx))
|
|
5082
|
+
elif hasattr(obj, 'data') and hasattr(obj, 'entity_def'):
|
|
5083
|
+
idx_str = idx.value if hasattr(idx, 'value') else str(idx) if idx is not None else None
|
|
5084
|
+
val = obj.get(idx_str) if idx_str else None
|
|
5085
|
+
stack.append(self._unwrap_after_builtin(val) if val is not None else None)
|
|
5086
|
+
elif isinstance(obj, dict) and isinstance(idx, int) and not isinstance(idx, bool):
|
|
5087
|
+
keys = list(obj.keys())
|
|
5088
|
+
if 0 <= idx < len(keys):
|
|
5089
|
+
stack.append(obj[keys[idx]])
|
|
5090
|
+
else:
|
|
5091
|
+
stack.append(None)
|
|
4455
5092
|
elif isinstance(obj, ZString):
|
|
4456
5093
|
stack.append(obj[idx])
|
|
4457
5094
|
else:
|
|
@@ -4459,6 +5096,59 @@ class VM:
|
|
|
4459
5096
|
stack.append(val)
|
|
4460
5097
|
except (IndexError, KeyError, TypeError):
|
|
4461
5098
|
stack.append(None)
|
|
5099
|
+
elif op_name == "FOR_ITER":
|
|
5100
|
+
var_count = operand if operand else 1
|
|
5101
|
+
idx = stack.pop() if stack else 0
|
|
5102
|
+
obj = stack.pop() if stack else None
|
|
5103
|
+
try:
|
|
5104
|
+
if isinstance(obj, dict):
|
|
5105
|
+
keys = list(obj.keys())
|
|
5106
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
5107
|
+
key = keys[idx]
|
|
5108
|
+
if var_count == 2:
|
|
5109
|
+
stack.append(key)
|
|
5110
|
+
stack.append(obj[key])
|
|
5111
|
+
else:
|
|
5112
|
+
stack.append(key)
|
|
5113
|
+
else:
|
|
5114
|
+
for _ in range(var_count):
|
|
5115
|
+
stack.append(None)
|
|
5116
|
+
elif isinstance(obj, ZMap):
|
|
5117
|
+
keys = list(obj.pairs.keys())
|
|
5118
|
+
if isinstance(idx, int) and 0 <= idx < len(keys):
|
|
5119
|
+
key = keys[idx]
|
|
5120
|
+
if var_count == 2:
|
|
5121
|
+
stack.append(key)
|
|
5122
|
+
stack.append(obj.pairs[key])
|
|
5123
|
+
else:
|
|
5124
|
+
stack.append(key)
|
|
5125
|
+
else:
|
|
5126
|
+
for _ in range(var_count):
|
|
5127
|
+
stack.append(None)
|
|
5128
|
+
elif isinstance(obj, (list, ZList)):
|
|
5129
|
+
elems = obj.elements if isinstance(obj, ZList) else obj
|
|
5130
|
+
if isinstance(idx, int) and 0 <= idx < len(elems):
|
|
5131
|
+
if var_count == 2:
|
|
5132
|
+
stack.append(idx)
|
|
5133
|
+
stack.append(elems[idx])
|
|
5134
|
+
else:
|
|
5135
|
+
stack.append(elems[idx])
|
|
5136
|
+
else:
|
|
5137
|
+
for _ in range(var_count):
|
|
5138
|
+
stack.append(None)
|
|
5139
|
+
else:
|
|
5140
|
+
try:
|
|
5141
|
+
if var_count == 2:
|
|
5142
|
+
stack.append(idx)
|
|
5143
|
+
stack.append(obj[idx] if obj is not None else None)
|
|
5144
|
+
else:
|
|
5145
|
+
stack.append(obj[idx] if obj is not None else None)
|
|
5146
|
+
except Exception:
|
|
5147
|
+
for _ in range(var_count):
|
|
5148
|
+
stack.append(None)
|
|
5149
|
+
except Exception:
|
|
5150
|
+
for _ in range(var_count):
|
|
5151
|
+
stack.append(None)
|
|
4462
5152
|
elif op_name == "SLICE":
|
|
4463
5153
|
end = _unwrap(stack.pop() if stack else None)
|
|
4464
5154
|
start = _unwrap(stack.pop() if stack else None)
|
|
@@ -4637,8 +5327,8 @@ class VM:
|
|
|
4637
5327
|
if callable(export_fn):
|
|
4638
5328
|
try:
|
|
4639
5329
|
export_fn(name, value)
|
|
4640
|
-
except Exception:
|
|
4641
|
-
|
|
5330
|
+
except Exception as _e:
|
|
5331
|
+
_vm_warn("EXPORT", f"export_fn('{name}') call failed (async path)", _e)
|
|
4642
5332
|
else:
|
|
4643
5333
|
self.env[name] = value
|
|
4644
5334
|
self._bump_env_version(name, value)
|
|
@@ -4655,8 +5345,8 @@ class VM:
|
|
|
4655
5345
|
env_dir = self.env.get("__DIR__", None)
|
|
4656
5346
|
if env_dir:
|
|
4657
5347
|
sandbox = _os_w.path.realpath(str(env_dir))
|
|
4658
|
-
except Exception:
|
|
4659
|
-
|
|
5348
|
+
except Exception as _e:
|
|
5349
|
+
_vm_warn("SANDBOX", "Failed to resolve sandbox __DIR__ for WRITE", _e)
|
|
4660
5350
|
|
|
4661
5351
|
path_str = str(path)
|
|
4662
5352
|
if "\x00" in path_str:
|
|
@@ -4848,7 +5538,7 @@ class VM:
|
|
|
4848
5538
|
ip = handler
|
|
4849
5539
|
else:
|
|
4850
5540
|
msg = exc.value if hasattr(exc, "value") else exc
|
|
4851
|
-
raise
|
|
5541
|
+
raise VMRuntimeError(str(msg))
|
|
4852
5542
|
|
|
4853
5543
|
elif op_name == "TX_COMMIT":
|
|
4854
5544
|
if self.env.get("_in_transaction", False):
|
|
@@ -4909,7 +5599,7 @@ class VM:
|
|
|
4909
5599
|
if self.env.get("_in_transaction", False):
|
|
4910
5600
|
self.env["_blockchain_state"] = dict(self.env.get("_tx_snapshot", {}))
|
|
4911
5601
|
self.env["_in_transaction"] = False
|
|
4912
|
-
raise
|
|
5602
|
+
raise VMRuntimeError(
|
|
4913
5603
|
f"Out of gas: required {amount}, remaining {current}")
|
|
4914
5604
|
self.env["_gas_remaining"] = new_gas
|
|
4915
5605
|
|
|
@@ -4930,7 +5620,7 @@ class VM:
|
|
|
4930
5620
|
tx_stack = self.env.get("_tx_stack", [])
|
|
4931
5621
|
if tx_stack: tx_stack.pop()
|
|
4932
5622
|
self.env["_in_transaction"] = bool(tx_stack)
|
|
4933
|
-
raise
|
|
5623
|
+
raise VMRuntimeError(f"Requirement failed: {message}")
|
|
4934
5624
|
|
|
4935
5625
|
elif op_name == "DEFINE_CONTRACT":
|
|
4936
5626
|
contract_obj = self._build_smart_contract(
|
|
@@ -4941,19 +5631,11 @@ class VM:
|
|
|
4941
5631
|
stack.append(contract_obj)
|
|
4942
5632
|
|
|
4943
5633
|
elif op_name == "DEFINE_ENTITY":
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
key_str = key_obj.value if hasattr(key_obj, 'value') else str(key_obj)
|
|
4950
|
-
members[key_str] = val_obj
|
|
4951
|
-
|
|
4952
|
-
name_obj = stack.pop() if stack else None
|
|
4953
|
-
# Create Entity (using Map for now, can be specialized Entity class later)
|
|
4954
|
-
members['_type'] = 'entity'
|
|
4955
|
-
members['_name'] = name_obj.value if hasattr(name_obj, 'value') else str(name_obj)
|
|
4956
|
-
stack.append(ZMap(members))
|
|
5634
|
+
entity_obj = self._build_entity_definition(
|
|
5635
|
+
operand, stack, lambda: stack.pop() if stack else None,
|
|
5636
|
+
lambda idx: consts[idx] if isinstance(idx, int) and 0 <= idx < len(consts) else idx,
|
|
5637
|
+
)
|
|
5638
|
+
stack.append(entity_obj)
|
|
4957
5639
|
|
|
4958
5640
|
elif op_name == "DEFINE_CAPABILITY":
|
|
4959
5641
|
name = stack.pop() if stack else None
|
|
@@ -5034,13 +5716,13 @@ class VM:
|
|
|
5034
5716
|
if owner is not None:
|
|
5035
5717
|
owner_val = owner.value if hasattr(owner, 'value') else str(owner)
|
|
5036
5718
|
if caller and caller != owner_val:
|
|
5037
|
-
raise
|
|
5719
|
+
raise VMRuntimeError(
|
|
5038
5720
|
f"Access denied: '{r_key}' restricted to owner only"
|
|
5039
5721
|
)
|
|
5040
5722
|
elif isinstance(restriction_val, (list, tuple)):
|
|
5041
5723
|
allowed = [a.value if hasattr(a, 'value') else str(a) for a in restriction_val]
|
|
5042
5724
|
if caller and caller not in allowed:
|
|
5043
|
-
raise
|
|
5725
|
+
raise VMRuntimeError(
|
|
5044
5726
|
f"Access denied: '{r_key}' restricted to allowed addresses"
|
|
5045
5727
|
)
|
|
5046
5728
|
|
|
@@ -5064,6 +5746,13 @@ class VM:
|
|
|
5064
5746
|
elapsed = time.perf_counter() - instr_start_time
|
|
5065
5747
|
self.profiler.measure_instruction(current_ip, elapsed)
|
|
5066
5748
|
except Exception as e:
|
|
5749
|
+
# Route Python exceptions through the try_stack (Zexus try/catch)
|
|
5750
|
+
if try_stack:
|
|
5751
|
+
handler_ip = try_stack.pop()
|
|
5752
|
+
exc_msg = e.args[0] if e.args else str(e)
|
|
5753
|
+
stack.append(str(exc_msg))
|
|
5754
|
+
ip = handler_ip
|
|
5755
|
+
continue
|
|
5067
5756
|
if self.env.get("_continue_on_error", False):
|
|
5068
5757
|
# Error Recovery Mode
|
|
5069
5758
|
if debug: print(f"[VM ERROR RECOVERY] {e}")
|
|
@@ -5082,9 +5771,9 @@ class VM:
|
|
|
5082
5771
|
if tx_stack:
|
|
5083
5772
|
tx_stack.pop()
|
|
5084
5773
|
self.env["_in_transaction"] = bool(tx_stack)
|
|
5085
|
-
except Exception:
|
|
5086
|
-
# Best-effort revert; never swallow the original exception.
|
|
5087
|
-
|
|
5774
|
+
except Exception as _e:
|
|
5775
|
+
# Best-effort revert; log but never swallow the original exception.
|
|
5776
|
+
_vm_warn("TX", "Transaction revert cleanup failed (best-effort)", _e)
|
|
5088
5777
|
raise
|
|
5089
5778
|
|
|
5090
5779
|
if profile_ops and opcode_counts is not None:
|
|
@@ -5099,10 +5788,15 @@ class VM:
|
|
|
5099
5788
|
# ==================== Helpers ====================
|
|
5100
5789
|
|
|
5101
5790
|
async def _invoke_callable_or_funcdesc(self, fn, args, is_constant=False):
|
|
5791
|
+
# 0. Entity construction
|
|
5792
|
+
from ..object import EntityDefinition as _EntityDef
|
|
5793
|
+
if isinstance(fn, _EntityDef):
|
|
5794
|
+
return self._construct_entity(fn, args)
|
|
5795
|
+
|
|
5102
5796
|
# 1. Function Descriptor (VM Bytecode Closure)
|
|
5103
5797
|
if isinstance(fn, dict) and "bytecode" in fn:
|
|
5104
5798
|
func_bc = fn["bytecode"]
|
|
5105
|
-
params = fn.get("params"
|
|
5799
|
+
params = fn.get("params") or fn.get("parameters") or []
|
|
5106
5800
|
is_async = fn.get("is_async", False)
|
|
5107
5801
|
# Use captured parent_vm (closure), fallback to self
|
|
5108
5802
|
parent_env = fn.get("parent_vm", self)
|
|
@@ -5241,6 +5935,76 @@ class VM:
|
|
|
5241
5935
|
print(f"[VM] fallback builtin '{name}' failed: {exc}")
|
|
5242
5936
|
return ZEvaluationError(f"Builtin '{name}' failed: {exc}")
|
|
5243
5937
|
|
|
5938
|
+
def _vm_native_call(self, name: str, args: list):
|
|
5939
|
+
"""Fast-path native handling of common builtins that operate on VM-native
|
|
5940
|
+
types (plain Python list/dict/str/int) WITHOUT wrapping to ZList/ZMap.
|
|
5941
|
+
|
|
5942
|
+
Returns _VM_NATIVE_MISS if no native handler exists, letting the caller
|
|
5943
|
+
fall through to the evaluator builtin path.
|
|
5944
|
+
"""
|
|
5945
|
+
if name == "push":
|
|
5946
|
+
# push(list, item) → return new list with item appended (non-mutating)
|
|
5947
|
+
if len(args) == 2:
|
|
5948
|
+
target = args[0]
|
|
5949
|
+
item = args[1]
|
|
5950
|
+
if isinstance(target, list):
|
|
5951
|
+
return target + [item]
|
|
5952
|
+
if isinstance(target, ZList):
|
|
5953
|
+
return ZList(target.elements + [item])
|
|
5954
|
+
return _VM_NATIVE_MISS
|
|
5955
|
+
if name == "append":
|
|
5956
|
+
# append(list, item) → mutate list in-place, return list
|
|
5957
|
+
if len(args) == 2:
|
|
5958
|
+
target = args[0]
|
|
5959
|
+
item = args[1]
|
|
5960
|
+
if isinstance(target, list):
|
|
5961
|
+
target.append(item)
|
|
5962
|
+
return target
|
|
5963
|
+
if isinstance(target, ZList):
|
|
5964
|
+
target.elements.append(item)
|
|
5965
|
+
return target
|
|
5966
|
+
return _VM_NATIVE_MISS
|
|
5967
|
+
if name == "length" or name == "len":
|
|
5968
|
+
if len(args) == 1:
|
|
5969
|
+
obj = args[0]
|
|
5970
|
+
if isinstance(obj, list):
|
|
5971
|
+
return len(obj)
|
|
5972
|
+
if isinstance(obj, ZList):
|
|
5973
|
+
return len(obj.elements)
|
|
5974
|
+
if isinstance(obj, (str, ZString)):
|
|
5975
|
+
val = obj.value if isinstance(obj, ZString) else obj
|
|
5976
|
+
return len(val)
|
|
5977
|
+
if isinstance(obj, (dict, ZMap)):
|
|
5978
|
+
if isinstance(obj, ZMap):
|
|
5979
|
+
return len(obj.pairs)
|
|
5980
|
+
return len(obj)
|
|
5981
|
+
if hasattr(obj, '__len__'):
|
|
5982
|
+
return len(obj)
|
|
5983
|
+
return 0
|
|
5984
|
+
return _VM_NATIVE_MISS
|
|
5985
|
+
if name == "str" or name == "string":
|
|
5986
|
+
if len(args) == 1:
|
|
5987
|
+
return self._format_print_value(args[0])
|
|
5988
|
+
return _VM_NATIVE_MISS
|
|
5989
|
+
if name == "range":
|
|
5990
|
+
# range(n) → [0..n-1] or range(start, end) → [start..end-1]
|
|
5991
|
+
if len(args) == 1:
|
|
5992
|
+
n = args[0]
|
|
5993
|
+
if isinstance(n, ZInteger): n = n.value
|
|
5994
|
+
if hasattr(n, 'value'): n = n.value
|
|
5995
|
+
if isinstance(n, (int, float)):
|
|
5996
|
+
return list(range(int(n)))
|
|
5997
|
+
elif len(args) == 2:
|
|
5998
|
+
a, b = args
|
|
5999
|
+
if isinstance(a, ZInteger): a = a.value
|
|
6000
|
+
if isinstance(b, ZInteger): b = b.value
|
|
6001
|
+
if hasattr(a, 'value'): a = a.value
|
|
6002
|
+
if hasattr(b, 'value'): b = b.value
|
|
6003
|
+
if isinstance(a, (int, float)) and isinstance(b, (int, float)):
|
|
6004
|
+
return list(range(int(a), int(b)))
|
|
6005
|
+
return _VM_NATIVE_MISS
|
|
6006
|
+
return _VM_NATIVE_MISS
|
|
6007
|
+
|
|
5244
6008
|
def profile_execution(self, bytecode, iterations: int = 1000) -> Dict[str, Any]:
|
|
5245
6009
|
"""Profile execution performance across available modes"""
|
|
5246
6010
|
import timeit
|
|
@@ -5464,6 +6228,15 @@ def create_vm(mode: str = "auto", use_jit: bool = True, **kwargs) -> VM:
|
|
|
5464
6228
|
return VM(mode=VMMode(mode.lower()), use_jit=use_jit, **kwargs)
|
|
5465
6229
|
|
|
5466
6230
|
|
|
6231
|
+
def _vm_div(a, b):
|
|
6232
|
+
"""Division with proper integer handling and zero-division errors."""
|
|
6233
|
+
if b == 0:
|
|
6234
|
+
raise VMRuntimeError("Division by zero")
|
|
6235
|
+
if isinstance(a, int) and not isinstance(a, bool) and isinstance(b, int) and not isinstance(b, bool):
|
|
6236
|
+
return a // b if a % b == 0 else a / b
|
|
6237
|
+
return a / b
|
|
6238
|
+
|
|
6239
|
+
|
|
5467
6240
|
def _safe_pow(a, b, *, max_exp: int = 10000, max_bits: int = 8192):
|
|
5468
6241
|
"""Bound exponentiation to avoid exponent DoS.
|
|
5469
6242
|
|