Skip to content

Commit 211cbfd

Browse files
committed
[jsinterp] Minimally handle arithmetic operator precedence
Resolves #32066
1 parent 26035bd commit 211cbfd

File tree

2 files changed

+48
-3
lines changed

2 files changed

+48
-3
lines changed

test/test_jsinterp.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,17 @@ def test_bitwise_operators_overflow(self):
505505
jsi = JSInterpreter('function x(){return 1236566549 << 5}')
506506
self.assertEqual(jsi.call_function('x'), 915423904)
507507

508+
def test_32066(self):
509+
jsi = JSInterpreter("function x(){return Math.pow(3, 5) + new Date('1970-01-01T08:01:42.000+08:00') / 1000 * -239 - -24205;}")
510+
self.assertEqual(jsi.call_function('x'), 70)
511+
512+
def test_unary_operators(self):
513+
jsi = JSInterpreter('function f(){return 2 - - - 2;}')
514+
self.assertEqual(jsi.call_function('f'), 0)
515+
# fails
516+
# jsi = JSInterpreter('function f(){return 2 + - + - - 2;}')
517+
# self.assertEqual(jsi.call_function('f'), 0)
518+
508519
""" # fails so far
509520
def test_packed(self):
510521
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}''')

youtube_dl/jsinterp.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import unicode_literals
22

3+
from functools import update_wrapper
34
import itertools
45
import json
56
import math
@@ -23,11 +24,23 @@
2324
)
2425

2526

27+
def wraps_op(op):
28+
29+
def update_and_rename_wrapper(w):
30+
f = update_wrapper(w, op)
31+
# fn names are str in both Py 2/3
32+
f.__name__ = str('JS_') + f.__name__
33+
return f
34+
35+
return update_and_rename_wrapper
36+
37+
2638
def _js_bit_op(op):
2739

2840
def zeroise(x):
2941
return 0 if x in (None, JS_Undefined) else x
3042

43+
@wraps_op(op)
3144
def wrapped(a, b):
3245
return op(zeroise(a), zeroise(b)) & 0xffffffff
3346

@@ -36,6 +49,7 @@ def wrapped(a, b):
3649

3750
def _js_arith_op(op):
3851

52+
@wraps_op(op)
3953
def wrapped(a, b):
4054
if JS_Undefined in (a, b):
4155
return float('nan')
@@ -66,6 +80,7 @@ def _js_exp(a, b):
6680

6781
def _js_eq_op(op):
6882

83+
@wraps_op(op)
6984
def wrapped(a, b):
7085
if set((a, b)) <= set((None, JS_Undefined)):
7186
return op(a, a)
@@ -76,6 +91,7 @@ def wrapped(a, b):
7691

7792
def _js_comp_op(op):
7893

94+
@wraps_op(op)
7995
def wrapped(a, b):
8096
if JS_Undefined in (a, b):
8197
return False
@@ -356,6 +372,7 @@ def _operator(self, op, left_val, right_expr, expr, local_vars, allow_recursion)
356372
return right_val
357373

358374
try:
375+
# print('Eval:', opfunc.__name__, left_val, right_val)
359376
return opfunc(left_val, right_val)
360377
except Exception as e:
361378
raise self.Exception('Failed to evaluate {left_val!r:.50} {op} {right_val!r:.50}'.format(**locals()), expr, cause=e)
@@ -395,6 +412,7 @@ def interpret_statement(self, stmt, local_vars, allow_recursion=100):
395412
raise self.Exception('Recursion limit reached')
396413
allow_recursion -= 1
397414

415+
# print('At: ' + stmt[:60])
398416
should_return = False
399417
# fails on (eg) if (...) stmt1; else stmt2;
400418
sub_statements = list(self._separate(stmt, ';')) or ['']
@@ -702,9 +720,24 @@ def interpret_statement(self, stmt, local_vars, allow_recursion=100):
702720
continue
703721

704722
right_expr = separated.pop()
705-
while op == '-' and len(separated) > 1 and not separated[-1].strip():
706-
right_expr = '-' + right_expr
707-
separated.pop()
723+
# handle operators that are both unary and binary, minimal BODMAS
724+
if op in ('+', '-'):
725+
undone = 0
726+
while len(separated) > 1 and not separated[-1].strip():
727+
undone += 1
728+
separated.pop()
729+
if op == '-' and undone % 2 != 0:
730+
right_expr = op + right_expr
731+
left_val = separated[-1]
732+
for dm_op in ('*', '%', '/', '**'):
733+
bodmas = tuple(self._separate(left_val, dm_op, skip_delims=skip_delim))
734+
if len(bodmas) > 1 and not bodmas[-1].strip():
735+
expr = op.join(separated) + op + right_expr
736+
right_expr = None
737+
break
738+
if right_expr is None:
739+
continue
740+
708741
left_val = self.interpret_expression(op.join(separated), local_vars, allow_recursion)
709742
return self._operator(op, left_val, right_expr, expr, local_vars, allow_recursion), should_return
710743

@@ -955,6 +988,7 @@ def valid_arg(y):
955988
def build_function(self, argnames, code, *global_stack):
956989
global_stack = list(global_stack) or [{}]
957990
argnames = tuple(argnames)
991+
# import pdb; pdb.set_trace()
958992

959993
def resf(args, kwargs={}, allow_recursion=100):
960994
global_stack[0].update(

0 commit comments

Comments
 (0)