JintUnaryExpression.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. using Esprima.Ast;
  2. using Jint.Extensions;
  3. using Jint.Native;
  4. using Jint.Runtime.Interop;
  5. using System.Collections.Concurrent;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Numerics;
  8. using System.Reflection;
  9. using Environment = Jint.Runtime.Environments.Environment;
  10. namespace Jint.Runtime.Interpreter.Expressions
  11. {
  12. internal sealed class JintUnaryExpression : JintExpression
  13. {
  14. private readonly record struct OperatorKey(string OperatorName, Type Operand);
  15. private static readonly ConcurrentDictionary<OperatorKey, MethodDescriptor?> _knownOperators = new();
  16. private readonly JintExpression _argument;
  17. private readonly UnaryOperator _operator;
  18. private JintUnaryExpression(UnaryExpression expression) : base(expression)
  19. {
  20. _argument = Build(expression.Argument);
  21. _operator = expression.Operator;
  22. }
  23. internal static JintExpression Build(UnaryExpression expression)
  24. {
  25. if (expression.AssociatedData is JsValue cached)
  26. {
  27. return new JintConstantExpression(expression, cached);
  28. }
  29. if (expression.Operator == UnaryOperator.TypeOf)
  30. {
  31. if (expression.Argument is Literal l)
  32. {
  33. var value = JintLiteralExpression.ConvertToJsValue(l);
  34. if (value is not null)
  35. {
  36. // valid for caching
  37. var evaluatedValue = JintTypeOfExpression.GetTypeOfString(value);
  38. expression.AssociatedData = evaluatedValue;
  39. return new JintConstantExpression(expression, evaluatedValue);
  40. }
  41. }
  42. return new JintTypeOfExpression(expression);
  43. }
  44. if (expression.Operator == UnaryOperator.Minus
  45. && expression.Argument is Literal literal)
  46. {
  47. var value = JintLiteralExpression.ConvertToJsValue(literal);
  48. if (value is not null)
  49. {
  50. // valid for caching
  51. var evaluatedValue = EvaluateMinus(value);
  52. expression.AssociatedData = evaluatedValue;
  53. return new JintConstantExpression(expression, evaluatedValue);
  54. }
  55. }
  56. return new JintUnaryExpression(expression);
  57. }
  58. private sealed class JintTypeOfExpression : JintExpression
  59. {
  60. private readonly JintExpression _argument;
  61. public JintTypeOfExpression(UnaryExpression expression) : base(expression)
  62. {
  63. _argument = Build(expression.Argument);
  64. }
  65. public override JsValue GetValue(EvaluationContext context)
  66. {
  67. // need to notify correct node when taking shortcut
  68. context.LastSyntaxElement = _expression;
  69. return (JsValue) EvaluateInternal(context);
  70. }
  71. protected override object EvaluateInternal(EvaluationContext context)
  72. {
  73. var engine = context.Engine;
  74. var result = _argument.Evaluate(context);
  75. JsValue v;
  76. if (result is Reference rf)
  77. {
  78. if (rf.IsUnresolvableReference)
  79. {
  80. engine._referencePool.Return(rf);
  81. return JsString.UndefinedString;
  82. }
  83. v = engine.GetValue(rf, true);
  84. }
  85. else
  86. {
  87. v = (JsValue) result;
  88. }
  89. return GetTypeOfString(v);
  90. }
  91. internal static JsString GetTypeOfString(JsValue v)
  92. {
  93. if (v.IsUndefined())
  94. {
  95. return JsString.UndefinedString;
  96. }
  97. if (v.IsNull())
  98. {
  99. return JsString.ObjectString;
  100. }
  101. switch (v.Type)
  102. {
  103. case Types.Boolean: return JsString.BooleanString;
  104. case Types.Number: return JsString.NumberString;
  105. case Types.BigInt: return JsString.BigIntString;
  106. case Types.String: return JsString.StringString;
  107. case Types.Symbol: return JsString.SymbolString;
  108. }
  109. if (v.IsCallable)
  110. {
  111. return JsString.FunctionString;
  112. }
  113. return JsString.ObjectString;
  114. }
  115. }
  116. public override JsValue GetValue(EvaluationContext context)
  117. {
  118. // need to notify correct node when taking shortcut
  119. context.LastSyntaxElement = _expression;
  120. return EvaluateJsValue(context);
  121. }
  122. protected override object EvaluateInternal(EvaluationContext context)
  123. {
  124. return EvaluateJsValue(context);
  125. }
  126. private JsValue EvaluateJsValue(EvaluationContext context)
  127. {
  128. var engine = context.Engine;
  129. switch (_operator)
  130. {
  131. case UnaryOperator.Plus:
  132. {
  133. var v = _argument.GetValue(context);
  134. if (context.OperatorOverloadingAllowed &&
  135. TryOperatorOverloading(context, v, "op_UnaryPlus", out var result))
  136. {
  137. return result;
  138. }
  139. return TypeConverter.ToNumber(v);
  140. }
  141. case UnaryOperator.Minus:
  142. {
  143. var v = _argument.GetValue(context);
  144. if (context.OperatorOverloadingAllowed &&
  145. TryOperatorOverloading(context, v, "op_UnaryNegation", out var result))
  146. {
  147. return result;
  148. }
  149. return EvaluateMinus(v);
  150. }
  151. case UnaryOperator.BitwiseNot:
  152. {
  153. var v = _argument.GetValue(context);
  154. if (context.OperatorOverloadingAllowed &&
  155. TryOperatorOverloading(context, v, "op_OnesComplement", out var result))
  156. {
  157. return result;
  158. }
  159. var value = TypeConverter.ToNumeric(v);
  160. if (value.IsNumber())
  161. {
  162. return JsNumber.Create(~TypeConverter.ToInt32(value));
  163. }
  164. return JsBigInt.Create(~value.AsBigInt());
  165. }
  166. case UnaryOperator.LogicalNot:
  167. {
  168. var v = _argument.GetValue(context);
  169. if (context.OperatorOverloadingAllowed &&
  170. TryOperatorOverloading(context, v, "op_LogicalNot", out var result))
  171. {
  172. return result;
  173. }
  174. return !TypeConverter.ToBoolean(v) ? JsBoolean.True : JsBoolean.False;
  175. }
  176. case UnaryOperator.Delete:
  177. // https://262.ecma-international.org/5.1/#sec-11.4.1
  178. if (_argument.Evaluate(context) is not Reference r)
  179. {
  180. return JsBoolean.True;
  181. }
  182. if (r.IsUnresolvableReference)
  183. {
  184. if (r.Strict)
  185. {
  186. ExceptionHelper.ThrowSyntaxError(engine.Realm, "Delete of an unqualified identifier in strict mode.");
  187. }
  188. engine._referencePool.Return(r);
  189. return JsBoolean.True;
  190. }
  191. var referencedName = r.ReferencedName;
  192. if (r.IsPropertyReference)
  193. {
  194. if (r.IsSuperReference)
  195. {
  196. ExceptionHelper.ThrowReferenceError(engine.Realm, r);
  197. }
  198. var o = TypeConverter.ToObject(engine.Realm, r.Base);
  199. var deleteStatus = o.Delete(referencedName);
  200. if (!deleteStatus)
  201. {
  202. if (r.Strict)
  203. {
  204. ExceptionHelper.ThrowTypeError(engine.Realm, $"Cannot delete property '{referencedName}' of {o}");
  205. }
  206. if (StrictModeScope.IsStrictModeCode && !r.Base.AsObject().GetProperty(referencedName).Configurable)
  207. {
  208. ExceptionHelper.ThrowTypeError(engine.Realm, $"Cannot delete property '{referencedName}' of {o}");
  209. }
  210. }
  211. engine._referencePool.Return(r);
  212. return deleteStatus ? JsBoolean.True : JsBoolean.False;
  213. }
  214. if (r.Strict)
  215. {
  216. ExceptionHelper.ThrowSyntaxError(engine.Realm);
  217. }
  218. var bindings = (Environment) r.Base;
  219. var property = referencedName;
  220. engine._referencePool.Return(r);
  221. return bindings.DeleteBinding(property.ToString()) ? JsBoolean.True : JsBoolean.False;
  222. case UnaryOperator.Void:
  223. _argument.GetValue(context);
  224. return JsValue.Undefined;
  225. default:
  226. ExceptionHelper.ThrowArgumentException();
  227. return null;
  228. }
  229. }
  230. private static JsValue EvaluateMinus(JsValue value)
  231. {
  232. if (value.IsInteger())
  233. {
  234. var asInteger = value.AsInteger();
  235. if (asInteger != 0)
  236. {
  237. return JsNumber.Create(asInteger * -1);
  238. }
  239. }
  240. value = TypeConverter.ToNumeric(value);
  241. if (value.IsNumber())
  242. {
  243. var n = ((JsNumber) value)._value;
  244. return double.IsNaN(n) ? JsNumber.DoubleNaN : JsNumber.Create(n * -1);
  245. }
  246. var bigInt = value.AsBigInt();
  247. return JsBigInt.Create(BigInteger.Negate(bigInt));
  248. }
  249. internal static bool TryOperatorOverloading(EvaluationContext context, JsValue value, string clrName, [NotNullWhen(true)] out JsValue? result)
  250. {
  251. var operand = value.ToObject();
  252. if (operand != null)
  253. {
  254. var operandType = operand.GetType();
  255. var arguments = new[] { value };
  256. var key = new OperatorKey(clrName, operandType);
  257. var method = _knownOperators.GetOrAdd(key, _ =>
  258. {
  259. MethodInfo? foundMethod = null;
  260. foreach (var x in operandType.GetOperatorOverloadMethods())
  261. {
  262. if (string.Equals(x.Name, clrName, StringComparison.Ordinal) && x.GetParameters().Length == 1)
  263. {
  264. foundMethod = x;
  265. break;
  266. }
  267. }
  268. if (foundMethod != null)
  269. {
  270. return new MethodDescriptor(foundMethod);
  271. }
  272. return null;
  273. });
  274. if (method != null)
  275. {
  276. result = method.Call(context.Engine, null, arguments);
  277. return true;
  278. }
  279. }
  280. result = null;
  281. return false;
  282. }
  283. }
  284. }