From: dirkf Date: Fri, 21 Apr 2023 13:04:30 +0000 (+0100) Subject: [jsinterp] Minimally handle arithmetic operator precedence X-Git-Url: http://git.oshgnacknak.de/?a=commitdiff_plain;h=211cbfd5d46025a8e4d8f9f3d424aaada4698974;p=youtube-dl [jsinterp] Minimally handle arithmetic operator precedence Resolves #32066 --- diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py index 5d129433d..e121358d7 100644 --- a/test/test_jsinterp.py +++ b/test/test_jsinterp.py @@ -505,6 +505,17 @@ class TestJSInterpreter(unittest.TestCase): jsi = JSInterpreter('function x(){return 1236566549 << 5}') self.assertEqual(jsi.call_function('x'), 915423904) + def test_32066(self): + jsi = JSInterpreter("function x(){return Math.pow(3, 5) + new Date('1970-01-01T08:01:42.000+08:00') / 1000 * -239 - -24205;}") + self.assertEqual(jsi.call_function('x'), 70) + + def test_unary_operators(self): + jsi = JSInterpreter('function f(){return 2 - - - 2;}') + self.assertEqual(jsi.call_function('f'), 0) + # fails + # jsi = JSInterpreter('function f(){return 2 + - + - - 2;}') + # self.assertEqual(jsi.call_function('f'), 0) + """ # fails so far def test_packed(self): jsi = JSInterpreter('''function x(p,a,c,k,e,d){while(c--)if(k[c])p=p.replace(new RegExp('\\b'+c.toString(a)+'\\b','g'),k[c]);return p}''') diff --git a/youtube_dl/jsinterp.py b/youtube_dl/jsinterp.py index ab7d6f926..a06fc4ff5 100644 --- a/youtube_dl/jsinterp.py +++ b/youtube_dl/jsinterp.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from functools import update_wrapper import itertools import json import math @@ -23,11 +24,23 @@ from .compat import ( ) +def wraps_op(op): + + def update_and_rename_wrapper(w): + f = update_wrapper(w, op) + # fn names are str in both Py 2/3 + f.__name__ = str('JS_') + f.__name__ + return f + + return update_and_rename_wrapper + + def _js_bit_op(op): def zeroise(x): return 0 if x in (None, JS_Undefined) else x + @wraps_op(op) def wrapped(a, b): return op(zeroise(a), zeroise(b)) & 0xffffffff @@ -36,6 +49,7 @@ def _js_bit_op(op): def _js_arith_op(op): + @wraps_op(op) def wrapped(a, b): if JS_Undefined in (a, b): return float('nan') @@ -66,6 +80,7 @@ def _js_exp(a, b): def _js_eq_op(op): + @wraps_op(op) def wrapped(a, b): if set((a, b)) <= set((None, JS_Undefined)): return op(a, a) @@ -76,6 +91,7 @@ def _js_eq_op(op): def _js_comp_op(op): + @wraps_op(op) def wrapped(a, b): if JS_Undefined in (a, b): return False @@ -356,6 +372,7 @@ class JSInterpreter(object): return right_val try: + # print('Eval:', opfunc.__name__, left_val, right_val) return opfunc(left_val, right_val) except Exception as e: raise self.Exception('Failed to evaluate {left_val!r:.50} {op} {right_val!r:.50}'.format(**locals()), expr, cause=e) @@ -395,6 +412,7 @@ class JSInterpreter(object): raise self.Exception('Recursion limit reached') allow_recursion -= 1 + # print('At: ' + stmt[:60]) should_return = False # fails on (eg) if (...) stmt1; else stmt2; sub_statements = list(self._separate(stmt, ';')) or [''] @@ -702,9 +720,24 @@ class JSInterpreter(object): continue right_expr = separated.pop() - while op == '-' and len(separated) > 1 and not separated[-1].strip(): - right_expr = '-' + right_expr - separated.pop() + # handle operators that are both unary and binary, minimal BODMAS + if op in ('+', '-'): + undone = 0 + while len(separated) > 1 and not separated[-1].strip(): + undone += 1 + separated.pop() + if op == '-' and undone % 2 != 0: + right_expr = op + right_expr + left_val = separated[-1] + for dm_op in ('*', '%', '/', '**'): + bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim)) + if len(bodmas) > 1 and not bodmas[-1].strip(): + expr = op.join(separated) + op + right_expr + right_expr = None + break + if right_expr is None: + continue + left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion) return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion), should_return @@ -955,6 +988,7 @@ class JSInterpreter(object): def build_function(self, argnames, code, *global_stack): global_stack = list(global_stack) or [{}] argnames = tuple(argnames) + # import pdb; pdb.set_trace() def resf(args, kwargs={}, allow_recursion=100): global_stack[0].update(