Commit 7f3d15aa authored by mythria's avatar mythria Committed by Commit bot

[Interpreter] Adds stackcheck in InterpreterPushArgsAndCall/Construct builtins.

In ignition, arguments to function calls and function constructors are
pushed onto the stack before calling the function. It is required to check
that stack does not overflow when pushing the arguments.

BUG=v8:4280
LOG=N

Review-Url: https://codereview.chromium.org/2335513004
Cr-Commit-Position: refs/heads/master@{#39470}
parent e1341ca8
......@@ -1173,15 +1173,33 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ Jump(lr);
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch,
Label* stack_overflow) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(scratch, Heap::kRealStackLimitRootIndex);
// Make scratch the space we have left. The stack might already be overflowed
// here which will cause scratch to become negative.
__ sub(scratch, sp, scratch);
// Check if the arguments will overflow the stack.
__ cmp(scratch, Operand(num_args, LSL, kPointerSizeLog2));
__ b(le, stack_overflow); // Signed comparison.
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register num_args, Register index,
Register limit, Register scratch) {
Register limit, Register scratch,
Label* stack_overflow) {
// Add a stack check before pushing arguments.
Generate_StackOverflowCheck(masm, num_args, scratch, stack_overflow);
// Find the address of the last argument.
__ mov(limit, num_args);
__ mov(limit, Operand(limit, LSL, kPointerSizeLog2));
__ sub(limit, index, limit);
// TODO(mythria): Add a stack check before pushing arguments.
Label loop_header, loop_check;
__ b(al, &loop_check);
__ bind(&loop_header);
......@@ -1203,11 +1221,12 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- r1 : the target to call (can be any Object).
// -----------------------------------
Label stack_overflow;
__ add(r3, r0, Operand(1)); // Add one for receiver.
// Push the arguments. r2, r4, r5 will be modified.
Generate_InterpreterPushArgs(masm, r3, r2, r4, r5);
Generate_InterpreterPushArgs(masm, r3, r2, r4, r5, &stack_overflow);
// Call the target.
if (function_type == CallableType::kJSFunction) {
......@@ -1220,6 +1239,13 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ bkpt(0);
}
}
// static
......@@ -1232,14 +1258,14 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// -- r2 : allocation site feedback if available, undefined otherwise.
// -- r4 : address of the first argument
// -----------------------------------
Label stack_overflow;
// Push a slot for the receiver to be constructed.
__ mov(ip, Operand::Zero());
__ push(ip);
// TODO(mythria): Add a stack check before pushing arguments.
// Push the arguments. r5, r4, r6 will be modified.
Generate_InterpreterPushArgs(masm, r0, r4, r5, r6);
Generate_InterpreterPushArgs(masm, r0, r4, r5, r6, &stack_overflow);
__ AssertUndefinedOrAllocationSite(r2, r5);
if (construct_type == CallableType::kJSFunction) {
......@@ -1257,6 +1283,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor with r0, r1, and r3 unmodified.
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ bkpt(0);
}
}
// static
......@@ -1268,18 +1301,26 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// -- r2 : allocation site feedback if available, undefined otherwise.
// -- r3 : address of the first argument
// -----------------------------------
Label stack_overflow;
__ add(r4, r0, Operand(1)); // Add one for receiver.
// TODO(mythria): Add a stack check before pushing arguments.
// Push the arguments. r3, r5, r6 will be modified.
Generate_InterpreterPushArgs(masm, r4, r3, r5, r6);
Generate_InterpreterPushArgs(masm, r4, r3, r5, r6, &stack_overflow);
// Array constructor expects constructor in r3. It is same as r1 here.
__ mov(r3, r1);
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ bkpt(0);
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2097,26 +2138,6 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
}
}
static void ArgumentAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- r0 : actual number of arguments
// -- r1 : function (passed through to callee)
// -- r2 : expected number of arguments
// -- r3 : new target (passed through to callee)
// -----------------------------------
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(r5, Heap::kRealStackLimitRootIndex);
// Make r5 the space we have left. The stack might already be overflowed
// here which will cause r5 to become negative.
__ sub(r5, sp, r5);
// Check if the arguments will overflow the stack.
__ cmp(r5, Operand(r2, LSL, kPointerSizeLog2));
__ b(le, stack_overflow); // Signed comparison.
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ SmiTag(r0);
__ mov(r4, Operand(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)));
......@@ -2801,7 +2822,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Enough parameters: actual >= expected
__ bind(&enough);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, r2, r5, &stack_overflow);
// Calculate copy start address into r0 and copy end address into r4.
// r0: actual number of arguments as a smi
......@@ -2834,7 +2855,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Too few parameters: Actual < expected
__ bind(&too_few);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, r2, r5, &stack_overflow);
// Calculate copy start address into r0 and copy end address is fp.
// r0: actual number of arguments as a smi
......
......@@ -1182,10 +1182,30 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ Ret();
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch,
Label* stack_overflow) {
// Check the stack for overflow.
// We are not trying to catch interruptions (e.g. debug break and
// preemption) here, so the "real stack limit" is checked.
Label enough_stack_space;
__ LoadRoot(scratch, Heap::kRealStackLimitRootIndex);
// Make scratch the space we have left. The stack might already be overflowed
// here which will cause scratch to become negative.
__ Sub(scratch, jssp, scratch);
// Check if the arguments will overflow the stack.
__ Cmp(scratch, Operand(num_args, LSL, kPointerSizeLog2));
__ B(le, stack_overflow);
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register num_args, Register index,
Register last_arg, Register stack_addr,
Register scratch) {
Register scratch,
Label* stack_overflow) {
// Add a stack check before pushing arguments.
Generate_StackOverflowCheck(masm, num_args, scratch, stack_overflow);
__ Mov(scratch, num_args);
__ lsl(scratch, scratch, kPointerSizeLog2);
__ sub(last_arg, index, scratch);
......@@ -1194,7 +1214,6 @@ static void Generate_InterpreterPushArgs(MacroAssembler* masm,
__ Mov(stack_addr, jssp);
__ Claim(scratch, 1);
// TODO(mythria): Add a stack check before pushing arguments.
// Push the arguments.
Label loop_header, loop_check;
__ B(&loop_check);
......@@ -1218,12 +1237,13 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- x1 : the target to call (can be any Object).
// -----------------------------------
Label stack_overflow;
// Add one for the receiver.
__ add(x3, x0, Operand(1));
// Push the arguments. x2, x4, x5, x6 will be modified.
Generate_InterpreterPushArgs(masm, x3, x2, x4, x5, x6);
Generate_InterpreterPushArgs(masm, x3, x2, x4, x5, x6, &stack_overflow);
// Call the target.
if (function_type == CallableType::kJSFunction) {
......@@ -1236,6 +1256,12 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();
}
}
// static
......@@ -1248,12 +1274,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// -- x2 : allocation site feedback if available, undefined otherwise
// -- x4 : address of the first argument
// -----------------------------------
Label stack_overflow;
// Push a slot for the receiver.
__ Push(xzr);
// Push the arguments. x5, x4, x6, x7 will be modified.
Generate_InterpreterPushArgs(masm, x0, x4, x5, x6, x7);
Generate_InterpreterPushArgs(masm, x0, x4, x5, x6, x7, &stack_overflow);
__ AssertUndefinedOrAllocationSite(x2, x6);
if (construct_type == CallableType::kJSFunction) {
......@@ -1270,6 +1297,12 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor with x0, x1, and x3 unmodified.
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();
}
}
// static
......@@ -1281,17 +1314,24 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// -- x2 : allocation site feedback if available, undefined otherwise.
// -- x3 : address of the first argument
// -----------------------------------
Label stack_overflow;
__ add(x4, x0, Operand(1)); // Add one for the receiver.
// Push the arguments. x3, x5, x6, x7 will be modified.
Generate_InterpreterPushArgs(masm, x4, x3, x5, x6, x7);
Generate_InterpreterPushArgs(masm, x4, x3, x5, x6, x7, &stack_overflow);
// Array constructor expects constructor in x3. It is same as call target.
__ mov(x3, x1);
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
__ Unreachable();
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2152,27 +2192,6 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
}
}
static void ArgumentAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- x0 : actual number of arguments
// -- x1 : function (passed through to callee)
// -- x2 : expected number of arguments
// -- x3 : new target (passed through to callee)
// -----------------------------------
// Check the stack for overflow.
// We are not trying to catch interruptions (e.g. debug break and
// preemption) here, so the "real stack limit" is checked.
Label enough_stack_space;
__ LoadRoot(x10, Heap::kRealStackLimitRootIndex);
// Make x10 the space we have left. The stack might already be overflowed
// here which will cause x10 to become negative.
__ Sub(x10, jssp, x10);
// Check if the arguments will overflow the stack.
__ Cmp(x10, Operand(x2, LSL, kPointerSizeLog2));
__ B(le, stack_overflow);
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ SmiTag(x10, x0);
__ Mov(x11, Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR));
......@@ -2887,7 +2906,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Enough parameters: actual >= expected
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, x2, x10, &stack_overflow);
Register copy_start = x10;
Register copy_end = x11;
......@@ -2934,7 +2953,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
Register scratch1 = x13, scratch2 = x14;
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, x2, x10, &stack_overflow);
__ Lsl(scratch2, argc_expected, kPointerSizeLog2);
__ Lsl(argc_actual, argc_actual, kPointerSizeLog2);
......
......@@ -710,6 +710,32 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ ret(0);
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch1, Register scratch2,
Label* stack_overflow,
bool include_receiver = false) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
ExternalReference real_stack_limit =
ExternalReference::address_of_real_stack_limit(masm->isolate());
__ mov(scratch1, Operand::StaticVariable(real_stack_limit));
// Make scratch2 the space we have left. The stack might already be overflowed
// here which will cause scratch2 to become negative.
__ mov(scratch2, esp);
__ sub(scratch2, scratch1);
// Make scratch1 the space we need for the array when it is unrolled onto the
// stack.
__ mov(scratch1, num_args);
if (include_receiver) {
__ add(scratch1, Immediate(1));
}
__ shl(scratch1, kPointerSizeLog2);
// Check if the arguments will overflow the stack.
__ cmp(scratch2, scratch1);
__ j(less_equal, stack_overflow); // Signed comparison.
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register array_limit,
Register start_address) {
......@@ -739,18 +765,25 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- edi : the target to call (can be any Object).
// -----------------------------------
Label stack_overflow;
// Compute the expected number of arguments.
__ mov(ecx, eax);
__ add(ecx, Immediate(1)); // Add one for receiver.
// Add a stack check before pushing the arguments. We need an extra register
// to perform a stack check. So push it onto the stack temporarily. This
// might cause stack overflow, but it will be detected by the check.
__ Push(edi);
Generate_StackOverflowCheck(masm, ecx, edx, edi, &stack_overflow);
__ Pop(edi);
// Pop return address to allow tail-call after pushing arguments.
__ Pop(edx);
// Find the address of the last argument.
__ mov(ecx, eax);
__ add(ecx, Immediate(1)); // Add one for receiver.
__ shl(ecx, kPointerSizeLog2);
__ neg(ecx);
__ add(ecx, ebx);
// TODO(mythria): Add a stack check before pushing the arguments.
Generate_InterpreterPushArgs(masm, ecx, ebx);
// Call the target.
......@@ -766,6 +799,17 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
// Pop the temporary registers, so that return address is on top of stack.
__ Pop(edi);
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
namespace {
......@@ -775,31 +819,31 @@ namespace {
// original values are restored after the use.
void Generate_InterpreterPushArgsAndReturnAddress(
MacroAssembler* masm, Register num_args, Register start_addr,
Register scratch1, Register scratch2, bool receiver_in_args) {
// Store scratch2, scratch1 onto the stack. We need to restore the original
// values
// so store scratch2, scratch1 temporarily on stack.
__ Push(scratch2);
__ Push(scratch1);
// We have to pop return address and the two temporary registers before we
// can push arguments onto the stack. we do not have any free registers so
// update the stack and copy them into the correct places on the stack.
Register scratch1, Register scratch2, bool receiver_in_args,
int num_slots_above_ret_addr, Label* stack_overflow) {
// We have to move return address and the temporary registers above it
// before we can copy arguments onto the stack. To achieve this:
// Step 1: Increment the stack pointer by num_args + 1 (for receiver).
// Step 2: Move the return address and values above it to the top of stack.
// Step 3: Copy the arguments into the correct locations.
// current stack =====> required stack layout
// | | | scratch1 | (2) <-- esp(1)
// | | | scratch2 | (3)
// | | | return addr | (4)
// | | | arg N | (5)
// | | | .... | (2)
// | | | scratch-n | (2)
// | | | return addr | (2)
// | | | arg N | (3)
// | scratch1 | <-- esp | .... |
// | scratch2 | | arg 0 |
// | .... | | arg 0 |
// | scratch-n | | arg 0 |
// | return addr | | receiver slot |
// First increment the stack pointer to the correct location.
// we need additional slots for arguments and the receiver.
// Step 1 - compute the required increment to the stack.
__ mov(scratch1, num_args);
__ shl(scratch1, kPointerSizeLog2);
__ add(scratch1, Immediate(kPointerSize));
// Check for stack overflow before we increment the stack pointer.
Generate_StackOverflowCheck(masm, num_args, scratch1, scratch2,
stack_overflow, true);
// Step 1 - Update the stack pointer. scratch1 already contains the required
// increment to the stack. i.e. num_args + 1 stack slots. This is computed in
// the Generate_StackOverflowCheck.
#ifdef _MSC_VER
// TODO(mythria): Move it to macro assembler.
......@@ -820,37 +864,27 @@ void Generate_InterpreterPushArgsAndReturnAddress(
__ bind(&update_stack_pointer);
#endif
// TODO(mythria): Add a stack check before updating the stack pointer.
// Step 1 - Update the stack pointer.
__ sub(esp, scratch1);
// Step 2 move scratch1 to the correct location. Move scratch1 first otherwise
// we may overwrite when num_args = 0 or 1, basically when the source and
// destination overlap. We at least need one extra slot for receiver,
// so no extra checks are required to avoid copy.
__ mov(scratch1,
Operand(esp, num_args, times_pointer_size, 1 * kPointerSize));
__ mov(Operand(esp, 0), scratch1);
// Step 3 move scratch2 to the correct location
__ mov(scratch1,
Operand(esp, num_args, times_pointer_size, 2 * kPointerSize));
__ mov(Operand(esp, 1 * kPointerSize), scratch1);
// Step 4 move return address to the correct location
__ mov(scratch1,
Operand(esp, num_args, times_pointer_size, 3 * kPointerSize));
__ mov(Operand(esp, 2 * kPointerSize), scratch1);
// Step 2 move return_address and slots above it to the correct locations.
// Move from top to bottom, otherwise we may overwrite when num_args = 0 or 1,
// basically when the source and destination overlap. We at least need one
// extra slot for receiver, so no extra checks are required to avoid copy.
for (int i = 0; i < num_slots_above_ret_addr + 1; i++) {
__ mov(scratch1,
Operand(esp, num_args, times_pointer_size, (i + 1) * kPointerSize));
__ mov(Operand(esp, i * kPointerSize), scratch1);
}
// Step 5 copy arguments to correct locations.
// Step 3 copy arguments to correct locations.
if (receiver_in_args) {
__ mov(scratch1, num_args);
__ add(scratch1, Immediate(1));
} else {
// Slot meant for receiver contains return address. Reset it so that
// we will not incorrectly interpret return address as an object.
__ mov(Operand(esp, num_args, times_pointer_size, 3 * kPointerSize),
__ mov(Operand(esp, num_args, times_pointer_size,
(num_slots_above_ret_addr + 1) * kPointerSize),
Immediate(0));
__ mov(scratch1, num_args);
}
......@@ -859,17 +893,14 @@ void Generate_InterpreterPushArgsAndReturnAddress(
__ jmp(&loop_check);
__ bind(&loop_header);
__ mov(scratch2, Operand(start_addr, 0));
__ mov(Operand(esp, scratch1, times_pointer_size, 2 * kPointerSize),
__ mov(Operand(esp, scratch1, times_pointer_size,
num_slots_above_ret_addr * kPointerSize),
scratch2);
__ sub(start_addr, Immediate(kPointerSize));
__ sub(scratch1, Immediate(1));
__ bind(&loop_check);
__ cmp(scratch1, Immediate(0));
__ j(greater, &loop_header, Label::kNear);
// Restore scratch1 and scratch2.
__ Pop(scratch1);
__ Pop(scratch2);
}
} // end anonymous namespace
......@@ -886,11 +917,20 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
Label stack_overflow;
// We need two scratch registers. Push edi and edx onto stack.
__ Push(edi);
__ Push(edx);
// Push arguments and move return address to the top of stack.
// The eax register is readonly. The ecx register will be modified. The edx
// and edi registers will be modified but restored to their original values.
Generate_InterpreterPushArgsAndReturnAddress(masm, eax, ecx, edx, edi, false);
Generate_InterpreterPushArgsAndReturnAddress(masm, eax, ecx, edx, edi, false,
2, &stack_overflow);
// Restore edi and edx
__ Pop(edx);
__ Pop(edi);
__ AssertUndefinedOrAllocationSite(ebx);
if (construct_type == CallableType::kJSFunction) {
......@@ -908,6 +948,18 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor with unmodified eax, edi, edx values.
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
// Pop the temporary registers, so that return address is on top of stack.
__ Pop(edx);
__ Pop(edi);
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
// static
......@@ -921,17 +973,36 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
Label stack_overflow;
// We need two scratch registers. Register edi is available, push edx onto
// stack.
__ Push(edx);
// Push arguments and move return address to the top of stack.
// The eax register is readonly. The ecx register will be modified. The edx
// and edi registers will be modified but restored to their original values.
Generate_InterpreterPushArgsAndReturnAddress(masm, eax, ecx, edx, ebx, true);
Generate_InterpreterPushArgsAndReturnAddress(masm, eax, ecx, edx, edi, true,
1, &stack_overflow);
// Restore edx.
__ Pop(edx);
// Array constructor expects constructor in edi. It is same as edx here.
__ Move(edi, edx);
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
__ bind(&stack_overflow);
{
// Pop the temporary registers, so that return address is on top of stack.
__ Pop(edx);
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2092,32 +2163,6 @@ void Builtins::Generate_StringConstructor_ConstructStub(MacroAssembler* masm) {
}
}
static void ArgumentsAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- eax : actual number of arguments
// -- ebx : expected number of arguments
// -- edx : new target (passed through to callee)
// -----------------------------------
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
ExternalReference real_stack_limit =
ExternalReference::address_of_real_stack_limit(masm->isolate());
__ mov(edi, Operand::StaticVariable(real_stack_limit));
// Make ecx the space we have left. The stack might already be overflowed
// here which will cause ecx to become negative.
__ mov(ecx, esp);
__ sub(ecx, edi);
// Make edi the space we need for the array when it is unrolled onto the
// stack.
__ mov(edi, ebx);
__ shl(edi, kPointerSizeLog2);
// Check if the arguments will overflow the stack.
__ cmp(ecx, edi);
__ j(less_equal, stack_overflow); // Signed comparison.
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ push(ebp);
__ mov(ebp, esp);
......@@ -2846,7 +2891,9 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Enough parameters: Actual >= expected.
__ bind(&enough);
EnterArgumentsAdaptorFrame(masm);
ArgumentsAdaptorStackCheck(masm, &stack_overflow);
// edi is used as a scratch register. It should be restored from the frame
// when needed.
Generate_StackOverflowCheck(masm, ebx, ecx, edi, &stack_overflow);
// Copy receiver and all expected arguments.
const int offset = StandardFrameConstants::kCallerSPOffset;
......@@ -2867,7 +2914,9 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Too few parameters: Actual < expected.
__ bind(&too_few);
EnterArgumentsAdaptorFrame(masm);
ArgumentsAdaptorStackCheck(masm, &stack_overflow);
// edi is used as a scratch register. It should be restored from the frame
// when needed.
Generate_StackOverflowCheck(masm, ebx, ecx, edi, &stack_overflow);
// Remember expected arguments in ecx.
__ mov(ecx, ebx);
......
......@@ -1171,13 +1171,33 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ Jump(ra);
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch1, Register scratch2,
Label* stack_overflow) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(scratch1, Heap::kRealStackLimitRootIndex);
// Make scratch1 the space we have left. The stack might already be overflowed
// here which will cause scratch1 to become negative.
__ subu(scratch1, sp, scratch1);
// Check if the arguments will overflow the stack.
__ sll(scratch2, num_args, kPointerSizeLog2);
// Signed comparison.
__ Branch(stack_overflow, le, scratch1, Operand(scratch2));
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register num_args, Register index,
Register scratch, Register last_addr) {
Register scratch, Register scratch2,
Label* stack_overflow) {
Generate_StackOverflowCheck(masm, num_args, scratch, scratch2,
stack_overflow);
// Find the address of the last argument.
__ mov(last_addr, num_args);
__ sll(last_addr, last_addr, kPointerSizeLog2);
__ Subu(last_addr, index, Operand(last_addr));
__ mov(scratch2, num_args);
__ sll(scratch2, scratch2, kPointerSizeLog2);
__ Subu(scratch2, index, Operand(scratch2));
// Push the arguments.
Label loop_header, loop_check;
......@@ -1187,7 +1207,7 @@ static void Generate_InterpreterPushArgs(MacroAssembler* masm,
__ Addu(index, index, Operand(-kPointerSize));
__ push(scratch);
__ bind(&loop_check);
__ Branch(&loop_header, gt, index, Operand(last_addr));
__ Branch(&loop_header, gt, index, Operand(scratch2));
}
// static
......@@ -1201,11 +1221,12 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- a1 : the target to call (can be any Object).
// -----------------------------------
Label stack_overflow;
__ Addu(t0, a0, Operand(1)); // Add one for receiver.
// This function modifies a2, t4 and t1.
Generate_InterpreterPushArgs(masm, t0, a2, t4, t1);
Generate_InterpreterPushArgs(masm, t0, a2, t4, t1, &stack_overflow);
// Call the target.
if (function_type == CallableType::kJSFunction) {
......@@ -1218,6 +1239,13 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
// static
......@@ -1230,12 +1258,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// -- a2 : allocation site feedback if available, undefined otherwise.
// -- t4 : address of the first argument
// -----------------------------------
Label stack_overflow;
// Push a slot for the receiver.
__ push(zero_reg);
// This function modified t4, t1 and t0.
Generate_InterpreterPushArgs(masm, a0, t4, t1, t0);
Generate_InterpreterPushArgs(masm, a0, t4, t1, t0, &stack_overflow);
__ AssertUndefinedOrAllocationSite(a2, t0);
if (construct_type == CallableType::kJSFunction) {
......@@ -1252,6 +1281,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor with a0, a1, and a3 unmodified.
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
// static
......@@ -1265,17 +1301,25 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
Label stack_overflow;
__ Addu(t0, a0, Operand(1)); // Add one for receiver.
// This function modifies a3, t4, and t1.
Generate_InterpreterPushArgs(masm, t0, a3, t1, t4);
Generate_InterpreterPushArgs(masm, t0, a3, t1, t4, &stack_overflow);
// ArrayConstructor stub expects constructor in a3. Set it here.
__ mov(a3, a1);
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2108,27 +2152,6 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
}
}
static void ArgumentAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- a0 : actual number of arguments
// -- a1 : function (passed through to callee)
// -- a2 : expected number of arguments
// -- a3 : new target (passed through to callee)
// -----------------------------------
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(t1, Heap::kRealStackLimitRootIndex);
// Make t1 the space we have left. The stack might already be overflowed
// here which will cause t1 to become negative.
__ subu(t1, sp, t1);
// Check if the arguments will overflow the stack.
__ sll(at, a2, kPointerSizeLog2);
// Signed comparison.
__ Branch(stack_overflow, le, t1, Operand(at));
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ sll(a0, a0, kSmiTagSize);
__ li(t0, Operand(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)));
......@@ -2871,7 +2894,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
// a3: new target (passed through to callee)
__ bind(&enough);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, a2, t1, at, &stack_overflow);
// Calculate copy start address into a0 and copy end address into t1.
__ Lsa(a0, fp, a0, kPointerSizeLog2 - kSmiTagSize);
......@@ -2901,7 +2924,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Too few parameters: Actual < expected.
__ bind(&too_few);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, a2, t1, at, &stack_overflow);
// Calculate copy start address into a0 and copy end address into t3.
// a0: actual number of arguments as a smi
......
......@@ -1163,13 +1163,33 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ Jump(ra);
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch1, Register scratch2,
Label* stack_overflow) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(scratch1, Heap::kRealStackLimitRootIndex);
// Make scratch1 the space we have left. The stack might already be overflowed
// here which will cause scratch1 to become negative.
__ dsubu(scratch1, sp, scratch1);
// Check if the arguments will overflow the stack.
__ dsll(scratch2, num_args, kPointerSizeLog2);
// Signed comparison.
__ Branch(stack_overflow, le, scratch1, Operand(scratch2));
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register num_args, Register index,
Register last_addr, Register scratch) {
Register scratch, Register scratch2,
Label* stack_overflow) {
// Generate_StackOverflowCheck(masm, num_args, scratch, scratch2,
// stack_overflow);
// Find the address of the last argument.
__ mov(last_addr, num_args);
__ dsll(last_addr, last_addr, kPointerSizeLog2);
__ Dsubu(last_addr, index, Operand(last_addr));
__ mov(scratch2, num_args);
__ dsll(scratch2, scratch2, kPointerSizeLog2);
__ Dsubu(scratch2, index, Operand(scratch2));
// Push the arguments.
Label loop_header, loop_check;
......@@ -1179,7 +1199,7 @@ static void Generate_InterpreterPushArgs(MacroAssembler* masm,
__ Daddu(index, index, Operand(-kPointerSize));
__ push(scratch);
__ bind(&loop_check);
__ Branch(&loop_header, gt, index, Operand(last_addr));
__ Branch(&loop_header, gt, index, Operand(scratch2));
}
// static
......@@ -1193,11 +1213,12 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- a1 : the target to call (can be any Object).
// -----------------------------------
Label stack_overflow;
__ Daddu(a3, a0, Operand(1)); // Add one for receiver.
// This function modifies a2, t0 and a4.
Generate_InterpreterPushArgs(masm, a3, a2, a4, t0);
Generate_InterpreterPushArgs(masm, a3, a2, a4, t0, &stack_overflow);
// Call the target.
if (function_type == CallableType::kJSFunction) {
......@@ -1210,6 +1231,13 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
// static
......@@ -1222,12 +1250,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// -- a2 : allocation site feedback if available, undefined otherwise.
// -- a4 : address of the first argument
// -----------------------------------
Label stack_overflow;
// Push a slot for the receiver.
__ push(zero_reg);
// This function modifies t0, a4 and a5.
Generate_InterpreterPushArgs(masm, a0, a4, a5, t0);
Generate_InterpreterPushArgs(masm, a0, a4, a5, t0, &stack_overflow);
__ AssertUndefinedOrAllocationSite(a2, t0);
if (construct_type == CallableType::kJSFunction) {
......@@ -1244,6 +1273,13 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor with a0, a1, and a3 unmodified.
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
// static
......@@ -1257,17 +1293,25 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
Label stack_overflow;
__ Daddu(a4, a0, Operand(1)); // Add one for receiver.
// This function modifies a3, a5 and a6.
Generate_InterpreterPushArgs(masm, a4, a3, a5, a6);
Generate_InterpreterPushArgs(masm, a4, a3, a5, a6, &stack_overflow);
// ArrayConstructor stub expects constructor in a3. Set it here.
__ mov(a3, a1);
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// Unreachable code.
__ break_(0xCC);
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2102,27 +2146,6 @@ void Builtins::Generate_ReflectConstruct(MacroAssembler* masm) {
}
}
static void ArgumentAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- a0 : actual number of arguments
// -- a1 : function (passed through to callee)
// -- a2 : expected number of arguments
// -- a3 : new target (passed through to callee)
// -----------------------------------
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(a5, Heap::kRealStackLimitRootIndex);
// Make a5 the space we have left. The stack might already be overflowed
// here which will cause a5 to become negative.
__ dsubu(a5, sp, a5);
// Check if the arguments will overflow the stack.
__ dsll(at, a2, kPointerSizeLog2);
// Signed comparison.
__ Branch(stack_overflow, le, a5, Operand(at));
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
// __ sll(a0, a0, kSmiTagSize);
__ dsll32(a0, a0, 0);
......@@ -2864,7 +2887,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
// a3: new target (passed through to callee)
__ bind(&enough);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, a2, a5, at, &stack_overflow);
// Calculate copy start address into a0 and copy end address into a4.
__ SmiScale(a0, a0, kPointerSizeLog2);
......@@ -2895,7 +2918,7 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Too few parameters: Actual < expected.
__ bind(&too_few);
EnterArgumentsAdaptorFrame(masm);
ArgumentAdaptorStackCheck(masm, &stack_overflow);
Generate_StackOverflowCheck(masm, a2, a5, at, &stack_overflow);
// Calculate copy start address into a0 and copy end address into a7.
// a0: actual number of arguments as a smi
......
......@@ -791,6 +791,26 @@ void Builtins::Generate_InterpreterMarkBaselineOnReturn(MacroAssembler* masm) {
__ ret(0);
}
static void Generate_StackOverflowCheck(MacroAssembler* masm, Register num_args,
Register scratch1, Register scratch2,
Label* stack_overflow) {
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
__ LoadRoot(scratch1, Heap::kRealStackLimitRootIndex);
__ movp(scratch2, rsp);
// Make scratch2 the space we have left. The stack might already be overflowed
// here which will cause scratch2 to become negative.
__ subp(scratch2, scratch1);
// Make scratch1 the space we need for the array when it is unrolled onto the
// stack.
__ movp(scratch1, num_args);
__ shlp(scratch1, Immediate(kPointerSizeLog2));
// Check if the arguments will overflow the stack.
__ cmpp(scratch2, scratch1);
__ j(less_equal, stack_overflow); // Signed comparison.
}
static void Generate_InterpreterPushArgs(MacroAssembler* masm,
Register num_args,
Register start_address,
......@@ -801,7 +821,6 @@ static void Generate_InterpreterPushArgs(MacroAssembler* masm,
__ negp(scratch);
__ addp(scratch, start_address);
// TODO(mythria): Add a stack check before pushing arguments.
// Push the arguments.
Label loop_header, loop_check;
__ j(always, &loop_check);
......@@ -824,14 +843,18 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
// they are to be pushed onto the stack.
// -- rdi : the target to call (can be any Object).
// -----------------------------------
// Pop return address to allow tail-call after pushing arguments.
__ PopReturnAddressTo(kScratchRegister);
Label stack_overflow;
// Number of values to be pushed.
__ Move(rcx, rax);
__ addp(rcx, Immediate(1)); // Add one for receiver.
// Add a stack check before pushing arguments.
Generate_StackOverflowCheck(masm, rcx, rdx, r8, &stack_overflow);
// Pop return address to allow tail-call after pushing arguments.
__ PopReturnAddressTo(kScratchRegister);
// rbx and rdx will be modified.
Generate_InterpreterPushArgs(masm, rcx, rbx, rdx);
......@@ -848,6 +871,14 @@ void Builtins::Generate_InterpreterPushArgsAndCallImpl(
tail_call_mode),
RelocInfo::CODE_TARGET);
}
// Throw stack overflow exception.
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
// static
......@@ -863,6 +894,10 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
Label stack_overflow;
// Add a stack check before pushing arguments.
Generate_StackOverflowCheck(masm, rax, r8, r9, &stack_overflow);
// Pop return address to allow tail-call after pushing arguments.
__ PopReturnAddressTo(kScratchRegister);
......@@ -892,6 +927,14 @@ void Builtins::Generate_InterpreterPushArgsAndConstructImpl(
// Call the constructor (rax, rdx, rdi passed on).
__ Jump(masm->isolate()->builtins()->Construct(), RelocInfo::CODE_TARGET);
}
// Throw stack overflow exception.
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
// static
......@@ -905,14 +948,18 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
// arguments should be consecutive above this, in the same order as
// they are to be pushed onto the stack.
// -----------------------------------
// Pop return address to allow tail-call after pushing arguments.
__ PopReturnAddressTo(kScratchRegister);
Label stack_overflow;
// Number of values to be pushed.
__ Move(r8, rax);
__ addp(r8, Immediate(1)); // Add one for receiver.
// Add a stack check before pushing arguments.
Generate_StackOverflowCheck(masm, r8, rdi, r9, &stack_overflow);
// Pop return address to allow tail-call after pushing arguments.
__ PopReturnAddressTo(kScratchRegister);
// rcx and rdi will be modified.
Generate_InterpreterPushArgs(masm, r8, rcx, rdi);
......@@ -924,6 +971,14 @@ void Builtins::Generate_InterpreterPushArgsAndConstructArray(
ArrayConstructorStub stub(masm->isolate());
__ TailCallStub(&stub);
// Throw stack overflow exception.
__ bind(&stack_overflow);
{
__ TailCallRuntime(Runtime::kThrowStackOverflow);
// This should be unreachable.
__ int3();
}
}
void Builtins::Generate_InterpreterEnterBytecodeDispatch(MacroAssembler* masm) {
......@@ -2062,32 +2117,6 @@ void Builtins::Generate_StringConstructor_ConstructStub(MacroAssembler* masm) {
}
}
static void ArgumentsAdaptorStackCheck(MacroAssembler* masm,
Label* stack_overflow) {
// ----------- S t a t e -------------
// -- rax : actual number of arguments
// -- rbx : expected number of arguments
// -- rdx : new target (passed through to callee)
// -- rdi : function (passed through to callee)
// -----------------------------------
// Check the stack for overflow. We are not trying to catch
// interruptions (e.g. debug break and preemption) here, so the "real stack
// limit" is checked.
Label okay;
__ LoadRoot(r8, Heap::kRealStackLimitRootIndex);
__ movp(rcx, rsp);
// Make rcx the space we have left. The stack might already be overflowed
// here which will cause rcx to become negative.
__ subp(rcx, r8);
// Make r8 the space we need for the array when it is unrolled onto the
// stack.
__ movp(r8, rbx);
__ shlp(r8, Immediate(kPointerSizeLog2));
// Check if the arguments will overflow the stack.
__ cmpp(rcx, r8);
__ j(less_equal, stack_overflow); // Signed comparison.
}
static void EnterArgumentsAdaptorFrame(MacroAssembler* masm) {
__ pushq(rbp);
__ movp(rbp, rsp);
......@@ -2183,7 +2212,8 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
{ // Enough parameters: Actual >= expected.
__ bind(&enough);
EnterArgumentsAdaptorFrame(masm);
ArgumentsAdaptorStackCheck(masm, &stack_overflow);
// The registers rcx and r8 will be modified. The register rbx is only read.
Generate_StackOverflowCheck(masm, rbx, rcx, r8, &stack_overflow);
// Copy receiver and all expected arguments.
const int offset = StandardFrameConstants::kCallerSPOffset;
......@@ -2204,7 +2234,8 @@ void Builtins::Generate_ArgumentsAdaptorTrampoline(MacroAssembler* masm) {
__ bind(&too_few);
EnterArgumentsAdaptorFrame(masm);
ArgumentsAdaptorStackCheck(masm, &stack_overflow);
// The registers rcx and r8 will be modified. The register rbx is only read.
Generate_StackOverflowCheck(masm, rbx, rcx, r8, &stack_overflow);
// Copy receiver and all actual arguments.
const int offset = StandardFrameConstants::kCallerSPOffset;
......
......@@ -40,7 +40,12 @@ try {
overflow();
} catch (e) {
var first_frame = e.stack.split("\n")[1]
assertTrue(first_frame.indexOf("stack-traces-overflow.js:30:18") > 0);
// The overflow can happen when pushing the arguments (in interpreter) or when
// the new function execution is starting. So the stack trace could either
// point to start of the function (stack-traces-overflow.js30:18) or to the
// location of call (stack-traces-overflow.js32:3).
assertTrue((first_frame.indexOf("stack-traces-overflow.js:30:18") > 0) ||
(first_frame.indexOf("stack-traces-overflow.js:32:3") > 0) );
}
// Test stack trace getter and setter.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment