Ver código fonte

fix: bound getFieldValue array index parsing to avoid std::stoll throw

fszontagh 1 mês atrás
pai
commit
f23e3673b3

+ 23 - 8
src/runner/engine/script_engine.cpp

@@ -1572,6 +1572,23 @@ void ScriptEngine::setupBuiltinAPIs() {
             return JS_DupValue(ctx, argv[0]);
         }
 
+        // An array index has to fit a uint32_t. A longer run of digits is not a
+        // valid index, and std::stoll would throw std::out_of_range on it - a
+        // C++ exception unwinding through QuickJS C frames takes the runner
+        // process with it, so the parse is bounded rather than guarded after
+        // the fact.
+        auto parseIndex = [](const std::string& text, uint32_t& out) -> bool {
+            if (text.empty() || text.size() > 10) return false;
+            if (text.find_first_not_of("0123456789") != std::string::npos) return false;
+            uint64_t value = 0;
+            for (char c : text) {
+                value = value * 10 + (uint64_t)(c - '0');
+            }
+            if (value > 0xFFFFFFFFull) return false;
+            out = (uint32_t)value;
+            return true;
+        };
+
         std::vector<std::string> keys;
         size_t start = 0;
         while (true) {
@@ -1604,22 +1621,20 @@ void ScriptEngine::setupBuiltinAPIs() {
             // key[3] - step into the property, then index it
             std::string key = raw_key;
             bool has_index = false;
-            int64_t index = 0;
+            uint32_t index = 0;
             size_t bracket = raw_key.find('[');
             if (bracket != std::string::npos && raw_key.back() == ']') {
                 std::string index_text = raw_key.substr(bracket + 1, raw_key.size() - bracket - 2);
-                if (!index_text.empty() &&
-                    index_text.find_first_not_of("0123456789") == std::string::npos) {
+                if (parseIndex(index_text, index)) {
                     key = raw_key.substr(0, bracket);
-                    index = std::stoll(index_text);
                     has_index = true;
                 }
             }
 
             if (!key.empty()) {
-                bool numeric = key.find_first_not_of("0123456789") == std::string::npos;
-                if (numeric && is_array) {
-                    JSValue element = JS_GetPropertyUint32(ctx, current, (uint32_t)std::stoll(key));
+                uint32_t bare_index = 0;
+                if (is_array && parseIndex(key, bare_index)) {
+                    JSValue element = JS_GetPropertyUint32(ctx, current, bare_index);
                     JS_FreeValue(ctx, current);
                     current = element;
                 } else {
@@ -1638,7 +1653,7 @@ void ScriptEngine::setupBuiltinAPIs() {
                     JS_FreeValue(ctx, current);
                     return JS_UNDEFINED;
                 }
-                JSValue element = JS_GetPropertyUint32(ctx, current, (uint32_t)index);
+                JSValue element = JS_GetPropertyUint32(ctx, current, index);
                 JS_FreeValue(ctx, current);
                 current = element;
             }

+ 4 - 2
tests/nodes/utils-get-field-value.json

@@ -3,7 +3,7 @@
   "nodes": [
     {"id": "n1", "name": "Trigger", "type": "click-trigger", "position": {"x": 0, "y": 0}, "config": {}},
     {"id": "n2", "name": "Probe", "type": "code", "position": {"x": 0, "y": 100},
-     "config": {"code": "const d = { a: { b: { c: 7 } }, list: [{ id: 'x' }, { id: 'y' }], flat: [10, 20, 30] };\nconst g = smartbotic.utils.getFieldValue;\nreturn {\n  nested: g(d, 'a.b.c'),\n  bracket: g(d, 'list[1].id'),\n  numeric: g(d, 'flat.2'),\n  length: g(d, 'flat.length'),\n  missing: g(d, 'a.nope.c') === undefined,\n  throughNull: g({ a: null }, 'a.b') === undefined,\n  emptyPath: g(d, '').a.b.c,\n  notAnObject: g(d, 'a.b.c.d') === undefined\n};"}}
+     "config": {"code": "const d = { a: { b: { c: 7 } }, list: [{ id: 'x' }, { id: 'y' }], flat: [10, 20, 30] };\nconst g = smartbotic.utils.getFieldValue;\nreturn {\n  nested: g(d, 'a.b.c'),\n  bracket: g(d, 'list[1].id'),\n  numeric: g(d, 'flat.2'),\n  length: g(d, 'flat.length'),\n  missing: g(d, 'a.nope.c') === undefined,\n  throughNull: g({ a: null }, 'a.b') === undefined,\n  emptyPath: g(d, '').a.b.c,\n  notAnObject: g(d, 'a.b.c.d') === undefined,\n  hugeBracket: g(d, 'flat[99999999999999999999]') === undefined,\n  hugeNumeric: g(d, 'flat.99999999999999999999') === undefined\n};"}}
   ],
   "connections": [
     {"sourceNodeId": "n1", "sourceOutput": "main", "targetNodeId": "n2", "targetInput": "data"}
@@ -17,7 +17,9 @@
       "missing": true,
       "throughNull": true,
       "emptyPath": 7,
-      "notAnObject": true
+      "notAnObject": true,
+      "hugeBracket": true,
+      "hugeNumeric": true
     }}}
   }
 }