using System.Collections.Generic;
using Esprima.Ast;
using Jint.Native;
using Jint.Runtime.Environments;
namespace Jint.Runtime.Interpreter.Statements
{
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-12.14
///
internal sealed class JintTryStatement : JintStatement
{
private readonly JintStatement _block;
private JintStatement _catch;
private readonly JintStatement _finalizer;
public JintTryStatement(Engine engine, TryStatement statement) : base(engine, statement)
{
_block = Build(engine, statement.Block);
if (statement.Finalizer != null)
{
_finalizer = Build(engine, _statement.Finalizer);
}
}
protected override Completion ExecuteInternal()
{
int callStackSizeBeforeExecution = _engine.CallStack.Count;
var b = _block.Execute();
if (b.Type == CompletionType.Throw)
{
// initialize lazily
if (_statement.Handler is not null && _catch is null)
{
_catch = Build(_engine, _statement.Handler.Body);
}
// execute catch
if (_statement.Handler is not null)
{
// Quick-patch for call stack not being unwinded when an exception is caught.
// Ideally, this should instead be solved by always popping the stack when returning
// from a call, regardless of whether it throws (i.e. CallStack.Pop() in finally clause
// in Engine.Call/Engine.Construct - however, that method currently breaks stack traces
// in error messages.
while (callStackSizeBeforeExecution < _engine.CallStack.Count)
{
_engine.CallStack.Pop();
}
// https://tc39.es/ecma262/#sec-runtime-semantics-catchclauseevaluation
var thrownValue = b.Value;
var oldEnv = _engine.ExecutionContext.LexicalEnvironment;
var catchEnv = JintEnvironment.NewDeclarativeEnvironment(_engine, oldEnv, catchEnvironment: true);
var boundNames = new List();
_statement.Handler.Param.GetBoundNames(boundNames);
foreach (var argName in boundNames)
{
catchEnv.CreateMutableBinding(argName, false);
}
_engine.UpdateLexicalEnvironment(catchEnv);
var catchParam = _statement.Handler?.Param;
catchParam.BindingInitialization(_engine, thrownValue, catchEnv);
b = _catch.Execute();
_engine.UpdateLexicalEnvironment(oldEnv);
}
}
if (_finalizer != null)
{
var f = _finalizer.Execute();
if (f.Type == CompletionType.Normal)
{
return b;
}
return f.UpdateEmpty(Undefined.Instance);
}
return b.UpdateEmpty(Undefined.Instance);
}
}
}