Upgrade V8 to 2.2.16
This commit is contained in:
Родитель
cec775a0de
Коммит
cd232a9cce
|
@ -1,3 +1,15 @@
|
|||
2010-06-07: Version 2.2.16
|
||||
|
||||
Remove the SetExternalStringDiposeCallback API. Changed the
|
||||
disposal of external string resources to call a virtual Dispose
|
||||
method on the resource.
|
||||
|
||||
Added support for more precise break points when debugging and
|
||||
stepping.
|
||||
|
||||
Memory usage improvements on all platforms.
|
||||
|
||||
|
||||
2010-06-07: Version 2.2.15
|
||||
|
||||
Add an API to control the disposal of external string resources.
|
||||
|
|
|
@ -134,6 +134,7 @@ namespace internal {
|
|||
|
||||
class Arguments;
|
||||
class Object;
|
||||
class Heap;
|
||||
class Top;
|
||||
|
||||
}
|
||||
|
@ -513,6 +514,7 @@ class V8EXPORT Data {
|
|||
class V8EXPORT ScriptData { // NOLINT
|
||||
public:
|
||||
virtual ~ScriptData() { }
|
||||
|
||||
/**
|
||||
* Pre-compiles the specified script (context-independent).
|
||||
*
|
||||
|
@ -521,6 +523,16 @@ class V8EXPORT ScriptData { // NOLINT
|
|||
*/
|
||||
static ScriptData* PreCompile(const char* input, int length);
|
||||
|
||||
/**
|
||||
* Pre-compiles the specified script (context-independent).
|
||||
*
|
||||
* NOTE: Pre-compilation using this method cannot happen on another thread
|
||||
* without using Lockers.
|
||||
*
|
||||
* \param source Script source code.
|
||||
*/
|
||||
static ScriptData* PreCompile(Handle<String> source);
|
||||
|
||||
/**
|
||||
* Load previous pre-compilation data.
|
||||
*
|
||||
|
@ -1026,12 +1038,24 @@ class V8EXPORT String : public Primitive {
|
|||
class V8EXPORT ExternalStringResourceBase {
|
||||
public:
|
||||
virtual ~ExternalStringResourceBase() {}
|
||||
|
||||
protected:
|
||||
ExternalStringResourceBase() {}
|
||||
|
||||
/**
|
||||
* Internally V8 will call this Dispose method when the external string
|
||||
* resource is no longer needed. The default implementation will use the
|
||||
* delete operator. This method can be overridden in subclasses to
|
||||
* control how allocated external string resources are disposed.
|
||||
*/
|
||||
virtual void Dispose() { delete this; }
|
||||
|
||||
private:
|
||||
// Disallow copying and assigning.
|
||||
ExternalStringResourceBase(const ExternalStringResourceBase&);
|
||||
void operator=(const ExternalStringResourceBase&);
|
||||
|
||||
friend class v8::internal::Heap;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1048,10 +1072,17 @@ class V8EXPORT String : public Primitive {
|
|||
* buffer.
|
||||
*/
|
||||
virtual ~ExternalStringResource() {}
|
||||
/** The string data from the underlying buffer.*/
|
||||
|
||||
/**
|
||||
* The string data from the underlying buffer.
|
||||
*/
|
||||
virtual const uint16_t* data() const = 0;
|
||||
/** The length of the string. That is, the number of two-byte characters.*/
|
||||
|
||||
/**
|
||||
* The length of the string. That is, the number of two-byte characters.
|
||||
*/
|
||||
virtual size_t length() const = 0;
|
||||
|
||||
protected:
|
||||
ExternalStringResource() {}
|
||||
};
|
||||
|
@ -1123,12 +1154,10 @@ class V8EXPORT String : public Primitive {
|
|||
/**
|
||||
* Creates a new external string using the data defined in the given
|
||||
* resource. When the external string is no longer live on V8's heap the
|
||||
* resource will be disposed. If a disposal callback has been set using
|
||||
* SetExternalStringDiposeCallback this callback will be called to dispose
|
||||
* the resource. Otherwise, V8 will dispose the resource using the C++ delete
|
||||
* operator. The caller of this function should not otherwise delete or
|
||||
* modify the resource. Neither should the underlying buffer be deallocated
|
||||
* or modified except through the destructor of the external string resource.
|
||||
* resource will be disposed by calling its Dispose method. The caller of
|
||||
* this function should not otherwise delete or modify the resource. Neither
|
||||
* should the underlying buffer be deallocated or modified except through the
|
||||
* destructor of the external string resource.
|
||||
*/
|
||||
static Local<String> NewExternal(ExternalStringResource* resource);
|
||||
|
||||
|
@ -1146,12 +1175,10 @@ class V8EXPORT String : public Primitive {
|
|||
/**
|
||||
* Creates a new external string using the ascii data defined in the given
|
||||
* resource. When the external string is no longer live on V8's heap the
|
||||
* resource will be disposed. If a disposal callback has been set using
|
||||
* SetExternalStringDiposeCallback this callback will be called to dispose
|
||||
* the resource. Otherwise, V8 will dispose the resource using the C++ delete
|
||||
* operator. The caller of this function should not otherwise delete or
|
||||
* modify the resource. Neither should the underlying buffer be deallocated
|
||||
* or modified except through the destructor of the external string resource.
|
||||
* resource will be disposed by calling its Dispose method. The caller of
|
||||
* this function should not otherwise delete or modify the resource. Neither
|
||||
* should the underlying buffer be deallocated or modified except through the
|
||||
* destructor of the external string resource.
|
||||
*/
|
||||
static Local<String> NewExternal(ExternalAsciiStringResource* resource);
|
||||
|
||||
|
@ -1251,10 +1278,6 @@ class V8EXPORT String : public Primitive {
|
|||
};
|
||||
|
||||
|
||||
typedef void (*ExternalStringDiposeCallback)
|
||||
(String::ExternalStringResourceBase* resource);
|
||||
|
||||
|
||||
/**
|
||||
* A JavaScript number value (ECMA-262, 4.3.20)
|
||||
*/
|
||||
|
@ -2471,15 +2494,6 @@ class V8EXPORT V8 {
|
|||
*/
|
||||
static void RemoveMessageListeners(MessageCallback that);
|
||||
|
||||
/**
|
||||
* Set a callback to be called when an external string is no longer live on
|
||||
* V8's heap. The resource will no longer be needed by V8 and the embedder
|
||||
* can dispose of if. If this callback is not set V8 will free the resource
|
||||
* using the C++ delete operator.
|
||||
*/
|
||||
static void SetExternalStringDiposeCallback(
|
||||
ExternalStringDiposeCallback that);
|
||||
|
||||
/**
|
||||
* Sets V8 flags from a string.
|
||||
*/
|
||||
|
|
|
@ -1120,6 +1120,12 @@ ScriptData* ScriptData::PreCompile(const char* input, int length) {
|
|||
}
|
||||
|
||||
|
||||
ScriptData* ScriptData::PreCompile(v8::Handle<String> source) {
|
||||
i::Handle<i::String> str = Utils::OpenHandle(*source);
|
||||
return i::PreParse(str, NULL, NULL);
|
||||
}
|
||||
|
||||
|
||||
ScriptData* ScriptData::New(const char* data, int length) {
|
||||
// Return an empty ScriptData if the length is obviously invalid.
|
||||
if (length % sizeof(unsigned) != 0) {
|
||||
|
@ -3692,14 +3698,6 @@ void V8::RemoveMessageListeners(MessageCallback that) {
|
|||
}
|
||||
|
||||
|
||||
void V8::SetExternalStringDiposeCallback(
|
||||
ExternalStringDiposeCallback callback) {
|
||||
if (IsDeadCheck("v8::V8::SetExternalStringDiposeCallback()"))
|
||||
return;
|
||||
i::Heap::SetExternalStringDiposeCallback(callback);
|
||||
}
|
||||
|
||||
|
||||
void V8::SetCounterFunction(CounterLookupCallback callback) {
|
||||
if (IsDeadCheck("v8::V8::SetCounterFunction()")) return;
|
||||
i::StatsTable::SetCounterFunction(callback);
|
||||
|
|
|
@ -116,9 +116,10 @@ Address* RelocInfo::target_reference_address() {
|
|||
|
||||
|
||||
Address RelocInfo::call_address() {
|
||||
ASSERT(IsPatchedReturnSequence());
|
||||
// The 2 instructions offset assumes patched return sequence.
|
||||
ASSERT(IsJSReturn(rmode()));
|
||||
// The 2 instructions offset assumes patched debug break slot or return
|
||||
// sequence.
|
||||
ASSERT((IsJSReturn(rmode()) && IsPatchedReturnSequence()) ||
|
||||
(IsDebugBreakSlot(rmode()) && IsPatchedDebugBreakSlotSequence()));
|
||||
return Memory::Address_at(pc_ + 2 * Assembler::kInstrSize);
|
||||
}
|
||||
|
||||
|
@ -168,6 +169,12 @@ bool RelocInfo::IsPatchedReturnSequence() {
|
|||
}
|
||||
|
||||
|
||||
bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
|
||||
Instr current_instr = Assembler::instr_at(pc_);
|
||||
return !Assembler::IsNop(current_instr, 2);
|
||||
}
|
||||
|
||||
|
||||
void RelocInfo::Visit(ObjectVisitor* visitor) {
|
||||
RelocInfo::Mode mode = rmode();
|
||||
if (mode == RelocInfo::EMBEDDED_OBJECT) {
|
||||
|
@ -178,8 +185,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
|
|||
visitor->VisitExternalReference(target_reference_address());
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
} else if (Debug::has_break_points() &&
|
||||
RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) {
|
||||
((RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(mode) &&
|
||||
IsPatchedDebugBreakSlotSequence()))) {
|
||||
visitor->VisitDebugTarget(this);
|
||||
#endif
|
||||
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
|
||||
|
|
|
@ -2040,6 +2040,13 @@ void Assembler::RecordJSReturn() {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::RecordDebugBreakSlot() {
|
||||
WriteRecordedPositions();
|
||||
CheckBuffer();
|
||||
RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
|
||||
}
|
||||
|
||||
|
||||
void Assembler::RecordComment(const char* msg) {
|
||||
if (FLAG_debug_code) {
|
||||
CheckBuffer();
|
||||
|
@ -2062,13 +2069,16 @@ void Assembler::RecordStatementPosition(int pos) {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::WriteRecordedPositions() {
|
||||
bool Assembler::WriteRecordedPositions() {
|
||||
bool written = false;
|
||||
|
||||
// Write the statement position if it is different from what was written last
|
||||
// time.
|
||||
if (current_statement_position_ != written_statement_position_) {
|
||||
CheckBuffer();
|
||||
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
|
||||
written_statement_position_ = current_statement_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Write the position if it is different from what was written last time and
|
||||
|
@ -2078,7 +2088,11 @@ void Assembler::WriteRecordedPositions() {
|
|||
CheckBuffer();
|
||||
RecordRelocInfo(RelocInfo::POSITION, current_position_);
|
||||
written_position_ = current_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Return whether something was written.
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
|
@ -2135,9 +2149,10 @@ void Assembler::GrowBuffer() {
|
|||
|
||||
void Assembler::RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data) {
|
||||
RelocInfo rinfo(pc_, rmode, data); // we do not try to reuse pool constants
|
||||
if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::STATEMENT_POSITION) {
|
||||
if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::DEBUG_BREAK_SLOT) {
|
||||
// Adjust code for new modes.
|
||||
ASSERT(RelocInfo::IsJSReturn(rmode)
|
||||
ASSERT(RelocInfo::IsDebugBreakSlot(rmode)
|
||||
|| RelocInfo::IsJSReturn(rmode)
|
||||
|| RelocInfo::IsComment(rmode)
|
||||
|| RelocInfo::IsPosition(rmode));
|
||||
// These modes do not need an entry in the constant pool.
|
||||
|
|
|
@ -629,22 +629,39 @@ class Assembler : public Malloced {
|
|||
// Distance between start of patched return sequence and the emitted address
|
||||
// to jump to.
|
||||
#ifdef USE_BLX
|
||||
// Return sequence is:
|
||||
// Patched return sequence is:
|
||||
// ldr ip, [pc, #0] @ emited address and start
|
||||
// blx ip
|
||||
static const int kPatchReturnSequenceAddressOffset = 0 * kInstrSize;
|
||||
#else
|
||||
// Return sequence is:
|
||||
// Patched return sequence is:
|
||||
// mov lr, pc @ start of sequence
|
||||
// ldr pc, [pc, #-4] @ emited address
|
||||
static const int kPatchReturnSequenceAddressOffset = kInstrSize;
|
||||
#endif
|
||||
|
||||
// Distance between start of patched debug break slot and the emitted address
|
||||
// to jump to.
|
||||
#ifdef USE_BLX
|
||||
// Patched debug break slot code is:
|
||||
// ldr ip, [pc, #0] @ emited address and start
|
||||
// blx ip
|
||||
static const int kPatchDebugBreakSlotAddressOffset = 0 * kInstrSize;
|
||||
#else
|
||||
// Patched debug break slot code is:
|
||||
// mov lr, pc @ start of sequence
|
||||
// ldr pc, [pc, #-4] @ emited address
|
||||
static const int kPatchDebugBreakSlotAddressOffset = kInstrSize;
|
||||
#endif
|
||||
|
||||
// Difference between address of current opcode and value read from pc
|
||||
// register.
|
||||
static const int kPcLoadDelta = 8;
|
||||
|
||||
static const int kJSReturnSequenceLength = 4;
|
||||
static const int kJSReturnSequenceInstructions = 4;
|
||||
static const int kDebugBreakSlotInstructions = 3;
|
||||
static const int kDebugBreakSlotLength =
|
||||
kDebugBreakSlotInstructions * kInstrSize;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Code generation
|
||||
|
@ -981,13 +998,16 @@ class Assembler : public Malloced {
|
|||
// Mark address of the ExitJSFrame code.
|
||||
void RecordJSReturn();
|
||||
|
||||
// Mark address of a debug break slot.
|
||||
void RecordDebugBreakSlot();
|
||||
|
||||
// Record a comment relocation entry that can be used by a disassembler.
|
||||
// Use --debug_code to enable.
|
||||
void RecordComment(const char* msg);
|
||||
|
||||
void RecordPosition(int pos);
|
||||
void RecordStatementPosition(int pos);
|
||||
void WriteRecordedPositions();
|
||||
bool WriteRecordedPositions();
|
||||
|
||||
int pc_offset() const { return pc_ - buffer_; }
|
||||
int current_position() const { return current_position_; }
|
||||
|
|
|
@ -386,8 +386,10 @@ void CodeGenerator::Generate(CompilationInfo* info) {
|
|||
// the add instruction the add will generate two instructions.
|
||||
int return_sequence_length =
|
||||
masm_->InstructionsGeneratedSince(&check_exit_codesize);
|
||||
CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
|
||||
return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
|
||||
CHECK(return_sequence_length ==
|
||||
Assembler::kJSReturnSequenceInstructions ||
|
||||
return_sequence_length ==
|
||||
Assembler::kJSReturnSequenceInstructions + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -226,7 +226,9 @@ class CodeGenerator: public AstVisitor {
|
|||
bool is_toplevel,
|
||||
Handle<Script> script);
|
||||
|
||||
static void RecordPositions(MacroAssembler* masm, int pos);
|
||||
static bool RecordPositions(MacroAssembler* masm,
|
||||
int pos,
|
||||
bool right_here = false);
|
||||
|
||||
// Accessors
|
||||
MacroAssembler* masm() { return masm_; }
|
||||
|
|
|
@ -57,7 +57,7 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
|
|||
// #endif
|
||||
// <debug break return code entry point address>
|
||||
// bktp 0
|
||||
CodePatcher patcher(rinfo()->pc(), 4);
|
||||
CodePatcher patcher(rinfo()->pc(), Assembler::kJSReturnSequenceInstructions);
|
||||
#ifdef USE_BLX
|
||||
patcher.masm()->ldr(v8::internal::ip, MemOperand(v8::internal::pc, 0));
|
||||
patcher.masm()->blx(v8::internal::ip);
|
||||
|
@ -73,17 +73,59 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
|
|||
// Restore the JS frame exit code.
|
||||
void BreakLocationIterator::ClearDebugBreakAtReturn() {
|
||||
rinfo()->PatchCode(original_rinfo()->pc(),
|
||||
Assembler::kJSReturnSequenceLength);
|
||||
Assembler::kJSReturnSequenceInstructions);
|
||||
}
|
||||
|
||||
|
||||
// A debug break in the exit code is identified by a call.
|
||||
// A debug break in the frame exit code is identified by the JS frame exit code
|
||||
// having been patched with a call instruction.
|
||||
bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) {
|
||||
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()));
|
||||
return rinfo->IsPatchedReturnSequence();
|
||||
}
|
||||
|
||||
|
||||
bool BreakLocationIterator::IsDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
// Check whether the debug break slot instructions have been patched.
|
||||
return rinfo()->IsPatchedDebugBreakSlotSequence();
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::SetDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
// Patch the code changing the debug break slot code from
|
||||
// mov r2, r2
|
||||
// mov r2, r2
|
||||
// mov r2, r2
|
||||
// to a call to the debug break slot code.
|
||||
// #if USE_BLX
|
||||
// ldr ip, [pc, #0]
|
||||
// blx ip
|
||||
// #else
|
||||
// mov lr, pc
|
||||
// ldr pc, [pc, #-4]
|
||||
// #endif
|
||||
// <debug break slot code entry point address>
|
||||
CodePatcher patcher(rinfo()->pc(), Assembler::kDebugBreakSlotInstructions);
|
||||
#ifdef USE_BLX
|
||||
patcher.masm()->ldr(v8::internal::ip, MemOperand(v8::internal::pc, 0));
|
||||
patcher.masm()->blx(v8::internal::ip);
|
||||
#else
|
||||
patcher.masm()->mov(v8::internal::lr, v8::internal::pc);
|
||||
patcher.masm()->ldr(v8::internal::pc, MemOperand(v8::internal::pc, -4));
|
||||
#endif
|
||||
patcher.Emit(Debug::debug_break_return()->entry());
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::ClearDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
rinfo()->PatchCode(original_rinfo()->pc(),
|
||||
Assembler::kDebugBreakSlotInstructions);
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
|
@ -220,10 +262,33 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
|
|||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlot(MacroAssembler* masm) {
|
||||
// Generate enough nop's to make space for a call instruction. Avoid emitting
|
||||
// the constant pool in the debug break slot code.
|
||||
Assembler::BlockConstPoolScope block_const_pool(masm);
|
||||
Label check_codesize;
|
||||
__ bind(&check_codesize);
|
||||
__ RecordDebugBreakSlot();
|
||||
for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
|
||||
__ nop(2);
|
||||
}
|
||||
ASSERT_EQ(Assembler::kDebugBreakSlotInstructions,
|
||||
masm->InstructionsGeneratedSince(&check_codesize));
|
||||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
|
||||
// In the places where a debug break slot is inserted no registers can contain
|
||||
// object pointers.
|
||||
Generate_DebugBreakCallHelper(masm, 0);
|
||||
}
|
||||
|
||||
|
||||
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
|
||||
masm->Abort("LiveEdit frame dropping is not supported on arm");
|
||||
}
|
||||
|
||||
|
||||
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
||||
masm->Abort("LiveEdit frame dropping is not supported on arm");
|
||||
}
|
||||
|
|
|
@ -238,8 +238,10 @@ void FullCodeGenerator::EmitReturnSequence(int position) {
|
|||
// add instruction the add will generate two instructions.
|
||||
int return_sequence_length =
|
||||
masm_->InstructionsGeneratedSince(&check_exit_codesize);
|
||||
CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
|
||||
return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
|
||||
CHECK(return_sequence_length ==
|
||||
Assembler::kJSReturnSequenceInstructions ||
|
||||
return_sequence_length ==
|
||||
Assembler::kJSReturnSequenceInstructions + 1);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -449,6 +449,11 @@ const char* RelocInfo::RelocModeName(RelocInfo::Mode rmode) {
|
|||
return "external reference";
|
||||
case RelocInfo::INTERNAL_REFERENCE:
|
||||
return "internal reference";
|
||||
case RelocInfo::DEBUG_BREAK_SLOT:
|
||||
#ifndef ENABLE_DEBUGGER_SUPPORT
|
||||
UNREACHABLE();
|
||||
#endif
|
||||
return "debug break slot";
|
||||
case RelocInfo::NUMBER_OF_MODES:
|
||||
UNREACHABLE();
|
||||
return "number_of_modes";
|
||||
|
@ -513,6 +518,7 @@ void RelocInfo::Verify() {
|
|||
case STATEMENT_POSITION:
|
||||
case EXTERNAL_REFERENCE:
|
||||
case INTERNAL_REFERENCE:
|
||||
case DEBUG_BREAK_SLOT:
|
||||
case NONE:
|
||||
break;
|
||||
case NUMBER_OF_MODES:
|
||||
|
|
|
@ -118,9 +118,9 @@ class RelocInfo BASE_EMBEDDED {
|
|||
enum Mode {
|
||||
// Please note the order is important (see IsCodeTarget, IsGCRelocMode).
|
||||
CONSTRUCT_CALL, // code target that is a call to a JavaScript constructor.
|
||||
CODE_TARGET_CONTEXT, // code target used for contextual loads.
|
||||
DEBUG_BREAK,
|
||||
CODE_TARGET, // code target which is not any of the above.
|
||||
CODE_TARGET_CONTEXT, // Code target used for contextual loads.
|
||||
DEBUG_BREAK, // Code target for the debugger statement.
|
||||
CODE_TARGET, // Code target which is not any of the above.
|
||||
EMBEDDED_OBJECT,
|
||||
|
||||
// Everything after runtime_entry (inclusive) is not GC'ed.
|
||||
|
@ -129,6 +129,7 @@ class RelocInfo BASE_EMBEDDED {
|
|||
COMMENT,
|
||||
POSITION, // See comment for kNoPosition above.
|
||||
STATEMENT_POSITION, // See comment for kNoPosition above.
|
||||
DEBUG_BREAK_SLOT, // Additional code inserted for debug break slot.
|
||||
EXTERNAL_REFERENCE, // The address of an external C++ function.
|
||||
INTERNAL_REFERENCE, // An address inside the same function.
|
||||
|
||||
|
@ -174,6 +175,9 @@ class RelocInfo BASE_EMBEDDED {
|
|||
static inline bool IsInternalReference(Mode mode) {
|
||||
return mode == INTERNAL_REFERENCE;
|
||||
}
|
||||
static inline bool IsDebugBreakSlot(Mode mode) {
|
||||
return mode == DEBUG_BREAK_SLOT;
|
||||
}
|
||||
static inline int ModeMask(Mode mode) { return 1 << mode; }
|
||||
|
||||
// Accessors
|
||||
|
@ -243,6 +247,10 @@ class RelocInfo BASE_EMBEDDED {
|
|||
// with a call to the debugger.
|
||||
INLINE(bool IsPatchedReturnSequence());
|
||||
|
||||
// Check whether this debug break slot has been patched with a call to the
|
||||
// debugger.
|
||||
INLINE(bool IsPatchedDebugBreakSlotSequence());
|
||||
|
||||
#ifdef ENABLE_DISASSEMBLER
|
||||
// Printing
|
||||
static const char* RelocModeName(Mode rmode);
|
||||
|
|
|
@ -1469,10 +1469,14 @@ class Conditional: public Expression {
|
|||
public:
|
||||
Conditional(Expression* condition,
|
||||
Expression* then_expression,
|
||||
Expression* else_expression)
|
||||
Expression* else_expression,
|
||||
int then_expression_position,
|
||||
int else_expression_position)
|
||||
: condition_(condition),
|
||||
then_expression_(then_expression),
|
||||
else_expression_(else_expression) { }
|
||||
else_expression_(else_expression),
|
||||
then_expression_position_(then_expression_position),
|
||||
else_expression_position_(else_expression_position) { }
|
||||
|
||||
virtual void Accept(AstVisitor* v);
|
||||
|
||||
|
@ -1482,10 +1486,15 @@ class Conditional: public Expression {
|
|||
Expression* then_expression() const { return then_expression_; }
|
||||
Expression* else_expression() const { return else_expression_; }
|
||||
|
||||
int then_expression_position() { return then_expression_position_; }
|
||||
int else_expression_position() { return else_expression_position_; }
|
||||
|
||||
private:
|
||||
Expression* condition_;
|
||||
Expression* then_expression_;
|
||||
Expression* else_expression_;
|
||||
int then_expression_position_;
|
||||
int else_expression_position_;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -1360,10 +1360,17 @@ static void Generate_StubNoRegisters_DebugBreak(MacroAssembler* masm) {
|
|||
Debug::GenerateStubNoRegistersDebugBreak(masm);
|
||||
}
|
||||
|
||||
|
||||
static void Generate_Slot_DebugBreak(MacroAssembler* masm) {
|
||||
Debug::GenerateSlotDebugBreak(masm);
|
||||
}
|
||||
|
||||
|
||||
static void Generate_PlainReturn_LiveEdit(MacroAssembler* masm) {
|
||||
Debug::GeneratePlainReturnLiveEdit(masm);
|
||||
}
|
||||
|
||||
|
||||
static void Generate_FrameDropper_LiveEdit(MacroAssembler* masm) {
|
||||
Debug::GenerateFrameDropperLiveEdit(masm);
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ enum BuiltinExtraArguments {
|
|||
V(KeyedLoadIC_DebugBreak, KEYED_LOAD_IC, DEBUG_BREAK) \
|
||||
V(StoreIC_DebugBreak, STORE_IC, DEBUG_BREAK) \
|
||||
V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK) \
|
||||
V(Slot_DebugBreak, BUILTIN, DEBUG_BREAK) \
|
||||
V(PlainReturn_LiveEdit, BUILTIN, DEBUG_BREAK) \
|
||||
V(FrameDropper_LiveEdit, BUILTIN, DEBUG_BREAK)
|
||||
#else
|
||||
|
|
|
@ -286,6 +286,7 @@ template <int> class StaticAssertionHelper { };
|
|||
#define ASSERT(condition) CHECK(condition)
|
||||
#define ASSERT_EQ(v1, v2) CHECK_EQ(v1, v2)
|
||||
#define ASSERT_NE(v1, v2) CHECK_NE(v1, v2)
|
||||
#define ASSERT_GE(v1, v2) CHECK_GE(v1, v2)
|
||||
#define STATIC_ASSERT(test) STATIC_CHECK(test)
|
||||
#define SLOW_ASSERT(condition) if (FLAG_enable_slow_asserts) CHECK(condition)
|
||||
#else
|
||||
|
@ -293,6 +294,7 @@ template <int> class StaticAssertionHelper { };
|
|||
#define ASSERT(condition) ((void) 0)
|
||||
#define ASSERT_EQ(v1, v2) ((void) 0)
|
||||
#define ASSERT_NE(v1, v2) ((void) 0)
|
||||
#define ASSERT_GE(v1, v2) ((void) 0)
|
||||
#define STATIC_ASSERT(test) ((void) 0)
|
||||
#define SLOW_ASSERT(condition) ((void) 0)
|
||||
#endif
|
||||
|
|
|
@ -415,32 +415,41 @@ CodeGenerator::ConditionAnalysis CodeGenerator::AnalyzeCondition(
|
|||
}
|
||||
|
||||
|
||||
void CodeGenerator::RecordPositions(MacroAssembler* masm, int pos) {
|
||||
bool CodeGenerator::RecordPositions(MacroAssembler* masm,
|
||||
int pos,
|
||||
bool right_here) {
|
||||
if (pos != RelocInfo::kNoPosition) {
|
||||
masm->RecordStatementPosition(pos);
|
||||
masm->RecordPosition(pos);
|
||||
if (right_here) {
|
||||
return masm->WriteRecordedPositions();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CodeGenerator::CodeForFunctionPosition(FunctionLiteral* fun) {
|
||||
if (FLAG_debug_info) RecordPositions(masm(), fun->start_position());
|
||||
if (FLAG_debug_info) RecordPositions(masm(), fun->start_position(), false);
|
||||
}
|
||||
|
||||
|
||||
void CodeGenerator::CodeForReturnPosition(FunctionLiteral* fun) {
|
||||
if (FLAG_debug_info) RecordPositions(masm(), fun->end_position());
|
||||
if (FLAG_debug_info) RecordPositions(masm(), fun->end_position(), false);
|
||||
}
|
||||
|
||||
|
||||
void CodeGenerator::CodeForStatementPosition(Statement* stmt) {
|
||||
if (FLAG_debug_info) RecordPositions(masm(), stmt->statement_pos());
|
||||
if (FLAG_debug_info) RecordPositions(masm(), stmt->statement_pos(), false);
|
||||
}
|
||||
|
||||
|
||||
void CodeGenerator::CodeForDoWhileConditionPosition(DoWhileStatement* stmt) {
|
||||
if (FLAG_debug_info) RecordPositions(masm(), stmt->condition_position());
|
||||
if (FLAG_debug_info)
|
||||
RecordPositions(masm(), stmt->condition_position(), false);
|
||||
}
|
||||
|
||||
|
||||
void CodeGenerator::CodeForSourcePosition(int pos) {
|
||||
if (FLAG_debug_info && pos != RelocInfo::kNoPosition) {
|
||||
masm()->RecordPosition(pos);
|
||||
|
|
|
@ -79,6 +79,8 @@ class CompilationSubCache {
|
|||
// young generation.
|
||||
void Age();
|
||||
|
||||
bool HasFunction(SharedFunctionInfo* function_info);
|
||||
|
||||
// GC support.
|
||||
void Iterate(ObjectVisitor* v);
|
||||
|
||||
|
@ -204,6 +206,27 @@ Handle<CompilationCacheTable> CompilationSubCache::GetTable(int generation) {
|
|||
}
|
||||
|
||||
|
||||
bool CompilationSubCache::HasFunction(SharedFunctionInfo* function_info) {
|
||||
if (function_info->script()->IsUndefined() ||
|
||||
Script::cast(function_info->script())->source()->IsUndefined()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String* source =
|
||||
String::cast(Script::cast(function_info->script())->source());
|
||||
// Check all generations.
|
||||
for (int generation = 0; generation < generations(); generation++) {
|
||||
if (tables_[generation]->IsUndefined()) continue;
|
||||
|
||||
CompilationCacheTable* table =
|
||||
CompilationCacheTable::cast(tables_[generation]);
|
||||
Object* object = table->Lookup(source);
|
||||
if (object->IsSharedFunctionInfo()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void CompilationSubCache::Age() {
|
||||
// Age the generations implicitly killing off the oldest.
|
||||
for (int i = generations_ - 1; i > 0; i--) {
|
||||
|
@ -506,6 +529,11 @@ void CompilationCache::Clear() {
|
|||
}
|
||||
|
||||
|
||||
bool CompilationCache::HasFunction(SharedFunctionInfo* function_info) {
|
||||
return script.HasFunction(function_info);
|
||||
}
|
||||
|
||||
|
||||
void CompilationCache::Iterate(ObjectVisitor* v) {
|
||||
for (int i = 0; i < kSubCacheCount; i++) {
|
||||
subcaches[i]->Iterate(v);
|
||||
|
|
|
@ -79,6 +79,9 @@ class CompilationCache {
|
|||
// Clear the cache - also used to initialize the cache at startup.
|
||||
static void Clear();
|
||||
|
||||
|
||||
static bool HasFunction(SharedFunctionInfo* function_info);
|
||||
|
||||
// GC support.
|
||||
static void Iterate(ObjectVisitor* v);
|
||||
|
||||
|
|
|
@ -601,6 +601,7 @@ void Compiler::SetFunctionInfo(Handle<SharedFunctionInfo> function_info,
|
|||
lit->has_only_simple_this_property_assignments(),
|
||||
*lit->this_property_assignments());
|
||||
function_info->set_try_full_codegen(lit->try_full_codegen());
|
||||
function_info->set_allows_lazy_compilation(lit->AllowsLazyCompilation());
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -294,7 +294,8 @@ CpuProfile* CpuProfiler::StopProfiling(Object* security_token, String* title) {
|
|||
int CpuProfiler::GetProfilesCount() {
|
||||
ASSERT(singleton_ != NULL);
|
||||
// The count of profiles doesn't depend on a security token.
|
||||
return singleton_->profiles_->Profiles(CodeEntry::kNoSecurityToken)->length();
|
||||
return singleton_->profiles_->Profiles(
|
||||
TokenEnumerator::kNoSecurityToken)->length();
|
||||
}
|
||||
|
||||
|
||||
|
@ -380,7 +381,7 @@ void CpuProfiler::CodeDeleteEvent(Address from) {
|
|||
|
||||
|
||||
void CpuProfiler::FunctionCreateEvent(JSFunction* function) {
|
||||
int security_token_id = CodeEntry::kNoSecurityToken;
|
||||
int security_token_id = TokenEnumerator::kNoSecurityToken;
|
||||
if (function->unchecked_context()->IsContext()) {
|
||||
security_token_id = singleton_->token_enumerator_->GetTokenId(
|
||||
function->context()->global_context()->security_token());
|
||||
|
@ -476,7 +477,8 @@ void CpuProfiler::StartProcessorIfNotStarted() {
|
|||
CpuProfile* CpuProfiler::StopCollectingProfile(const char* title) {
|
||||
const double actual_sampling_rate = generator_->actual_sampling_rate();
|
||||
StopProcessorIfLastProfile();
|
||||
CpuProfile* result = profiles_->StopProfiling(CodeEntry::kNoSecurityToken,
|
||||
CpuProfile* result =
|
||||
profiles_->StopProfiling(TokenEnumerator::kNoSecurityToken,
|
||||
title,
|
||||
actual_sampling_rate);
|
||||
if (result != NULL) {
|
||||
|
|
|
@ -576,6 +576,9 @@ Handle<String> Shell::ReadFile(const char* name) {
|
|||
void Shell::RunShell() {
|
||||
LineEditor* editor = LineEditor::Get();
|
||||
printf("V8 version %s [console: %s]\n", V8::GetVersion(), editor->name());
|
||||
if (i::FLAG_debugger) {
|
||||
printf("JavaScript debugger enabled\n");
|
||||
}
|
||||
editor->Open();
|
||||
while (true) {
|
||||
Locker locker;
|
||||
|
|
|
@ -129,10 +129,14 @@ void BreakLocationIterator::Next() {
|
|||
ASSERT(statement_position_ >= 0);
|
||||
}
|
||||
|
||||
if (IsDebugBreakSlot()) {
|
||||
// There is always a possible break point at a debug break slot.
|
||||
break_point_++;
|
||||
return;
|
||||
} else if (RelocInfo::IsCodeTarget(rmode())) {
|
||||
// Check for breakable code target. Look in the original code as setting
|
||||
// break points can cause the code targets in the running (debugged) code to
|
||||
// be of a different kind than in the original code.
|
||||
if (RelocInfo::IsCodeTarget(rmode())) {
|
||||
// break points can cause the code targets in the running (debugged) code
|
||||
// to be of a different kind than in the original code.
|
||||
Address target = original_rinfo()->target_address();
|
||||
Code* code = Code::GetCodeFromTargetAddress(target);
|
||||
if ((code->is_inline_cache_stub() &&
|
||||
|
@ -329,6 +333,9 @@ void BreakLocationIterator::SetDebugBreak() {
|
|||
if (RelocInfo::IsJSReturn(rmode())) {
|
||||
// Patch the frame exit code with a break point.
|
||||
SetDebugBreakAtReturn();
|
||||
} else if (IsDebugBreakSlot()) {
|
||||
// Patch the code in the break slot.
|
||||
SetDebugBreakAtSlot();
|
||||
} else {
|
||||
// Patch the IC call.
|
||||
SetDebugBreakAtIC();
|
||||
|
@ -346,6 +353,9 @@ void BreakLocationIterator::ClearDebugBreak() {
|
|||
if (RelocInfo::IsJSReturn(rmode())) {
|
||||
// Restore the frame exit code.
|
||||
ClearDebugBreakAtReturn();
|
||||
} else if (IsDebugBreakSlot()) {
|
||||
// Restore the code in the break slot.
|
||||
ClearDebugBreakAtSlot();
|
||||
} else {
|
||||
// Patch the IC call.
|
||||
ClearDebugBreakAtIC();
|
||||
|
@ -417,6 +427,8 @@ bool BreakLocationIterator::HasBreakPoint() {
|
|||
bool BreakLocationIterator::IsDebugBreak() {
|
||||
if (RelocInfo::IsJSReturn(rmode())) {
|
||||
return IsDebugBreakAtReturn();
|
||||
} else if (IsDebugBreakSlot()) {
|
||||
return IsDebugBreakAtSlot();
|
||||
} else {
|
||||
return Debug::IsDebugBreak(rinfo()->target_address());
|
||||
}
|
||||
|
@ -478,6 +490,11 @@ bool BreakLocationIterator::IsDebuggerStatement() {
|
|||
}
|
||||
|
||||
|
||||
bool BreakLocationIterator::IsDebugBreakSlot() {
|
||||
return RelocInfo::DEBUG_BREAK_SLOT == rmode();
|
||||
}
|
||||
|
||||
|
||||
Object* BreakLocationIterator::BreakPointObjects() {
|
||||
return debug_info_->GetBreakPointObjects(code_position());
|
||||
}
|
||||
|
@ -573,6 +590,7 @@ bool Debug::break_on_uncaught_exception_ = true;
|
|||
|
||||
Handle<Context> Debug::debug_context_ = Handle<Context>();
|
||||
Code* Debug::debug_break_return_ = NULL;
|
||||
Code* Debug::debug_break_slot_ = NULL;
|
||||
|
||||
|
||||
void ScriptCache::Add(Handle<Script> script) {
|
||||
|
@ -656,6 +674,10 @@ void Debug::Setup(bool create_heap_objects) {
|
|||
debug_break_return_ =
|
||||
Builtins::builtin(Builtins::Return_DebugBreak);
|
||||
ASSERT(debug_break_return_->IsCode());
|
||||
// Get code to handle debug break in debug break slots.
|
||||
debug_break_slot_ =
|
||||
Builtins::builtin(Builtins::Slot_DebugBreak);
|
||||
ASSERT(debug_break_slot_->IsCode());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -824,6 +846,7 @@ void Debug::PreemptionWhileInDebugger() {
|
|||
|
||||
void Debug::Iterate(ObjectVisitor* v) {
|
||||
v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_return_)));
|
||||
v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_slot_)));
|
||||
}
|
||||
|
||||
|
||||
|
@ -1631,16 +1654,21 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
|
|||
// break point is still active after processing the break point.
|
||||
Address addr = frame->pc() - Assembler::kCallTargetAddressOffset;
|
||||
|
||||
// Check if the location is at JS exit.
|
||||
// Check if the location is at JS exit or debug break slot.
|
||||
bool at_js_return = false;
|
||||
bool break_at_js_return_active = false;
|
||||
bool at_debug_break_slot = false;
|
||||
RelocIterator it(debug_info->code());
|
||||
while (!it.done()) {
|
||||
while (!it.done() && !at_js_return && !at_debug_break_slot) {
|
||||
if (RelocInfo::IsJSReturn(it.rinfo()->rmode())) {
|
||||
at_js_return = (it.rinfo()->pc() ==
|
||||
addr - Assembler::kPatchReturnSequenceAddressOffset);
|
||||
break_at_js_return_active = it.rinfo()->IsPatchedReturnSequence();
|
||||
}
|
||||
if (RelocInfo::IsDebugBreakSlot(it.rinfo()->rmode())) {
|
||||
at_debug_break_slot = (it.rinfo()->pc() ==
|
||||
addr - Assembler::kPatchDebugBreakSlotAddressOffset);
|
||||
}
|
||||
it.next();
|
||||
}
|
||||
|
||||
|
@ -1657,25 +1685,30 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
|
|||
// Move back to where the call instruction sequence started.
|
||||
thread_local_.after_break_target_ =
|
||||
addr - Assembler::kPatchReturnSequenceAddressOffset;
|
||||
} else {
|
||||
// Check if there still is a debug break call at the target address. If the
|
||||
// break point has been removed it will have disappeared. If it have
|
||||
// disappeared don't try to look in the original code as the running code
|
||||
// will have the right address. This takes care of the case where the last
|
||||
// break point is removed from the function and therefore no "original code"
|
||||
// is available. If the debug break call is still there find the address in
|
||||
// the original code.
|
||||
if (IsDebugBreak(Assembler::target_address_at(addr))) {
|
||||
// If the break point is still there find the call address which was
|
||||
// overwritten in the original code by the call to DebugBreakXXX.
|
||||
} else if (at_debug_break_slot) {
|
||||
// Address of where the debug break slot starts.
|
||||
addr = addr - Assembler::kPatchDebugBreakSlotAddressOffset;
|
||||
|
||||
// Continue just after the slot.
|
||||
thread_local_.after_break_target_ = addr + Assembler::kDebugBreakSlotLength;
|
||||
} else if (IsDebugBreak(Assembler::target_address_at(addr))) {
|
||||
// We now know that there is still a debug break call at the target address,
|
||||
// so the break point is still there and the original code will hold the
|
||||
// address to jump to in order to complete the call which is replaced by a
|
||||
// call to DebugBreakXXX.
|
||||
|
||||
// Find the corresponding address in the original code.
|
||||
addr += original_code->instruction_start() - code->instruction_start();
|
||||
}
|
||||
|
||||
// Install jump to the call address in the original code. This will be the
|
||||
// call which was overwritten by the call to DebugBreakXXX.
|
||||
thread_local_.after_break_target_ = Assembler::target_address_at(addr);
|
||||
} else {
|
||||
// There is no longer a break point present. Don't try to look in the
|
||||
// original code as the running code will have the right address. This takes
|
||||
// care of the case where the last break point is removed from the function
|
||||
// and therefore no "original code" is available.
|
||||
thread_local_.after_break_target_ = Assembler::target_address_at(addr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,11 @@ class BreakLocationIterator {
|
|||
void SetDebugBreakAtReturn();
|
||||
void ClearDebugBreakAtReturn();
|
||||
|
||||
bool IsDebugBreakSlot();
|
||||
bool IsDebugBreakAtSlot();
|
||||
void SetDebugBreakAtSlot();
|
||||
void ClearDebugBreakAtSlot();
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BreakLocationIterator);
|
||||
};
|
||||
|
||||
|
@ -323,6 +328,7 @@ class Debug {
|
|||
enum AddressId {
|
||||
k_after_break_target_address,
|
||||
k_debug_break_return_address,
|
||||
k_debug_break_slot_address,
|
||||
k_register_address
|
||||
};
|
||||
|
||||
|
@ -342,6 +348,12 @@ class Debug {
|
|||
return &debug_break_return_;
|
||||
}
|
||||
|
||||
// Access to the debug break in debug break slot code.
|
||||
static Code* debug_break_slot() { return debug_break_slot_; }
|
||||
static Code** debug_break_slot_address() {
|
||||
return &debug_break_slot_;
|
||||
}
|
||||
|
||||
static const int kEstimatedNofDebugInfoEntries = 16;
|
||||
static const int kEstimatedNofBreakPointsInFunction = 16;
|
||||
|
||||
|
@ -370,6 +382,7 @@ class Debug {
|
|||
static void AfterGarbageCollection();
|
||||
|
||||
// Code generator routines.
|
||||
static void GenerateSlot(MacroAssembler* masm);
|
||||
static void GenerateLoadICDebugBreak(MacroAssembler* masm);
|
||||
static void GenerateStoreICDebugBreak(MacroAssembler* masm);
|
||||
static void GenerateKeyedLoadICDebugBreak(MacroAssembler* masm);
|
||||
|
@ -377,6 +390,7 @@ class Debug {
|
|||
static void GenerateConstructCallDebugBreak(MacroAssembler* masm);
|
||||
static void GenerateReturnDebugBreak(MacroAssembler* masm);
|
||||
static void GenerateStubNoRegistersDebugBreak(MacroAssembler* masm);
|
||||
static void GenerateSlotDebugBreak(MacroAssembler* masm);
|
||||
static void GeneratePlainReturnLiveEdit(MacroAssembler* masm);
|
||||
static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
|
||||
|
||||
|
@ -472,6 +486,9 @@ class Debug {
|
|||
// Code to call for handling debug break on return.
|
||||
static Code* debug_break_return_;
|
||||
|
||||
// Code to call for handling debug break in debug break slots.
|
||||
static Code* debug_break_slot_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Debug);
|
||||
};
|
||||
|
||||
|
@ -895,6 +912,8 @@ class Debug_Address {
|
|||
return reinterpret_cast<Address>(Debug::after_break_target_address());
|
||||
case Debug::k_debug_break_return_address:
|
||||
return reinterpret_cast<Address>(Debug::debug_break_return_address());
|
||||
case Debug::k_debug_break_slot_address:
|
||||
return reinterpret_cast<Address>(Debug::debug_break_slot_address());
|
||||
case Debug::k_register_address:
|
||||
return reinterpret_cast<Address>(Debug::register_address(reg_));
|
||||
default:
|
||||
|
|
|
@ -277,7 +277,7 @@ DEFINE_string(testing_serialization_file, "/tmp/serdes",
|
|||
|
||||
DEFINE_bool(help, false, "Print usage message, including flags, on console")
|
||||
DEFINE_bool(dump_counters, false, "Dump counters on exit")
|
||||
DEFINE_bool(debugger, true, "Enable JavaScript debugger")
|
||||
DEFINE_bool(debugger, false, "Enable JavaScript debugger")
|
||||
DEFINE_bool(remote_debugger, false, "Connect JavaScript debugger to the "
|
||||
"debugger agent in another process")
|
||||
DEFINE_bool(debugger_agent, false, "Enable debugger agent")
|
||||
|
|
|
@ -439,6 +439,231 @@ void FullCodeGenSyntaxChecker::VisitThisFunction(ThisFunction* expr) {
|
|||
#undef CHECK_BAILOUT
|
||||
|
||||
|
||||
void BreakableStatementChecker::Check(Statement* stmt) {
|
||||
Visit(stmt);
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::Check(Expression* expr) {
|
||||
Visit(expr);
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitDeclaration(Declaration* decl) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitBlock(Block* stmt) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitExpressionStatement(
|
||||
ExpressionStatement* stmt) {
|
||||
// Check if expression is breakable.
|
||||
Visit(stmt->expression());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitEmptyStatement(EmptyStatement* stmt) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitIfStatement(IfStatement* stmt) {
|
||||
// If the condition is breakable the if statement is breakable.
|
||||
Visit(stmt->condition());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitContinueStatement(
|
||||
ContinueStatement* stmt) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitBreakStatement(BreakStatement* stmt) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitReturnStatement(ReturnStatement* stmt) {
|
||||
// Return is breakable if the expression is.
|
||||
Visit(stmt->expression());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitWithEnterStatement(
|
||||
WithEnterStatement* stmt) {
|
||||
Visit(stmt->expression());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitWithExitStatement(
|
||||
WithExitStatement* stmt) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitSwitchStatement(SwitchStatement* stmt) {
|
||||
// Switch statements breakable if the tag expression is.
|
||||
Visit(stmt->tag());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitDoWhileStatement(DoWhileStatement* stmt) {
|
||||
// Mark do while as breakable to avoid adding a break slot in front of it.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitWhileStatement(WhileStatement* stmt) {
|
||||
// Mark while statements breakable if the condition expression is.
|
||||
Visit(stmt->cond());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitForStatement(ForStatement* stmt) {
|
||||
// Mark for statements breakable if the condition expression is.
|
||||
if (stmt->cond() != NULL) {
|
||||
Visit(stmt->cond());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitForInStatement(ForInStatement* stmt) {
|
||||
// Mark for in statements breakable if the enumerable expression is.
|
||||
Visit(stmt->enumerable());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitTryCatchStatement(
|
||||
TryCatchStatement* stmt) {
|
||||
// Mark try catch as breakable to avoid adding a break slot in front of it.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitTryFinallyStatement(
|
||||
TryFinallyStatement* stmt) {
|
||||
// Mark try finally as breakable to avoid adding a break slot in front of it.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitDebuggerStatement(
|
||||
DebuggerStatement* stmt) {
|
||||
// The debugger statement is breakable.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitFunctionLiteral(FunctionLiteral* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitSharedFunctionInfoLiteral(
|
||||
SharedFunctionInfoLiteral* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitConditional(Conditional* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitSlot(Slot* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitVariableProxy(VariableProxy* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitLiteral(Literal* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitRegExpLiteral(RegExpLiteral* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitObjectLiteral(ObjectLiteral* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitArrayLiteral(ArrayLiteral* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCatchExtensionObject(
|
||||
CatchExtensionObject* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitAssignment(Assignment* expr) {
|
||||
// If assigning to a property (including a global property) the assignment is
|
||||
// breakable.
|
||||
Variable* var = expr->target()->AsVariableProxy()->AsVariable();
|
||||
Property* prop = expr->target()->AsProperty();
|
||||
if (prop != NULL || (var != NULL && var->is_global())) {
|
||||
is_breakable_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise the assignment is breakable if the assigned value is.
|
||||
Visit(expr->value());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitThrow(Throw* expr) {
|
||||
// Throw is breakable if the expression is.
|
||||
Visit(expr->exception());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitProperty(Property* expr) {
|
||||
// Property load is breakable.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCall(Call* expr) {
|
||||
// Function calls both through IC and call stub are breakable.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCallNew(CallNew* expr) {
|
||||
// Function calls through new are breakable.
|
||||
is_breakable_ = true;
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCallRuntime(CallRuntime* expr) {
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitUnaryOperation(UnaryOperation* expr) {
|
||||
Visit(expr->expression());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCountOperation(CountOperation* expr) {
|
||||
Visit(expr->expression());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitBinaryOperation(BinaryOperation* expr) {
|
||||
Visit(expr->left());
|
||||
Visit(expr->right());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitCompareOperation(CompareOperation* expr) {
|
||||
Visit(expr->left());
|
||||
Visit(expr->right());
|
||||
}
|
||||
|
||||
|
||||
void BreakableStatementChecker::VisitThisFunction(ThisFunction* expr) {
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm())
|
||||
|
||||
Handle<Code> FullCodeGenerator::MakeCode(CompilationInfo* info) {
|
||||
|
@ -552,7 +777,60 @@ void FullCodeGenerator::SetReturnPosition(FunctionLiteral* fun) {
|
|||
|
||||
void FullCodeGenerator::SetStatementPosition(Statement* stmt) {
|
||||
if (FLAG_debug_info) {
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
if (!Debugger::IsDebuggerActive()) {
|
||||
CodeGenerator::RecordPositions(masm_, stmt->statement_pos());
|
||||
} else {
|
||||
// Check if the statement will be breakable without adding a debug break
|
||||
// slot.
|
||||
BreakableStatementChecker checker;
|
||||
checker.Check(stmt);
|
||||
// Record the statement position right here if the statement is not
|
||||
// breakable. For breakable statements the actual recording of the
|
||||
// position will be postponed to the breakable code (typically an IC).
|
||||
bool position_recorded = CodeGenerator::RecordPositions(
|
||||
masm_, stmt->statement_pos(), !checker.is_breakable());
|
||||
// If the position recording did record a new position generate a debug
|
||||
// break slot to make the statement breakable.
|
||||
if (position_recorded) {
|
||||
Debug::GenerateSlot(masm_);
|
||||
}
|
||||
}
|
||||
#else
|
||||
CodeGenerator::RecordPositions(masm_, stmt->statement_pos());
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void FullCodeGenerator::SetExpressionPosition(Expression* expr, int pos) {
|
||||
if (FLAG_debug_info) {
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
if (!Debugger::IsDebuggerActive()) {
|
||||
CodeGenerator::RecordPositions(masm_, pos);
|
||||
} else {
|
||||
// Check if the expression will be breakable without adding a debug break
|
||||
// slot.
|
||||
BreakableStatementChecker checker;
|
||||
checker.Check(expr);
|
||||
// Record a statement position right here if the expression is not
|
||||
// breakable. For breakable expressions the actual recording of the
|
||||
// position will be postponed to the breakable code (typically an IC).
|
||||
// NOTE this will record a statement position for something which might
|
||||
// not be a statement. As stepping in the debugger will only stop at
|
||||
// statement positions this is used for e.g. the condition expression of
|
||||
// a do while loop.
|
||||
bool position_recorded = CodeGenerator::RecordPositions(
|
||||
masm_, pos, !checker.is_breakable());
|
||||
// If the position recording did record a new position generate a debug
|
||||
// break slot to make the statement breakable.
|
||||
if (position_recorded) {
|
||||
Debug::GenerateSlot(masm_);
|
||||
}
|
||||
}
|
||||
#else
|
||||
CodeGenerator::RecordPositions(masm_, pos);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,7 +1126,11 @@ void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
|
|||
__ bind(&stack_check_success);
|
||||
|
||||
__ bind(loop_statement.continue_target());
|
||||
SetStatementPosition(stmt->condition_position());
|
||||
|
||||
// Record the position of the do while condition and make sure it is possible
|
||||
// to break on the condition.
|
||||
SetExpressionPosition(stmt->cond(), stmt->condition_position());
|
||||
|
||||
VisitForControl(stmt->cond(), &body, loop_statement.break_target());
|
||||
|
||||
__ bind(&stack_limit_hit);
|
||||
|
@ -864,7 +1146,6 @@ void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
|
|||
|
||||
void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
|
||||
Comment cmnt(masm_, "[ WhileStatement");
|
||||
SetStatementPosition(stmt);
|
||||
Label body, stack_limit_hit, stack_check_success;
|
||||
|
||||
Iteration loop_statement(this, stmt);
|
||||
|
@ -877,6 +1158,9 @@ void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
|
|||
Visit(stmt->body());
|
||||
|
||||
__ bind(loop_statement.continue_target());
|
||||
// Emit the statement position here as this is where the while statement code
|
||||
// starts.
|
||||
SetStatementPosition(stmt);
|
||||
|
||||
// Check stack before looping.
|
||||
__ StackLimitCheck(&stack_limit_hit);
|
||||
|
@ -896,7 +1180,6 @@ void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
|
|||
|
||||
void FullCodeGenerator::VisitForStatement(ForStatement* stmt) {
|
||||
Comment cmnt(masm_, "[ ForStatement");
|
||||
SetStatementPosition(stmt);
|
||||
Label test, body, stack_limit_hit, stack_check_success;
|
||||
|
||||
Iteration loop_statement(this, stmt);
|
||||
|
@ -919,6 +1202,9 @@ void FullCodeGenerator::VisitForStatement(ForStatement* stmt) {
|
|||
}
|
||||
|
||||
__ bind(&test);
|
||||
// Emit the statement position here as this is where the for statement code
|
||||
// starts.
|
||||
SetStatementPosition(stmt);
|
||||
|
||||
// Check stack before looping.
|
||||
__ StackLimitCheck(&stack_limit_hit);
|
||||
|
@ -1064,6 +1350,8 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) {
|
|||
VisitForControl(expr->condition(), &true_case, &false_case);
|
||||
|
||||
__ bind(&true_case);
|
||||
SetExpressionPosition(expr->then_expression(),
|
||||
expr->then_expression_position());
|
||||
Visit(expr->then_expression());
|
||||
// If control flow falls through Visit, jump to done.
|
||||
if (context_ == Expression::kEffect || context_ == Expression::kValue) {
|
||||
|
@ -1071,6 +1359,8 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) {
|
|||
}
|
||||
|
||||
__ bind(&false_case);
|
||||
SetExpressionPosition(expr->else_expression(),
|
||||
expr->else_expression_position());
|
||||
Visit(expr->else_expression());
|
||||
// If control flow falls through Visit, merge it with true case here.
|
||||
if (context_ == Expression::kEffect || context_ == Expression::kValue) {
|
||||
|
|
|
@ -59,6 +59,31 @@ class FullCodeGenSyntaxChecker: public AstVisitor {
|
|||
};
|
||||
|
||||
|
||||
// AST node visitor which can tell whether a given statement will be breakable
|
||||
// when the code is compiled by the full compiler in the debugger. This means
|
||||
// that there will be an IC (load/store/call) in the code generated for the
|
||||
// debugger to piggybag on.
|
||||
class BreakableStatementChecker: public AstVisitor {
|
||||
public:
|
||||
BreakableStatementChecker() : is_breakable_(false) {}
|
||||
|
||||
void Check(Statement* stmt);
|
||||
void Check(Expression* stmt);
|
||||
|
||||
bool is_breakable() { return is_breakable_; }
|
||||
|
||||
private:
|
||||
// AST node visit functions.
|
||||
#define DECLARE_VISIT(type) virtual void Visit##type(type* node);
|
||||
AST_NODE_LIST(DECLARE_VISIT)
|
||||
#undef DECLARE_VISIT
|
||||
|
||||
bool is_breakable_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BreakableStatementChecker);
|
||||
};
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Full code generator.
|
||||
|
||||
|
@ -458,6 +483,7 @@ class FullCodeGenerator: public AstVisitor {
|
|||
void SetFunctionPosition(FunctionLiteral* fun);
|
||||
void SetReturnPosition(FunctionLiteral* fun);
|
||||
void SetStatementPosition(Statement* stmt);
|
||||
void SetExpressionPosition(Expression* expr, int pos);
|
||||
void SetStatementPosition(int pos);
|
||||
void SetSourcePosition(int pos);
|
||||
|
||||
|
|
|
@ -118,11 +118,9 @@ void Heap::FinalizeExternalString(String* string) {
|
|||
ExternalString::kResourceOffset -
|
||||
kHeapObjectTag);
|
||||
|
||||
// Dispose of the C++ object.
|
||||
if (external_string_dispose_callback_ != NULL) {
|
||||
external_string_dispose_callback_(*resource_addr);
|
||||
} else {
|
||||
delete *resource_addr;
|
||||
// Dispose of the C++ object if it has not already been disposed.
|
||||
if (*resource_addr != NULL) {
|
||||
(*resource_addr)->Dispose();
|
||||
}
|
||||
|
||||
// Clear the resource pointer in the string.
|
||||
|
|
|
@ -98,8 +98,6 @@ size_t Heap::code_range_size_ = 0;
|
|||
// set up by ConfigureHeap otherwise.
|
||||
int Heap::reserved_semispace_size_ = Heap::max_semispace_size_;
|
||||
|
||||
ExternalStringDiposeCallback Heap::external_string_dispose_callback_ = NULL;
|
||||
|
||||
List<Heap::GCPrologueCallbackPair> Heap::gc_prologue_callbacks_;
|
||||
List<Heap::GCEpilogueCallbackPair> Heap::gc_epilogue_callbacks_;
|
||||
|
||||
|
@ -607,6 +605,9 @@ void Heap::PerformGarbageCollection(AllocationSpace space,
|
|||
EnsureFromSpaceIsCommitted();
|
||||
|
||||
if (collector == MARK_COMPACTOR) {
|
||||
// Flush all potentially unused code.
|
||||
FlushCode();
|
||||
|
||||
// Perform mark-sweep with optional compaction.
|
||||
MarkCompact(tracer);
|
||||
|
||||
|
@ -659,18 +660,19 @@ void Heap::PerformGarbageCollection(AllocationSpace space,
|
|||
|
||||
void Heap::MarkCompact(GCTracer* tracer) {
|
||||
gc_state_ = MARK_COMPACT;
|
||||
if (MarkCompactCollector::IsCompacting()) {
|
||||
mc_count_++;
|
||||
} else {
|
||||
ms_count_++;
|
||||
}
|
||||
tracer->set_full_gc_count(mc_count_);
|
||||
LOG(ResourceEvent("markcompact", "begin"));
|
||||
|
||||
MarkCompactCollector::Prepare(tracer);
|
||||
|
||||
bool is_compacting = MarkCompactCollector::IsCompacting();
|
||||
|
||||
if (is_compacting) {
|
||||
mc_count_++;
|
||||
} else {
|
||||
ms_count_++;
|
||||
}
|
||||
tracer->set_full_gc_count(mc_count_ + ms_count_);
|
||||
|
||||
MarkCompactPrologue(is_compacting);
|
||||
|
||||
MarkCompactCollector::CollectGarbage();
|
||||
|
@ -2185,6 +2187,87 @@ Object* Heap::AllocateExternalArray(int length,
|
|||
}
|
||||
|
||||
|
||||
// The StackVisitor is used to traverse all the archived threads to see if
|
||||
// there are activations on any of the stacks corresponding to the code.
|
||||
class FlushingStackVisitor : public ThreadVisitor {
|
||||
public:
|
||||
explicit FlushingStackVisitor(Code* code) : found_(false), code_(code) {}
|
||||
|
||||
void VisitThread(ThreadLocalTop* top) {
|
||||
// If we already found the code in a previous traversed thread we return.
|
||||
if (found_) return;
|
||||
|
||||
for (StackFrameIterator it(top); !it.done(); it.Advance()) {
|
||||
if (code_->contains(it.frame()->pc())) {
|
||||
found_ = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
bool FoundCode() {return found_;}
|
||||
|
||||
private:
|
||||
bool found_;
|
||||
Code* code_;
|
||||
};
|
||||
|
||||
|
||||
static void FlushCodeForFunction(SharedFunctionInfo* function_info) {
|
||||
// The function must be compiled and have the source code available,
|
||||
// to be able to recompile it in case we need the function again.
|
||||
if (!(function_info->is_compiled() && function_info->HasSourceCode())) return;
|
||||
|
||||
// We never flush code for Api functions.
|
||||
if (function_info->IsApiFunction()) return;
|
||||
|
||||
// Only flush code for functions.
|
||||
if (!function_info->code()->kind() == Code::FUNCTION) return;
|
||||
|
||||
// Function must be lazy compilable.
|
||||
if (!function_info->allows_lazy_compilation()) return;
|
||||
|
||||
// If this is a full script wrapped in a function we do no flush the code.
|
||||
if (function_info->is_toplevel()) return;
|
||||
|
||||
// If this function is in the compilation cache we do not flush the code.
|
||||
if (CompilationCache::HasFunction(function_info)) return;
|
||||
|
||||
// Make sure we are not referencing the code from the stack.
|
||||
for (StackFrameIterator it; !it.done(); it.Advance()) {
|
||||
if (function_info->code()->contains(it.frame()->pc())) return;
|
||||
}
|
||||
// Iterate the archived stacks in all threads to check if
|
||||
// the code is referenced.
|
||||
FlushingStackVisitor threadvisitor(function_info->code());
|
||||
ThreadManager::IterateArchivedThreads(&threadvisitor);
|
||||
if (threadvisitor.FoundCode()) return;
|
||||
|
||||
HandleScope scope;
|
||||
// Compute the lazy compilable version of the code.
|
||||
function_info->set_code(*ComputeLazyCompile(function_info->length()));
|
||||
}
|
||||
|
||||
|
||||
void Heap::FlushCode() {
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
// Do not flush code if the debugger is loaded or there are breakpoints.
|
||||
if (Debug::IsLoaded() || Debug::has_break_points()) return;
|
||||
#endif
|
||||
HeapObjectIterator it(old_pointer_space());
|
||||
for (HeapObject* obj = it.next(); obj != NULL; obj = it.next()) {
|
||||
if (obj->IsJSFunction()) {
|
||||
JSFunction* jsfunction = JSFunction::cast(obj);
|
||||
|
||||
// The function must have a valid context and not be a builtin.
|
||||
if (jsfunction->unchecked_context()->IsContext() &&
|
||||
!jsfunction->IsBuiltin()) {
|
||||
FlushCodeForFunction(jsfunction->shared());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Object* Heap::CreateCode(const CodeDesc& desc,
|
||||
ZoneScopeInfo* sinfo,
|
||||
Code::Flags flags,
|
||||
|
|
|
@ -690,11 +690,6 @@ class Heap : public AllStatic {
|
|||
static bool GarbageCollectionGreedyCheck();
|
||||
#endif
|
||||
|
||||
static void SetExternalStringDiposeCallback(
|
||||
ExternalStringDiposeCallback callback) {
|
||||
external_string_dispose_callback_ = callback;
|
||||
}
|
||||
|
||||
static void AddGCPrologueCallback(
|
||||
GCEpilogueCallback callback, GCType gc_type_filter);
|
||||
static void RemoveGCPrologueCallback(GCEpilogueCallback callback);
|
||||
|
@ -1143,9 +1138,6 @@ class Heap : public AllStatic {
|
|||
// any string when looked up in properties.
|
||||
static String* hidden_symbol_;
|
||||
|
||||
static ExternalStringDiposeCallback
|
||||
external_string_dispose_callback_;
|
||||
|
||||
// GC callback function, called before and after mark-compact GC.
|
||||
// Allocations in the callback function are disallowed.
|
||||
struct GCPrologueCallbackPair {
|
||||
|
@ -1274,6 +1266,10 @@ class Heap : public AllStatic {
|
|||
// Flush the number to string cache.
|
||||
static void FlushNumberStringCache();
|
||||
|
||||
// Flush code from functions we do not expect to use again. The code will
|
||||
// be replaced with a lazy compilable version.
|
||||
static void FlushCode();
|
||||
|
||||
static const int kInitialSymbolTableSize = 2048;
|
||||
static const int kInitialEvalCacheSize = 64;
|
||||
|
||||
|
|
|
@ -52,16 +52,21 @@ Condition NegateCondition(Condition cc) {
|
|||
void RelocInfo::apply(intptr_t delta) {
|
||||
if (rmode_ == RUNTIME_ENTRY || IsCodeTarget(rmode_)) {
|
||||
int32_t* p = reinterpret_cast<int32_t*>(pc_);
|
||||
*p -= delta; // relocate entry
|
||||
*p -= delta; // Relocate entry.
|
||||
} else if (rmode_ == JS_RETURN && IsPatchedReturnSequence()) {
|
||||
// Special handling of js_return when a break point is set (call
|
||||
// instruction has been inserted).
|
||||
int32_t* p = reinterpret_cast<int32_t*>(pc_ + 1);
|
||||
*p -= delta; // relocate entry
|
||||
*p -= delta; // Relocate entry.
|
||||
} else if (rmode_ == DEBUG_BREAK_SLOT && IsPatchedDebugBreakSlotSequence()) {
|
||||
// Special handling of a debug break slot when a break point is set (call
|
||||
// instruction has been inserted).
|
||||
int32_t* p = reinterpret_cast<int32_t*>(pc_ + 1);
|
||||
*p -= delta; // Relocate entry.
|
||||
} else if (IsInternalReference(rmode_)) {
|
||||
// absolute code pointer inside code object moves with the code object.
|
||||
int32_t* p = reinterpret_cast<int32_t*>(pc_);
|
||||
*p += delta; // relocate entry
|
||||
*p += delta; // Relocate entry.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -154,6 +159,11 @@ bool RelocInfo::IsPatchedReturnSequence() {
|
|||
}
|
||||
|
||||
|
||||
bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
|
||||
return !Assembler::IsNop(pc());
|
||||
}
|
||||
|
||||
|
||||
void RelocInfo::Visit(ObjectVisitor* visitor) {
|
||||
RelocInfo::Mode mode = rmode();
|
||||
if (mode == RelocInfo::EMBEDDED_OBJECT) {
|
||||
|
@ -164,8 +174,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
|
|||
visitor->VisitExternalReference(target_reference_address());
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
} else if (Debug::has_break_points() &&
|
||||
RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) {
|
||||
((RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(mode) &&
|
||||
IsPatchedDebugBreakSlotSequence()))) {
|
||||
visitor->VisitDebugTarget(this);
|
||||
#endif
|
||||
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
|
||||
|
|
|
@ -206,6 +206,7 @@ void RelocInfo::PatchCodeWithCall(Address target, int guard_bytes) {
|
|||
patcher.masm()->SizeOfCodeGeneratedSince(&check_codesize));
|
||||
|
||||
// Add the requested number of int3 instructions after the call.
|
||||
ASSERT_GE(guard_bytes, 0);
|
||||
for (int i = 0; i < guard_bytes; i++) {
|
||||
patcher.masm()->int3();
|
||||
}
|
||||
|
@ -2371,6 +2372,13 @@ void Assembler::RecordJSReturn() {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::RecordDebugBreakSlot() {
|
||||
WriteRecordedPositions();
|
||||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
|
||||
}
|
||||
|
||||
|
||||
void Assembler::RecordComment(const char* msg) {
|
||||
if (FLAG_debug_code) {
|
||||
EnsureSpace ensure_space(this);
|
||||
|
@ -2393,13 +2401,16 @@ void Assembler::RecordStatementPosition(int pos) {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::WriteRecordedPositions() {
|
||||
bool Assembler::WriteRecordedPositions() {
|
||||
bool written = false;
|
||||
|
||||
// Write the statement position if it is different from what was written last
|
||||
// time.
|
||||
if (current_statement_position_ != written_statement_position_) {
|
||||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
|
||||
written_statement_position_ = current_statement_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Write the position if it is different from what was written last time and
|
||||
|
@ -2409,7 +2420,11 @@ void Assembler::WriteRecordedPositions() {
|
|||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::POSITION, current_position_);
|
||||
written_position_ = current_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Return whether something was written.
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -468,9 +468,16 @@ class Assembler : public Malloced {
|
|||
// to jump to.
|
||||
static const int kPatchReturnSequenceAddressOffset = 1; // JMP imm32.
|
||||
|
||||
// Distance between start of patched debug break slot and the emitted address
|
||||
// to jump to.
|
||||
static const int kPatchDebugBreakSlotAddressOffset = 1; // JMP imm32.
|
||||
|
||||
static const int kCallInstructionLength = 5;
|
||||
static const int kJSReturnSequenceLength = 6;
|
||||
|
||||
// The debug break slot must be able to contain a call instruction.
|
||||
static const int kDebugBreakSlotLength = kCallInstructionLength;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Code generation
|
||||
//
|
||||
|
@ -809,13 +816,16 @@ class Assembler : public Malloced {
|
|||
// Mark address of the ExitJSFrame code.
|
||||
void RecordJSReturn();
|
||||
|
||||
// Mark address of a debug break slot.
|
||||
void RecordDebugBreakSlot();
|
||||
|
||||
// Record a comment relocation entry that can be used by a disassembler.
|
||||
// Use --debug_code to enable.
|
||||
void RecordComment(const char* msg);
|
||||
|
||||
void RecordPosition(int pos);
|
||||
void RecordStatementPosition(int pos);
|
||||
void WriteRecordedPositions();
|
||||
bool WriteRecordedPositions();
|
||||
|
||||
// Writes a single word of data in the code stream.
|
||||
// Used for inline tables, e.g., jump-tables.
|
||||
|
@ -833,6 +843,8 @@ class Assembler : public Malloced {
|
|||
// Get the number of bytes available in the buffer.
|
||||
inline int available_space() const { return reloc_info_writer.pos() - pc_; }
|
||||
|
||||
static bool IsNop(Address addr) { return *addr == 0x90; }
|
||||
|
||||
// Avoid overflows for displacements etc.
|
||||
static const int kMaximalBufferSize = 512*MB;
|
||||
static const int kMinimalBufferSize = 4*KB;
|
||||
|
|
|
@ -316,7 +316,9 @@ class CodeGenerator: public AstVisitor {
|
|||
static bool ShouldGenerateLog(Expression* type);
|
||||
#endif
|
||||
|
||||
static void RecordPositions(MacroAssembler* masm, int pos);
|
||||
static bool RecordPositions(MacroAssembler* masm,
|
||||
int pos,
|
||||
bool right_here = false);
|
||||
|
||||
// Accessors
|
||||
MacroAssembler* masm() { return masm_; }
|
||||
|
|
|
@ -69,6 +69,27 @@ bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) {
|
|||
}
|
||||
|
||||
|
||||
bool BreakLocationIterator::IsDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
// Check whether the debug break slot instructions have been patched.
|
||||
return rinfo()->IsPatchedDebugBreakSlotSequence();
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::SetDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
rinfo()->PatchCodeWithCall(
|
||||
Debug::debug_break_slot()->entry(),
|
||||
Assembler::kDebugBreakSlotLength - Assembler::kCallInstructionLength);
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::ClearDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
rinfo()->PatchCode(original_rinfo()->pc(), Assembler::kDebugBreakSlotLength);
|
||||
}
|
||||
|
||||
|
||||
#define __ ACCESS_MASM(masm)
|
||||
|
||||
|
||||
|
@ -208,10 +229,31 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
|
|||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlot(MacroAssembler* masm) {
|
||||
// Generate enough nop's to make space for a call instruction.
|
||||
Label check_codesize;
|
||||
__ bind(&check_codesize);
|
||||
__ RecordDebugBreakSlot();
|
||||
for (int i = 0; i < Assembler::kDebugBreakSlotLength; i++) {
|
||||
__ nop();
|
||||
}
|
||||
ASSERT_EQ(Assembler::kDebugBreakSlotLength,
|
||||
masm->SizeOfCodeGeneratedSince(&check_codesize));
|
||||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
|
||||
// In the places where a debug break slot is inserted no registers can contain
|
||||
// object pointers.
|
||||
Generate_DebugBreakCallHelper(masm, 0, true);
|
||||
}
|
||||
|
||||
|
||||
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
|
||||
masm->ret(0);
|
||||
}
|
||||
|
||||
|
||||
// FrameDropper is a code replacement for a JavaScript frame with possibly
|
||||
// several frames above.
|
||||
// There is no calling conventions here, because it never actually gets called,
|
||||
|
|
|
@ -924,14 +924,18 @@ int DisassemblerIA32::InstructionDecode(v8::internal::Vector<char> out_buffer,
|
|||
break;
|
||||
|
||||
case 0xF6:
|
||||
{ int mod, regop, rm;
|
||||
get_modrm(*(data+1), &mod, ®op, &rm);
|
||||
if (mod == 3 && regop == eax) {
|
||||
AppendToBuffer("test_b %s,%d", NameOfCPURegister(rm), *(data+2));
|
||||
{ data++;
|
||||
int mod, regop, rm;
|
||||
get_modrm(*data, &mod, ®op, &rm);
|
||||
if (regop == eax) {
|
||||
AppendToBuffer("test_b ");
|
||||
data += PrintRightOperand(data);
|
||||
int32_t imm = *data;
|
||||
AppendToBuffer(",0x%x", imm);
|
||||
data++;
|
||||
} else {
|
||||
UnimplementedInstruction();
|
||||
}
|
||||
data += 3;
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
@ -273,8 +273,10 @@ class MarkingVisitor : public ObjectVisitor {
|
|||
}
|
||||
|
||||
void VisitDebugTarget(RelocInfo* rinfo) {
|
||||
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence());
|
||||
ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedDebugBreakSlotSequence()));
|
||||
HeapObject* code = Code::GetCodeFromTargetAddress(rinfo->call_address());
|
||||
MarkCompactCollector::MarkObject(code);
|
||||
}
|
||||
|
@ -1106,8 +1108,10 @@ class PointersToNewGenUpdatingVisitor: public ObjectVisitor {
|
|||
}
|
||||
|
||||
void VisitDebugTarget(RelocInfo* rinfo) {
|
||||
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence());
|
||||
ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedDebugBreakSlotSequence()));
|
||||
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
|
||||
VisitPointer(&target);
|
||||
rinfo->set_call_address(Code::cast(target)->instruction_start());
|
||||
|
@ -1856,8 +1860,10 @@ class UpdatingVisitor: public ObjectVisitor {
|
|||
}
|
||||
|
||||
void VisitDebugTarget(RelocInfo* rinfo) {
|
||||
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence());
|
||||
ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedDebugBreakSlotSequence()));
|
||||
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
|
||||
VisitPointer(&target);
|
||||
rinfo->set_call_address(
|
||||
|
|
|
@ -1046,13 +1046,16 @@ void Assembler::RecordStatementPosition(int pos) {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::WriteRecordedPositions() {
|
||||
bool Assembler::WriteRecordedPositions() {
|
||||
bool written = false;
|
||||
|
||||
// Write the statement position if it is different from what was written last
|
||||
// time.
|
||||
if (current_statement_position_ != written_statement_position_) {
|
||||
CheckBuffer();
|
||||
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
|
||||
written_statement_position_ = current_statement_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Write the position if it is different from what was written last time and
|
||||
|
@ -1062,7 +1065,11 @@ void Assembler::WriteRecordedPositions() {
|
|||
CheckBuffer();
|
||||
RecordRelocInfo(RelocInfo::POSITION, current_position_);
|
||||
written_position_ = current_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Return whether something was written.
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -355,6 +355,9 @@ class Assembler : public Malloced {
|
|||
// to jump to.
|
||||
static const int kPatchReturnSequenceAddressOffset = kInstrSize;
|
||||
|
||||
// Distance between start of patched debug break slot and the emitted address
|
||||
// to jump to.
|
||||
static const int kPatchDebugBreakSlotAddressOffset = kInstrSize;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Code generation.
|
||||
|
@ -518,7 +521,7 @@ class Assembler : public Malloced {
|
|||
|
||||
void RecordPosition(int pos);
|
||||
void RecordStatementPosition(int pos);
|
||||
void WriteRecordedPositions();
|
||||
bool WriteRecordedPositions();
|
||||
|
||||
int32_t pc_offset() const { return pc_ - buffer_; }
|
||||
int32_t current_position() const { return current_position_; }
|
||||
|
|
|
@ -2468,6 +2468,10 @@ BOOL_ACCESSORS(SharedFunctionInfo,
|
|||
compiler_hints,
|
||||
try_full_codegen,
|
||||
kTryFullCodegen)
|
||||
BOOL_ACCESSORS(SharedFunctionInfo,
|
||||
compiler_hints,
|
||||
allows_lazy_compilation,
|
||||
kAllowLazyCompilation)
|
||||
|
||||
#if V8_HOST_ARCH_32_BIT
|
||||
SMI_ACCESSORS(SharedFunctionInfo, length, kLengthOffset)
|
||||
|
|
|
@ -5264,8 +5264,10 @@ void ObjectVisitor::VisitCodeTarget(RelocInfo* rinfo) {
|
|||
|
||||
|
||||
void ObjectVisitor::VisitDebugTarget(RelocInfo* rinfo) {
|
||||
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence());
|
||||
ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
|
||||
rinfo->IsPatchedDebugBreakSlotSequence()));
|
||||
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
|
||||
Object* old_target = target;
|
||||
VisitPointer(&target);
|
||||
|
@ -5278,6 +5280,7 @@ void Code::CodeIterateBody(ObjectVisitor* v) {
|
|||
RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) |
|
||||
RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) |
|
||||
RelocInfo::ModeMask(RelocInfo::JS_RETURN) |
|
||||
RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) |
|
||||
RelocInfo::ModeMask(RelocInfo::RUNTIME_ENTRY);
|
||||
|
||||
for (RelocIterator it(this, mode_mask); !it.done(); it.next()) {
|
||||
|
|
|
@ -3308,6 +3308,12 @@ class SharedFunctionInfo: public HeapObject {
|
|||
inline bool try_full_codegen();
|
||||
inline void set_try_full_codegen(bool flag);
|
||||
|
||||
// Indicates if this function can be lazy compiled.
|
||||
// This is used to determine if we can safely flush code from a function
|
||||
// when doing GC if we expect that the function will no longer be used.
|
||||
inline bool allows_lazy_compilation();
|
||||
inline void set_allows_lazy_compilation(bool flag);
|
||||
|
||||
// Check whether a inlined constructor can be generated with the given
|
||||
// prototype.
|
||||
bool CanGenerateInlineConstructor(Object* prototype);
|
||||
|
@ -3433,6 +3439,7 @@ class SharedFunctionInfo: public HeapObject {
|
|||
// Bit positions in compiler_hints.
|
||||
static const int kHasOnlySimpleThisPropertyAssignments = 0;
|
||||
static const int kTryFullCodegen = 1;
|
||||
static const int kAllowLazyCompilation = 2;
|
||||
|
||||
DISALLOW_IMPLICIT_CONSTRUCTORS(SharedFunctionInfo);
|
||||
};
|
||||
|
|
|
@ -2867,10 +2867,13 @@ Expression* Parser::ParseConditionalExpression(bool accept_IN, bool* ok) {
|
|||
// In parsing the first assignment expression in conditional
|
||||
// expressions we always accept the 'in' keyword; see ECMA-262,
|
||||
// section 11.12, page 58.
|
||||
int left_position = scanner().peek_location().beg_pos;
|
||||
Expression* left = ParseAssignmentExpression(true, CHECK_OK);
|
||||
Expect(Token::COLON, CHECK_OK);
|
||||
int right_position = scanner().peek_location().beg_pos;
|
||||
Expression* right = ParseAssignmentExpression(accept_IN, CHECK_OK);
|
||||
return NEW(Conditional(expression, left, right));
|
||||
return NEW(Conditional(expression, left, right,
|
||||
left_position, right_position));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ TokenEnumerator::~TokenEnumerator() {
|
|||
|
||||
|
||||
int TokenEnumerator::GetTokenId(Object* token) {
|
||||
if (token == NULL) return CodeEntry::kNoSecurityToken;
|
||||
if (token == NULL) return TokenEnumerator::kNoSecurityToken;
|
||||
for (int i = 0; i < token_locations_.length(); ++i) {
|
||||
if (*token_locations_[i] == token && !token_removed_[i]) return i;
|
||||
}
|
||||
|
@ -86,6 +86,37 @@ void TokenEnumerator::TokenRemoved(Object** token_location) {
|
|||
}
|
||||
|
||||
|
||||
StringsStorage::StringsStorage()
|
||||
: names_(StringsMatch) {
|
||||
}
|
||||
|
||||
|
||||
StringsStorage::~StringsStorage() {
|
||||
for (HashMap::Entry* p = names_.Start();
|
||||
p != NULL;
|
||||
p = names_.Next(p)) {
|
||||
DeleteArray(reinterpret_cast<const char*>(p->value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* StringsStorage::GetName(String* name) {
|
||||
if (name->IsString()) {
|
||||
char* c_name =
|
||||
name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL).Detach();
|
||||
HashMap::Entry* cache_entry = names_.Lookup(c_name, name->Hash(), true);
|
||||
if (cache_entry->value == NULL) {
|
||||
// New entry added.
|
||||
cache_entry->value = c_name;
|
||||
} else {
|
||||
DeleteArray(c_name);
|
||||
}
|
||||
return reinterpret_cast<const char*>(cache_entry->value);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
const char* CodeEntry::kEmptyNamePrefix = "";
|
||||
unsigned CodeEntry::next_call_uid_ = 1;
|
||||
|
||||
|
@ -171,7 +202,7 @@ ProfileTree::ProfileTree()
|
|||
"(root)",
|
||||
"",
|
||||
0,
|
||||
CodeEntry::kNoSecurityToken),
|
||||
TokenEnumerator::kNoSecurityToken),
|
||||
root_(new ProfileNode(this, &root_entry_)) {
|
||||
}
|
||||
|
||||
|
@ -248,11 +279,11 @@ class FilteredCloneCallback {
|
|||
|
||||
private:
|
||||
bool IsTokenAcceptable(int token, int parent_token) {
|
||||
if (token == CodeEntry::kNoSecurityToken
|
||||
if (token == TokenEnumerator::kNoSecurityToken
|
||||
|| token == security_token_id_) return true;
|
||||
if (token == CodeEntry::kInheritsSecurityToken) {
|
||||
ASSERT(parent_token != CodeEntry::kInheritsSecurityToken);
|
||||
return parent_token == CodeEntry::kNoSecurityToken
|
||||
if (token == TokenEnumerator::kInheritsSecurityToken) {
|
||||
ASSERT(parent_token != TokenEnumerator::kInheritsSecurityToken);
|
||||
return parent_token == TokenEnumerator::kNoSecurityToken
|
||||
|| parent_token == security_token_id_;
|
||||
}
|
||||
return false;
|
||||
|
@ -373,7 +404,7 @@ void CpuProfile::SetActualSamplingRate(double actual_sampling_rate) {
|
|||
|
||||
|
||||
CpuProfile* CpuProfile::FilteredClone(int security_token_id) {
|
||||
ASSERT(security_token_id != CodeEntry::kNoSecurityToken);
|
||||
ASSERT(security_token_id != TokenEnumerator::kNoSecurityToken);
|
||||
CpuProfile* clone = new CpuProfile(title_, uid_);
|
||||
clone->top_down_.FilteredClone(&top_down_, security_token_id);
|
||||
clone->bottom_up_.FilteredClone(&bottom_up_, security_token_id);
|
||||
|
@ -438,8 +469,7 @@ void CodeMap::Print() {
|
|||
|
||||
|
||||
CpuProfilesCollection::CpuProfilesCollection()
|
||||
: function_and_resource_names_(StringsMatch),
|
||||
profiles_uids_(UidsMatch),
|
||||
: profiles_uids_(UidsMatch),
|
||||
current_profiles_semaphore_(OS::CreateSemaphore(1)) {
|
||||
// Create list of unabridged profiles.
|
||||
profiles_by_token_.Add(new List<CpuProfile*>());
|
||||
|
@ -470,11 +500,6 @@ CpuProfilesCollection::~CpuProfilesCollection() {
|
|||
profiles_by_token_.Iterate(DeleteProfilesList);
|
||||
code_entries_.Iterate(DeleteCodeEntry);
|
||||
args_count_names_.Iterate(DeleteArgsCountName);
|
||||
for (HashMap::Entry* p = function_and_resource_names_.Start();
|
||||
p != NULL;
|
||||
p = function_and_resource_names_.Next(p)) {
|
||||
DeleteArray(reinterpret_cast<const char*>(p->value));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -517,7 +542,7 @@ CpuProfile* CpuProfilesCollection::StopProfiling(int security_token_id,
|
|||
profile->CalculateTotalTicks();
|
||||
profile->SetActualSamplingRate(actual_sampling_rate);
|
||||
List<CpuProfile*>* unabridged_list =
|
||||
profiles_by_token_[TokenToIndex(CodeEntry::kNoSecurityToken)];
|
||||
profiles_by_token_[TokenToIndex(TokenEnumerator::kNoSecurityToken)];
|
||||
unabridged_list->Add(profile);
|
||||
HashMap::Entry* entry =
|
||||
profiles_uids_.Lookup(reinterpret_cast<void*>(profile->uid()),
|
||||
|
@ -550,8 +575,8 @@ CpuProfile* CpuProfilesCollection::GetProfile(int security_token_id,
|
|||
return NULL;
|
||||
}
|
||||
List<CpuProfile*>* unabridged_list =
|
||||
profiles_by_token_[TokenToIndex(CodeEntry::kNoSecurityToken)];
|
||||
if (security_token_id == CodeEntry::kNoSecurityToken) {
|
||||
profiles_by_token_[TokenToIndex(TokenEnumerator::kNoSecurityToken)];
|
||||
if (security_token_id == TokenEnumerator::kNoSecurityToken) {
|
||||
return unabridged_list->at(index);
|
||||
}
|
||||
List<CpuProfile*>* list = GetProfilesList(security_token_id);
|
||||
|
@ -564,7 +589,7 @@ CpuProfile* CpuProfilesCollection::GetProfile(int security_token_id,
|
|||
|
||||
|
||||
int CpuProfilesCollection::TokenToIndex(int security_token_id) {
|
||||
ASSERT(CodeEntry::kNoSecurityToken == -1);
|
||||
ASSERT(TokenEnumerator::kNoSecurityToken == -1);
|
||||
return security_token_id + 1; // kNoSecurityToken -> 0, 0 -> 1, ...
|
||||
}
|
||||
|
||||
|
@ -575,7 +600,7 @@ List<CpuProfile*>* CpuProfilesCollection::GetProfilesList(
|
|||
const int lists_to_add = index - profiles_by_token_.length() + 1;
|
||||
if (lists_to_add > 0) profiles_by_token_.AddBlock(NULL, lists_to_add);
|
||||
List<CpuProfile*>* unabridged_list =
|
||||
profiles_by_token_[TokenToIndex(CodeEntry::kNoSecurityToken)];
|
||||
profiles_by_token_[TokenToIndex(TokenEnumerator::kNoSecurityToken)];
|
||||
const int current_count = unabridged_list->length();
|
||||
if (profiles_by_token_[index] == NULL) {
|
||||
profiles_by_token_[index] = new List<CpuProfile*>(current_count);
|
||||
|
@ -589,8 +614,8 @@ List<CpuProfile*>* CpuProfilesCollection::GetProfilesList(
|
|||
|
||||
List<CpuProfile*>* CpuProfilesCollection::Profiles(int security_token_id) {
|
||||
List<CpuProfile*>* unabridged_list =
|
||||
profiles_by_token_[TokenToIndex(CodeEntry::kNoSecurityToken)];
|
||||
if (security_token_id == CodeEntry::kNoSecurityToken) {
|
||||
profiles_by_token_[TokenToIndex(TokenEnumerator::kNoSecurityToken)];
|
||||
if (security_token_id == TokenEnumerator::kNoSecurityToken) {
|
||||
return unabridged_list;
|
||||
}
|
||||
List<CpuProfile*>* list = GetProfilesList(security_token_id);
|
||||
|
@ -613,7 +638,7 @@ CodeEntry* CpuProfilesCollection::NewCodeEntry(Logger::LogEventsAndTags tag,
|
|||
GetFunctionName(name),
|
||||
GetName(resource_name),
|
||||
line_number,
|
||||
CodeEntry::kNoSecurityToken);
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
code_entries_.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -626,7 +651,7 @@ CodeEntry* CpuProfilesCollection::NewCodeEntry(Logger::LogEventsAndTags tag,
|
|||
GetFunctionName(name),
|
||||
"",
|
||||
v8::CpuProfileNode::kNoLineNumberInfo,
|
||||
CodeEntry::kNoSecurityToken);
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
code_entries_.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -640,7 +665,7 @@ CodeEntry* CpuProfilesCollection::NewCodeEntry(Logger::LogEventsAndTags tag,
|
|||
GetName(name),
|
||||
"",
|
||||
v8::CpuProfileNode::kNoLineNumberInfo,
|
||||
CodeEntry::kInheritsSecurityToken);
|
||||
TokenEnumerator::kInheritsSecurityToken);
|
||||
code_entries_.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -653,7 +678,7 @@ CodeEntry* CpuProfilesCollection::NewCodeEntry(Logger::LogEventsAndTags tag,
|
|||
GetName(args_count),
|
||||
"",
|
||||
v8::CpuProfileNode::kNoLineNumberInfo,
|
||||
CodeEntry::kInheritsSecurityToken);
|
||||
TokenEnumerator::kInheritsSecurityToken);
|
||||
code_entries_.Add(entry);
|
||||
return entry;
|
||||
}
|
||||
|
@ -666,27 +691,6 @@ CodeEntry* CpuProfilesCollection::NewCodeEntry(int security_token_id) {
|
|||
}
|
||||
|
||||
|
||||
const char* CpuProfilesCollection::GetName(String* name) {
|
||||
if (name->IsString()) {
|
||||
char* c_name =
|
||||
name->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL).Detach();
|
||||
HashMap::Entry* cache_entry =
|
||||
function_and_resource_names_.Lookup(c_name,
|
||||
name->Hash(),
|
||||
true);
|
||||
if (cache_entry->value == NULL) {
|
||||
// New entry added.
|
||||
cache_entry->value = c_name;
|
||||
} else {
|
||||
DeleteArray(c_name);
|
||||
}
|
||||
return reinterpret_cast<const char*>(cache_entry->value);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char* CpuProfilesCollection::GetName(int args_count) {
|
||||
ASSERT(args_count >= 0);
|
||||
if (args_count_names_.length() <= args_count) {
|
||||
|
|
|
@ -41,6 +41,9 @@ class TokenEnumerator {
|
|||
~TokenEnumerator();
|
||||
int GetTokenId(Object* token);
|
||||
|
||||
static const int kNoSecurityToken = -1;
|
||||
static const int kInheritsSecurityToken = -2;
|
||||
|
||||
private:
|
||||
static void TokenRemovedCallback(v8::Persistent<v8::Value> handle,
|
||||
void* parameter);
|
||||
|
@ -53,6 +56,28 @@ class TokenEnumerator {
|
|||
};
|
||||
|
||||
|
||||
// Provides a storage of strings allocated in C++ heap, to hold them
|
||||
// forever, even if they disappear from JS heap or external storage.
|
||||
class StringsStorage {
|
||||
public:
|
||||
StringsStorage();
|
||||
~StringsStorage();
|
||||
|
||||
const char* GetName(String* name);
|
||||
|
||||
private:
|
||||
INLINE(static bool StringsMatch(void* key1, void* key2)) {
|
||||
return strcmp(reinterpret_cast<char*>(key1),
|
||||
reinterpret_cast<char*>(key2)) == 0;
|
||||
}
|
||||
|
||||
// String::Hash -> const char*
|
||||
HashMap names_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(StringsStorage);
|
||||
};
|
||||
|
||||
|
||||
class CodeEntry {
|
||||
public:
|
||||
explicit INLINE(CodeEntry(int security_token_id));
|
||||
|
@ -78,8 +103,6 @@ class CodeEntry {
|
|||
void CopyData(const CodeEntry& source);
|
||||
|
||||
static const char* kEmptyNamePrefix;
|
||||
static const int kNoSecurityToken = -1;
|
||||
static const int kInheritsSecurityToken = -2;
|
||||
|
||||
private:
|
||||
unsigned call_uid_;
|
||||
|
@ -257,10 +280,12 @@ class CpuProfilesCollection {
|
|||
String* title,
|
||||
double actual_sampling_rate);
|
||||
List<CpuProfile*>* Profiles(int security_token_id);
|
||||
const char* GetName(String* name) {
|
||||
return function_and_resource_names_.GetName(name);
|
||||
}
|
||||
CpuProfile* GetProfile(int security_token_id, unsigned uid);
|
||||
inline bool is_last_profile();
|
||||
|
||||
const char* GetName(String* name);
|
||||
CodeEntry* NewCodeEntry(Logger::LogEventsAndTags tag,
|
||||
String* name, String* resource_name, int line_number);
|
||||
CodeEntry* NewCodeEntry(Logger::LogEventsAndTags tag, const char* name);
|
||||
|
@ -279,17 +304,11 @@ class CpuProfilesCollection {
|
|||
List<CpuProfile*>* GetProfilesList(int security_token_id);
|
||||
int TokenToIndex(int security_token_id);
|
||||
|
||||
INLINE(static bool StringsMatch(void* key1, void* key2)) {
|
||||
return strcmp(reinterpret_cast<char*>(key1),
|
||||
reinterpret_cast<char*>(key2)) == 0;
|
||||
}
|
||||
|
||||
INLINE(static bool UidsMatch(void* key1, void* key2)) {
|
||||
return key1 == key2;
|
||||
}
|
||||
|
||||
// String::Hash -> const char*
|
||||
HashMap function_and_resource_names_;
|
||||
StringsStorage function_and_resource_names_;
|
||||
// args_count -> char*
|
||||
List<char*> args_count_names_;
|
||||
List<CodeEntry*> code_entries_;
|
||||
|
|
|
@ -229,6 +229,10 @@ void ExternalReferenceTable::PopulateTable() {
|
|||
DEBUG_ADDRESS,
|
||||
Debug::k_after_break_target_address << kDebugIdShift,
|
||||
"Debug::after_break_target_address()");
|
||||
Add(Debug_Address(Debug::k_debug_break_slot_address).address(),
|
||||
DEBUG_ADDRESS,
|
||||
Debug::k_debug_break_slot_address << kDebugIdShift,
|
||||
"Debug::debug_break_slot_address()");
|
||||
Add(Debug_Address(Debug::k_debug_break_return_address).address(),
|
||||
DEBUG_ADDRESS,
|
||||
Debug::k_debug_break_return_address << kDebugIdShift,
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
// cannot be changed without changing the SCons build script.
|
||||
#define MAJOR_VERSION 2
|
||||
#define MINOR_VERSION 2
|
||||
#define BUILD_NUMBER 15
|
||||
#define BUILD_NUMBER 16
|
||||
#define PATCH_LEVEL 0
|
||||
#define CANDIDATE_VERSION false
|
||||
|
||||
|
|
|
@ -210,6 +210,10 @@ void RelocInfo::apply(intptr_t delta) {
|
|||
// Special handling of js_return when a break point is set (call
|
||||
// instruction has been inserted).
|
||||
Memory::int32_at(pc_ + 1) -= static_cast<int32_t>(delta); // relocate entry
|
||||
} else if (rmode_ == DEBUG_BREAK_SLOT && IsPatchedDebugBreakSlotSequence()) {
|
||||
// Special handling of debug break slot when a break point is set (call
|
||||
// instruction has been inserted).
|
||||
Memory::int32_at(pc_ + 1) -= static_cast<int32_t>(delta); // relocate entry
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -298,6 +302,11 @@ bool RelocInfo::IsPatchedReturnSequence() {
|
|||
}
|
||||
|
||||
|
||||
bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
|
||||
return !Assembler::IsNop(pc());
|
||||
}
|
||||
|
||||
|
||||
Address RelocInfo::call_address() {
|
||||
ASSERT(IsPatchedReturnSequence());
|
||||
return Memory::Address_at(
|
||||
|
@ -341,8 +350,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
|
|||
visitor->VisitExternalReference(target_reference_address());
|
||||
#ifdef ENABLE_DEBUGGER_SUPPORT
|
||||
} else if (Debug::has_break_points() &&
|
||||
RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) {
|
||||
((RelocInfo::IsJSReturn(mode) &&
|
||||
IsPatchedReturnSequence()) ||
|
||||
(RelocInfo::IsDebugBreakSlot(mode) &&
|
||||
IsPatchedDebugBreakSlotSequence()))) {
|
||||
visitor->VisitDebugTarget(this);
|
||||
#endif
|
||||
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
|
||||
|
|
|
@ -2800,6 +2800,13 @@ void Assembler::RecordJSReturn() {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::RecordDebugBreakSlot() {
|
||||
WriteRecordedPositions();
|
||||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
|
||||
}
|
||||
|
||||
|
||||
void Assembler::RecordComment(const char* msg) {
|
||||
if (FLAG_debug_code) {
|
||||
EnsureSpace ensure_space(this);
|
||||
|
@ -2822,13 +2829,16 @@ void Assembler::RecordStatementPosition(int pos) {
|
|||
}
|
||||
|
||||
|
||||
void Assembler::WriteRecordedPositions() {
|
||||
bool Assembler::WriteRecordedPositions() {
|
||||
bool written = false;
|
||||
|
||||
// Write the statement position if it is different from what was written last
|
||||
// time.
|
||||
if (current_statement_position_ != written_statement_position_) {
|
||||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
|
||||
written_statement_position_ = current_statement_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Write the position if it is different from what was written last time and
|
||||
|
@ -2838,7 +2848,11 @@ void Assembler::WriteRecordedPositions() {
|
|||
EnsureSpace ensure_space(this);
|
||||
RecordRelocInfo(RelocInfo::POSITION, current_position_);
|
||||
written_position_ = current_position_;
|
||||
written = true;
|
||||
}
|
||||
|
||||
// Return whether something was written.
|
||||
return written;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -455,6 +455,11 @@ class Assembler : public Malloced {
|
|||
// return address. TODO: Use return sequence length instead.
|
||||
// Should equal Debug::kX64JSReturnSequenceLength - kCallTargetAddressOffset;
|
||||
static const int kPatchReturnSequenceAddressOffset = 13 - 4;
|
||||
// Distance between start of patched debug break slot and where the
|
||||
// 32-bit displacement of a near call would be, relative to the pushed
|
||||
// return address. TODO: Use return sequence length instead.
|
||||
// Should equal Debug::kX64JSReturnSequenceLength - kCallTargetAddressOffset;
|
||||
static const int kPatchDebugBreakSlotAddressOffset = 13 - 4;
|
||||
// TODO(X64): Rename this, removing the "Real", after changing the above.
|
||||
static const int kRealPatchReturnSequenceAddressOffset = 2;
|
||||
|
||||
|
@ -463,6 +468,10 @@ class Assembler : public Malloced {
|
|||
static const int kCallInstructionLength = 13;
|
||||
static const int kJSReturnSequenceLength = 13;
|
||||
|
||||
// The debug break slot must be able to contain a call instruction.
|
||||
static const int kDebugBreakSlotLength = kCallInstructionLength;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Code generation
|
||||
//
|
||||
|
@ -1135,13 +1144,16 @@ class Assembler : public Malloced {
|
|||
// Mark address of the ExitJSFrame code.
|
||||
void RecordJSReturn();
|
||||
|
||||
// Mark address of a debug break slot.
|
||||
void RecordDebugBreakSlot();
|
||||
|
||||
// Record a comment relocation entry that can be used by a disassembler.
|
||||
// Use --debug_code to enable.
|
||||
void RecordComment(const char* msg);
|
||||
|
||||
void RecordPosition(int pos);
|
||||
void RecordStatementPosition(int pos);
|
||||
void WriteRecordedPositions();
|
||||
bool WriteRecordedPositions();
|
||||
|
||||
int pc_offset() const { return static_cast<int>(pc_ - buffer_); }
|
||||
int current_statement_position() const { return current_statement_position_; }
|
||||
|
@ -1159,6 +1171,8 @@ class Assembler : public Malloced {
|
|||
return static_cast<int>(reloc_info_writer.pos() - pc_);
|
||||
}
|
||||
|
||||
static bool IsNop(Address addr) { return *addr == 0x90; }
|
||||
|
||||
// Avoid overflows for displacements etc.
|
||||
static const int kMaximalBufferSize = 512*MB;
|
||||
static const int kMinimalBufferSize = 4*KB;
|
||||
|
|
|
@ -314,7 +314,9 @@ class CodeGenerator: public AstVisitor {
|
|||
static bool ShouldGenerateLog(Expression* type);
|
||||
#endif
|
||||
|
||||
static void RecordPositions(MacroAssembler* masm, int pos);
|
||||
static bool RecordPositions(MacroAssembler* masm,
|
||||
int pos,
|
||||
bool right_here = false);
|
||||
|
||||
// Accessors
|
||||
MacroAssembler* masm() { return masm_; }
|
||||
|
|
|
@ -181,10 +181,31 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
|
|||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlot(MacroAssembler* masm) {
|
||||
// Generate enough nop's to make space for a call instruction.
|
||||
Label check_codesize;
|
||||
__ bind(&check_codesize);
|
||||
__ RecordDebugBreakSlot();
|
||||
for (int i = 0; i < Assembler::kDebugBreakSlotLength; i++) {
|
||||
__ nop();
|
||||
}
|
||||
ASSERT_EQ(Assembler::kDebugBreakSlotLength,
|
||||
masm->SizeOfCodeGeneratedSince(&check_codesize));
|
||||
}
|
||||
|
||||
|
||||
void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
|
||||
// In the places where a debug break slot is inserted no registers can contain
|
||||
// object pointers.
|
||||
Generate_DebugBreakCallHelper(masm, 0, true);
|
||||
}
|
||||
|
||||
|
||||
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
|
||||
masm->Abort("LiveEdit frame dropping is not supported on x64");
|
||||
}
|
||||
|
||||
|
||||
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
|
||||
masm->Abort("LiveEdit frame dropping is not supported on x64");
|
||||
}
|
||||
|
@ -217,6 +238,28 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
|
|||
Assembler::kJSReturnSequenceLength - Assembler::kCallInstructionLength);
|
||||
}
|
||||
|
||||
|
||||
bool BreakLocationIterator::IsDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
// Check whether the debug break slot instructions have been patched.
|
||||
return !Assembler::IsNop(rinfo()->pc());
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::SetDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
rinfo()->PatchCodeWithCall(
|
||||
Debug::debug_break_slot()->entry(),
|
||||
Assembler::kDebugBreakSlotLength - Assembler::kCallInstructionLength);
|
||||
}
|
||||
|
||||
|
||||
void BreakLocationIterator::ClearDebugBreakAtSlot() {
|
||||
ASSERT(IsDebugBreakSlot());
|
||||
rinfo()->PatchCode(original_rinfo()->pc(), Assembler::kDebugBreakSlotLength);
|
||||
}
|
||||
|
||||
|
||||
#endif // ENABLE_DEBUGGER_SUPPORT
|
||||
|
||||
} } // namespace v8::internal
|
||||
|
|
|
@ -612,30 +612,33 @@ THREADED_TEST(ScavengeExternalAsciiString) {
|
|||
}
|
||||
|
||||
|
||||
static int dispose_count = 0;
|
||||
static void DisposeExternalStringCount(
|
||||
String::ExternalStringResourceBase* resource) {
|
||||
dispose_count++;
|
||||
}
|
||||
class TestAsciiResourceWithDisposeControl: public TestAsciiResource {
|
||||
public:
|
||||
static int dispose_calls;
|
||||
|
||||
TestAsciiResourceWithDisposeControl(const char* data, bool dispose)
|
||||
: TestAsciiResource(data),
|
||||
dispose_(dispose) { }
|
||||
|
||||
void Dispose() {
|
||||
++dispose_calls;
|
||||
if (dispose_) delete this;
|
||||
}
|
||||
private:
|
||||
bool dispose_;
|
||||
};
|
||||
|
||||
|
||||
static void DisposeExternalStringDeleteAndCount(
|
||||
String::ExternalStringResourceBase* resource) {
|
||||
delete resource;
|
||||
dispose_count++;
|
||||
}
|
||||
int TestAsciiResourceWithDisposeControl::dispose_calls = 0;
|
||||
|
||||
|
||||
TEST(ExternalStringWithResourceDisposeCallback) {
|
||||
TEST(ExternalStringWithDisposeHandling) {
|
||||
const char* c_source = "1 + 2 * 3";
|
||||
|
||||
// Set an external string collected callback which does not delete the object.
|
||||
v8::V8::SetExternalStringDiposeCallback(DisposeExternalStringCount);
|
||||
|
||||
// Use a stack allocated external string resource allocated object.
|
||||
dispose_count = 0;
|
||||
TestAsciiResource::dispose_count = 0;
|
||||
TestAsciiResource res_stack(i::StrDup(c_source));
|
||||
TestAsciiResourceWithDisposeControl::dispose_calls = 0;
|
||||
TestAsciiResourceWithDisposeControl res_stack(i::StrDup(c_source), false);
|
||||
{
|
||||
v8::HandleScope scope;
|
||||
LocalContext env;
|
||||
|
@ -649,16 +652,14 @@ TEST(ExternalStringWithResourceDisposeCallback) {
|
|||
}
|
||||
v8::internal::CompilationCache::Clear();
|
||||
v8::internal::Heap::CollectAllGarbage(false);
|
||||
CHECK_EQ(1, dispose_count);
|
||||
CHECK_EQ(1, TestAsciiResourceWithDisposeControl::dispose_calls);
|
||||
CHECK_EQ(0, TestAsciiResource::dispose_count);
|
||||
|
||||
// Set an external string collected callback which does delete the object.
|
||||
v8::V8::SetExternalStringDiposeCallback(DisposeExternalStringDeleteAndCount);
|
||||
|
||||
// Use a heap allocated external string resource allocated object.
|
||||
dispose_count = 0;
|
||||
TestAsciiResource::dispose_count = 0;
|
||||
TestAsciiResource* res_heap = new TestAsciiResource(i::StrDup(c_source));
|
||||
TestAsciiResourceWithDisposeControl::dispose_calls = 0;
|
||||
TestAsciiResource* res_heap =
|
||||
new TestAsciiResourceWithDisposeControl(i::StrDup(c_source), true);
|
||||
{
|
||||
v8::HandleScope scope;
|
||||
LocalContext env;
|
||||
|
@ -672,7 +673,7 @@ TEST(ExternalStringWithResourceDisposeCallback) {
|
|||
}
|
||||
v8::internal::CompilationCache::Clear();
|
||||
v8::internal::Heap::CollectAllGarbage(false);
|
||||
CHECK_EQ(1, dispose_count);
|
||||
CHECK_EQ(1, TestAsciiResourceWithDisposeControl::dispose_calls);
|
||||
CHECK_EQ(1, TestAsciiResource::dispose_count);
|
||||
}
|
||||
|
||||
|
@ -8497,6 +8498,30 @@ TEST(PreCompileDeserializationError) {
|
|||
}
|
||||
|
||||
|
||||
// Verifies that the Handle<String> and const char* versions of the API produce
|
||||
// the same results (at least for one trivial case).
|
||||
TEST(PreCompileAPIVariationsAreSame) {
|
||||
v8::V8::Initialize();
|
||||
v8::HandleScope scope;
|
||||
|
||||
const char* cstring = "function foo(a) { return a+1; }";
|
||||
v8::ScriptData* sd_from_cstring =
|
||||
v8::ScriptData::PreCompile(cstring, i::StrLength(cstring));
|
||||
|
||||
TestAsciiResource* resource = new TestAsciiResource(cstring);
|
||||
v8::ScriptData* sd_from_istring = v8::ScriptData::PreCompile(
|
||||
v8::String::NewExternal(resource));
|
||||
|
||||
CHECK_EQ(sd_from_cstring->Length(), sd_from_istring->Length());
|
||||
CHECK_EQ(0, memcmp(sd_from_cstring->Data(),
|
||||
sd_from_istring->Data(),
|
||||
sd_from_cstring->Length()));
|
||||
|
||||
delete sd_from_cstring;
|
||||
delete sd_from_istring;
|
||||
}
|
||||
|
||||
|
||||
// This tests that we do not allow dictionary load/call inline caches
|
||||
// to use functions that have not yet been compiled. The potential
|
||||
// problem of loading a function that has not yet been compiled can
|
||||
|
|
|
@ -16,6 +16,7 @@ using i::CpuProfilesCollection;
|
|||
using i::ProfileGenerator;
|
||||
using i::ProfileNode;
|
||||
using i::ProfilerEventsProcessor;
|
||||
using i::TokenEnumerator;
|
||||
|
||||
|
||||
TEST(StartStop) {
|
||||
|
@ -115,7 +116,7 @@ TEST(CodeEvents) {
|
|||
processor.CodeCreateEvent(i::Logger::STUB_TAG, 3, ToAddress(0x1600), 0x10);
|
||||
processor.CodeDeleteEvent(ToAddress(0x1600));
|
||||
processor.FunctionCreateEvent(ToAddress(0x1700), ToAddress(0x1000),
|
||||
CodeEntry::kNoSecurityToken);
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
// Enqueue a tick event to enable code events processing.
|
||||
EnqueueTickSampleEvent(&processor, ToAddress(0x1000));
|
||||
|
||||
|
@ -178,7 +179,7 @@ TEST(TickEvents) {
|
|||
processor.Stop();
|
||||
processor.Join();
|
||||
CpuProfile* profile =
|
||||
profiles.StopProfiling(CodeEntry::kNoSecurityToken, "", 1);
|
||||
profiles.StopProfiling(TokenEnumerator::kNoSecurityToken, "", 1);
|
||||
CHECK_NE(NULL, profile);
|
||||
|
||||
// Check call trees.
|
||||
|
|
|
@ -1231,6 +1231,11 @@ TEST(GCDuringBreakPointProcessing) {
|
|||
SetBreakPoint(foo, 0);
|
||||
CallWithBreakPoints(env->Global(), foo, 1, 25);
|
||||
|
||||
// Test debug break slot break point with garbage collection.
|
||||
foo = CompileFunction(&env, "function foo(){var a;}", "foo");
|
||||
SetBreakPoint(foo, 0);
|
||||
CallWithBreakPoints(env->Global(), foo, 1, 25);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
@ -1660,7 +1665,7 @@ TEST(ConditionalScriptBreakPoint) {
|
|||
f->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(1, break_point_hit_count);
|
||||
|
||||
ChangeScriptBreakPointConditionFromJS(sbp1, "a % 2 == 0");
|
||||
ChangeScriptBreakPointConditionFromJS(sbp1, "x % 2 == 0");
|
||||
break_point_hit_count = 0;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
f->Call(env->Global(), 0, NULL);
|
||||
|
@ -2144,17 +2149,19 @@ TEST(DebugEvaluate) {
|
|||
v8::Local<v8::Function> foo = CompileFunction(&env,
|
||||
"function foo(x) {"
|
||||
" var a;"
|
||||
" y=0; /* To ensure break location.*/"
|
||||
" y=0;" // To ensure break location 1.
|
||||
" a=x;"
|
||||
" y=0;" // To ensure break location 2.
|
||||
"}",
|
||||
"foo");
|
||||
const int foo_break_position = 15;
|
||||
const int foo_break_position_1 = 15;
|
||||
const int foo_break_position_2 = 29;
|
||||
|
||||
// Arguments with one parameter "Hello, world!"
|
||||
v8::Handle<v8::Value> argv_foo[1] = { v8::String::New("Hello, world!") };
|
||||
|
||||
// Call foo with breakpoint set before a=x and undefined as parameter.
|
||||
int bp = SetBreakPoint(foo, foo_break_position);
|
||||
int bp = SetBreakPoint(foo, foo_break_position_1);
|
||||
checks = checks_uu;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
|
||||
|
@ -2164,7 +2171,7 @@ TEST(DebugEvaluate) {
|
|||
|
||||
// Call foo with breakpoint set after a=x and parameter "Hello, world!".
|
||||
ClearBreakPoint(bp);
|
||||
SetBreakPoint(foo, foo_break_position + 1);
|
||||
SetBreakPoint(foo, foo_break_position_2);
|
||||
checks = checks_hh;
|
||||
foo->Call(env->Global(), 1, argv_foo);
|
||||
|
||||
|
@ -2426,6 +2433,9 @@ TEST(DebugStepKeyedLoadLoop) {
|
|||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping of keyed load. The statement 'y=1'
|
||||
// is there to have more than one breakable statement in the loop, TODO(315).
|
||||
v8::Local<v8::Function> foo = CompileFunction(
|
||||
|
@ -2451,9 +2461,6 @@ TEST(DebugStepKeyedLoadLoop) {
|
|||
v8::Handle<v8::Value> args[kArgc] = { a };
|
||||
foo->Call(env->Global(), kArgc, args);
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Setup break point and step through the function.
|
||||
SetBreakPoint(foo, 3);
|
||||
step_action = StepNext;
|
||||
|
@ -2461,7 +2468,7 @@ TEST(DebugStepKeyedLoadLoop) {
|
|||
foo->Call(env->Global(), kArgc, args);
|
||||
|
||||
// With stepping all break locations are hit.
|
||||
CHECK_EQ(22, break_point_hit_count);
|
||||
CHECK_EQ(33, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -2473,6 +2480,9 @@ TEST(DebugStepKeyedStoreLoop) {
|
|||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping of keyed store. The statement 'y=1'
|
||||
// is there to have more than one breakable statement in the loop, TODO(315).
|
||||
v8::Local<v8::Function> foo = CompileFunction(
|
||||
|
@ -2497,9 +2507,6 @@ TEST(DebugStepKeyedStoreLoop) {
|
|||
v8::Handle<v8::Value> args[kArgc] = { a };
|
||||
foo->Call(env->Global(), kArgc, args);
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Setup break point and step through the function.
|
||||
SetBreakPoint(foo, 3);
|
||||
step_action = StepNext;
|
||||
|
@ -2507,7 +2514,7 @@ TEST(DebugStepKeyedStoreLoop) {
|
|||
foo->Call(env->Global(), kArgc, args);
|
||||
|
||||
// With stepping all break locations are hit.
|
||||
CHECK_EQ(22, break_point_hit_count);
|
||||
CHECK_EQ(32, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -2519,6 +2526,9 @@ TEST(DebugStepNamedLoadLoop) {
|
|||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping of named load.
|
||||
v8::Local<v8::Function> foo = CompileFunction(
|
||||
&env,
|
||||
|
@ -2541,9 +2551,6 @@ TEST(DebugStepNamedLoadLoop) {
|
|||
// Call function without any break points to ensure inlining is in place.
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Setup break point and step through the function.
|
||||
SetBreakPoint(foo, 4);
|
||||
step_action = StepNext;
|
||||
|
@ -2551,7 +2558,7 @@ TEST(DebugStepNamedLoadLoop) {
|
|||
foo->Call(env->Global(), 0, NULL);
|
||||
|
||||
// With stepping all break locations are hit.
|
||||
CHECK_EQ(41, break_point_hit_count);
|
||||
CHECK_EQ(53, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -2563,6 +2570,9 @@ TEST(DebugStepLinearMixedICs) {
|
|||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env,
|
||||
"function bar() {};"
|
||||
|
@ -2573,15 +2583,12 @@ TEST(DebugStepLinearMixedICs) {
|
|||
" a=1;b=2;x=a;y[index]=3;x=y[index];bar();}", "foo");
|
||||
SetBreakPoint(foo, 0);
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
|
||||
// With stepping all break locations are hit.
|
||||
CHECK_EQ(8, break_point_hit_count);
|
||||
CHECK_EQ(11, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -2601,6 +2608,66 @@ TEST(DebugStepLinearMixedICs) {
|
|||
}
|
||||
|
||||
|
||||
TEST(DebugStepDeclarations) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const char* src = "function foo() { "
|
||||
" var a;"
|
||||
" var b = 1;"
|
||||
" var c = foo;"
|
||||
" var d = Math.floor;"
|
||||
" var e = b + d(1.2);"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
SetBreakPoint(foo, 0);
|
||||
|
||||
// Stepping through the declarations.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepLocals) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const char* src = "function foo() { "
|
||||
" var a,b;"
|
||||
" a = 1;"
|
||||
" b = a + 2;"
|
||||
" b = 1 + 2 + 3;"
|
||||
" a = Math.floor(b);"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
SetBreakPoint(foo, 0);
|
||||
|
||||
// Stepping through the declarations.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepIf) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
@ -2627,14 +2694,14 @@ TEST(DebugStepIf) {
|
|||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_true[argc] = { v8::True() };
|
||||
foo->Call(env->Global(), argc, argv_true);
|
||||
CHECK_EQ(3, break_point_hit_count);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
|
||||
// Stepping through the false part.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_false[argc] = { v8::False() };
|
||||
foo->Call(env->Global(), argc, argv_false);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
CHECK_EQ(5, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
|
@ -2662,6 +2729,7 @@ TEST(DebugStepSwitch) {
|
|||
" case 3:"
|
||||
" d = 1;"
|
||||
" e = 1;"
|
||||
" f = 1;"
|
||||
" break;"
|
||||
" }"
|
||||
"}";
|
||||
|
@ -2673,21 +2741,97 @@ TEST(DebugStepSwitch) {
|
|||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_1[argc] = { v8::Number::New(1) };
|
||||
foo->Call(env->Global(), argc, argv_1);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
|
||||
// Another case.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_2[argc] = { v8::Number::New(2) };
|
||||
foo->Call(env->Global(), argc, argv_2);
|
||||
CHECK_EQ(3, break_point_hit_count);
|
||||
CHECK_EQ(5, break_point_hit_count);
|
||||
|
||||
// Last case.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_3[argc] = { v8::Number::New(3) };
|
||||
foo->Call(env->Global(), argc, argv_3);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
CHECK_EQ(7, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepWhile) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const int argc = 1;
|
||||
const char* src = "function foo(x) { "
|
||||
" var a = 0;"
|
||||
" while (a < x) {"
|
||||
" a++;"
|
||||
" }"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
SetBreakPoint(foo, 8); // "var a = 0;"
|
||||
|
||||
// Looping 10 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
|
||||
foo->Call(env->Global(), argc, argv_10);
|
||||
CHECK_EQ(23, break_point_hit_count);
|
||||
|
||||
// Looping 100 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
|
||||
foo->Call(env->Global(), argc, argv_100);
|
||||
CHECK_EQ(203, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepDoWhile) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const int argc = 1;
|
||||
const char* src = "function foo(x) { "
|
||||
" var a = 0;"
|
||||
" do {"
|
||||
" a++;"
|
||||
" } while (a < x)"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
SetBreakPoint(foo, 8); // "var a = 0;"
|
||||
|
||||
// Looping 10 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
|
||||
foo->Call(env->Global(), argc, argv_10);
|
||||
CHECK_EQ(22, break_point_hit_count);
|
||||
|
||||
// Looping 100 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
|
||||
foo->Call(env->Global(), argc, argv_100);
|
||||
CHECK_EQ(202, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
|
@ -2733,6 +2877,210 @@ TEST(DebugStepFor) {
|
|||
}
|
||||
|
||||
|
||||
TEST(DebugStepForContinue) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const int argc = 1;
|
||||
const char* src = "function foo(x) { "
|
||||
" var a = 0;"
|
||||
" var b = 0;"
|
||||
" var c = 0;"
|
||||
" for (var i = 0; i < x; i++) {"
|
||||
" a++;"
|
||||
" if (a % 2 == 0) continue;"
|
||||
" b++;"
|
||||
" c++;"
|
||||
" }"
|
||||
" return b;"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
v8::Handle<v8::Value> result;
|
||||
SetBreakPoint(foo, 8); // "var a = 0;"
|
||||
|
||||
// Each loop generates 4 or 5 steps depending on whether a is equal.
|
||||
|
||||
// Looping 10 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
|
||||
result = foo->Call(env->Global(), argc, argv_10);
|
||||
CHECK_EQ(5, result->Int32Value());
|
||||
CHECK_EQ(50, break_point_hit_count);
|
||||
|
||||
// Looping 100 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
|
||||
result = foo->Call(env->Global(), argc, argv_100);
|
||||
CHECK_EQ(50, result->Int32Value());
|
||||
CHECK_EQ(455, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepForBreak) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const int argc = 1;
|
||||
const char* src = "function foo(x) { "
|
||||
" var a = 0;"
|
||||
" var b = 0;"
|
||||
" var c = 0;"
|
||||
" for (var i = 0; i < 1000; i++) {"
|
||||
" a++;"
|
||||
" if (a == x) break;"
|
||||
" b++;"
|
||||
" c++;"
|
||||
" }"
|
||||
" return b;"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
v8::Handle<v8::Value> result;
|
||||
SetBreakPoint(foo, 8); // "var a = 0;"
|
||||
|
||||
// Each loop generates 5 steps except for the last (when break is executed)
|
||||
// which only generates 4.
|
||||
|
||||
// Looping 10 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
|
||||
result = foo->Call(env->Global(), argc, argv_10);
|
||||
CHECK_EQ(9, result->Int32Value());
|
||||
CHECK_EQ(53, break_point_hit_count);
|
||||
|
||||
// Looping 100 times.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
|
||||
result = foo->Call(env->Global(), argc, argv_100);
|
||||
CHECK_EQ(99, result->Int32Value());
|
||||
CHECK_EQ(503, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepForIn) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
v8::Local<v8::Function> foo;
|
||||
const char* src_1 = "function foo() { "
|
||||
" var a = [1, 2];"
|
||||
" for (x in a) {"
|
||||
" b = 0;"
|
||||
" }"
|
||||
"}";
|
||||
foo = CompileFunction(&env, src_1, "foo");
|
||||
SetBreakPoint(foo, 0); // "var a = ..."
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
|
||||
const char* src_2 = "function foo() { "
|
||||
" var a = {a:[1, 2, 3]};"
|
||||
" for (x in a.a) {"
|
||||
" b = 0;"
|
||||
" }"
|
||||
"}";
|
||||
foo = CompileFunction(&env, src_2, "foo");
|
||||
SetBreakPoint(foo, 0); // "var a = ..."
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(8, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugStepWith) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const char* src = "function foo(x) { "
|
||||
" var a = {};"
|
||||
" with (a) {}"
|
||||
" with (b) {}"
|
||||
"}";
|
||||
env->Global()->Set(v8::String::New("b"), v8::Object::New());
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
v8::Handle<v8::Value> result;
|
||||
SetBreakPoint(foo, 8); // "var a = {};"
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(DebugConditional) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
||||
// Register a debug event listener which steps and counts.
|
||||
v8::Debug::SetDebugEventListener(DebugEventStep);
|
||||
|
||||
// Create a function for testing stepping.
|
||||
const char* src = "function foo(x) { "
|
||||
" var a;"
|
||||
" a = x ? 1 : 2;"
|
||||
" return a;"
|
||||
"}";
|
||||
v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
|
||||
SetBreakPoint(foo, 0); // "var a;"
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(5, break_point_hit_count);
|
||||
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
const int argc = 1;
|
||||
v8::Handle<v8::Value> argv_true[argc] = { v8::True() };
|
||||
foo->Call(env->Global(), argc, argv_true);
|
||||
CHECK_EQ(5, break_point_hit_count);
|
||||
|
||||
// Get rid of the debug event listener.
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
}
|
||||
|
||||
|
||||
TEST(StepInOutSimple) {
|
||||
v8::HandleScope scope;
|
||||
DebugLocalContext env;
|
||||
|
@ -2854,7 +3202,7 @@ TEST(StepInOutBranch) {
|
|||
// Step through invocation of a.
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
expected_step_sequence = "abaca";
|
||||
expected_step_sequence = "abbaca";
|
||||
a->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
@ -2923,7 +3271,7 @@ TEST(DebugStepFunctionApply) {
|
|||
foo->Call(env->Global(), 0, NULL);
|
||||
|
||||
// With stepping all break locations are hit.
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
CHECK_EQ(7, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -2967,14 +3315,14 @@ TEST(DebugStepFunctionCall) {
|
|||
// Check stepping where the if condition in bar is false.
|
||||
break_point_hit_count = 0;
|
||||
foo->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(4, break_point_hit_count);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
|
||||
// Check stepping where the if condition in bar is true.
|
||||
break_point_hit_count = 0;
|
||||
const int argc = 1;
|
||||
v8::Handle<v8::Value> argv[argc] = { v8::True() };
|
||||
foo->Call(env->Global(), argc, argv);
|
||||
CHECK_EQ(6, break_point_hit_count);
|
||||
CHECK_EQ(8, break_point_hit_count);
|
||||
|
||||
v8::Debug::SetDebugEventListener(NULL);
|
||||
CheckDebuggerUnloaded();
|
||||
|
@ -3267,14 +3615,13 @@ TEST(StepWithException) {
|
|||
b->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
||||
// Step through invocation of d + e.
|
||||
v8::Local<v8::Function> d = CompileFunction(&env, src, "d");
|
||||
SetBreakPoint(d, 0);
|
||||
ChangeBreakOnException(false, true);
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
expected_step_sequence = "dded";
|
||||
expected_step_sequence = "ddedd";
|
||||
d->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
@ -3283,7 +3630,7 @@ TEST(StepWithException) {
|
|||
ChangeBreakOnException(true, true);
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
expected_step_sequence = "ddeed";
|
||||
expected_step_sequence = "ddeedd";
|
||||
d->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
@ -3294,7 +3641,7 @@ TEST(StepWithException) {
|
|||
ChangeBreakOnException(false, true);
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
expected_step_sequence = "ffghf";
|
||||
expected_step_sequence = "ffghhff";
|
||||
f->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
@ -3303,7 +3650,7 @@ TEST(StepWithException) {
|
|||
ChangeBreakOnException(true, true);
|
||||
step_action = StepIn;
|
||||
break_point_hit_count = 0;
|
||||
expected_step_sequence = "ffghhf";
|
||||
expected_step_sequence = "ffghhhff";
|
||||
f->Call(env->Global(), 0, NULL);
|
||||
CHECK_EQ(StrLength(expected_step_sequence),
|
||||
break_point_hit_count);
|
||||
|
|
|
@ -244,6 +244,9 @@ TEST(DisasmIa320) {
|
|||
|
||||
__ test(edx, Immediate(12345));
|
||||
__ test(edx, Operand(ebx, ecx, times_8, 10000));
|
||||
__ test(Operand(esi, edi, times_1, -20000000), Immediate(300000000));
|
||||
__ test_b(edx, Operand(ecx, ebx, times_2, 1000));
|
||||
__ test_b(Operand(eax, -20), 0x9A);
|
||||
__ nop();
|
||||
|
||||
__ xor_(edx, 12345);
|
||||
|
|
|
@ -957,3 +957,42 @@ TEST(Regression39128) {
|
|||
// Check that region covering inobject property 1 is marked dirty.
|
||||
CHECK(page->IsRegionDirty(clone_addr + (object_size - kPointerSize)));
|
||||
}
|
||||
|
||||
TEST(TestCodeFlushing) {
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
InitializeVM();
|
||||
v8::HandleScope scope;
|
||||
const char* source = "function foo() {"
|
||||
" var x = 42;"
|
||||
" var y = 42;"
|
||||
" var z = x + y;"
|
||||
"};"
|
||||
"foo()";
|
||||
Handle<String> foo_name = Factory::LookupAsciiSymbol("foo");
|
||||
|
||||
// This compile will add the code to the compilation cache.
|
||||
CompileRun(source);
|
||||
|
||||
// Check function is compiled.
|
||||
Object* func_value = Top::context()->global()->GetProperty(*foo_name);
|
||||
CHECK(func_value->IsJSFunction());
|
||||
Handle<JSFunction> function(JSFunction::cast(func_value));
|
||||
CHECK(function->shared()->is_compiled());
|
||||
|
||||
Heap::CollectAllGarbage(true);
|
||||
Heap::CollectAllGarbage(true);
|
||||
|
||||
// foo should still be in the compilation cache and therefore not
|
||||
// have been removed.
|
||||
CHECK(function->shared()->is_compiled());
|
||||
Heap::CollectAllGarbage(true);
|
||||
Heap::CollectAllGarbage(true);
|
||||
Heap::CollectAllGarbage(true);
|
||||
Heap::CollectAllGarbage(true);
|
||||
|
||||
// foo should no longer be in the compilation cache
|
||||
CHECK(!function->shared()->is_compiled());
|
||||
// Call foo to get it recompiled.
|
||||
CompileRun("foo()");
|
||||
CHECK(function->shared()->is_compiled());
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ class TokenEnumeratorTester {
|
|||
|
||||
TEST(TokenEnumerator) {
|
||||
TokenEnumerator te;
|
||||
CHECK_EQ(CodeEntry::kNoSecurityToken, te.GetTokenId(NULL));
|
||||
CHECK_EQ(TokenEnumerator::kNoSecurityToken, te.GetTokenId(NULL));
|
||||
v8::HandleScope hs;
|
||||
v8::Local<v8::String> token1(v8::String::New("1"));
|
||||
CHECK_EQ(0, te.GetTokenId(*v8::Utils::OpenHandle(*token1)));
|
||||
|
@ -65,20 +65,20 @@ TEST(TokenEnumerator) {
|
|||
|
||||
TEST(ProfileNodeFindOrAddChild) {
|
||||
ProfileNode node(NULL, NULL);
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
ProfileNode* childNode1 = node.FindOrAddChild(&entry1);
|
||||
CHECK_NE(NULL, childNode1);
|
||||
CHECK_EQ(childNode1, node.FindOrAddChild(&entry1));
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
ProfileNode* childNode2 = node.FindOrAddChild(&entry2);
|
||||
CHECK_NE(NULL, childNode2);
|
||||
CHECK_NE(childNode1, childNode2);
|
||||
CHECK_EQ(childNode1, node.FindOrAddChild(&entry1));
|
||||
CHECK_EQ(childNode2, node.FindOrAddChild(&entry2));
|
||||
CodeEntry entry3(
|
||||
i::Logger::FUNCTION_TAG, "", "ccc", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
ProfileNode* childNode3 = node.FindOrAddChild(&entry3);
|
||||
CHECK_NE(NULL, childNode3);
|
||||
CHECK_NE(childNode1, childNode3);
|
||||
|
@ -119,12 +119,12 @@ class ProfileTreeTestHelper {
|
|||
} // namespace
|
||||
|
||||
TEST(ProfileTreeAddPathFromStart) {
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry3(
|
||||
i::Logger::FUNCTION_TAG, "", "ccc", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
ProfileTree tree;
|
||||
ProfileTreeTestHelper helper(&tree);
|
||||
CHECK_EQ(NULL, helper.Walk(&entry1));
|
||||
|
@ -189,12 +189,12 @@ TEST(ProfileTreeAddPathFromStart) {
|
|||
|
||||
|
||||
TEST(ProfileTreeAddPathFromEnd) {
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry3(
|
||||
i::Logger::FUNCTION_TAG, "", "ccc", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
ProfileTree tree;
|
||||
ProfileTreeTestHelper helper(&tree);
|
||||
CHECK_EQ(NULL, helper.Walk(&entry1));
|
||||
|
@ -272,8 +272,8 @@ TEST(ProfileTreeCalculateTotalTicks) {
|
|||
CHECK_EQ(1, empty_tree.root()->total_ticks());
|
||||
CHECK_EQ(1, empty_tree.root()->self_ticks());
|
||||
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry* e1_path[] = {&entry1};
|
||||
Vector<CodeEntry*> e1_path_vec(
|
||||
e1_path, sizeof(e1_path) / sizeof(e1_path[0]));
|
||||
|
@ -294,8 +294,8 @@ TEST(ProfileTreeCalculateTotalTicks) {
|
|||
CHECK_EQ(1, node1->total_ticks());
|
||||
CHECK_EQ(1, node1->self_ticks());
|
||||
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry* e1_e2_path[] = {&entry1, &entry2};
|
||||
Vector<CodeEntry*> e1_e2_path_vec(
|
||||
e1_e2_path, sizeof(e1_e2_path) / sizeof(e1_e2_path[0]));
|
||||
|
@ -330,8 +330,8 @@ TEST(ProfileTreeCalculateTotalTicks) {
|
|||
CodeEntry* e2_path[] = {&entry2};
|
||||
Vector<CodeEntry*> e2_path_vec(
|
||||
e2_path, sizeof(e2_path) / sizeof(e2_path[0]));
|
||||
CodeEntry entry3(
|
||||
i::Logger::FUNCTION_TAG, "", "ccc", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry* e3_path[] = {&entry3};
|
||||
Vector<CodeEntry*> e3_path_vec(
|
||||
e3_path, sizeof(e3_path) / sizeof(e3_path[0]));
|
||||
|
@ -394,7 +394,7 @@ TEST(ProfileTreeFilteredClone) {
|
|||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0, token0);
|
||||
CodeEntry entry4(
|
||||
i::Logger::FUNCTION_TAG, "", "ddd", "", 0,
|
||||
CodeEntry::kInheritsSecurityToken);
|
||||
TokenEnumerator::kInheritsSecurityToken);
|
||||
|
||||
{
|
||||
CodeEntry* e1_e2_path[] = {&entry1, &entry2};
|
||||
|
@ -491,14 +491,14 @@ static inline i::Address ToAddress(int n) {
|
|||
|
||||
TEST(CodeMapAddCode) {
|
||||
CodeMap code_map;
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry3(
|
||||
i::Logger::FUNCTION_TAG, "", "ccc", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry4(
|
||||
i::Logger::FUNCTION_TAG, "", "ddd", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry3(i::Logger::FUNCTION_TAG, "", "ccc", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry4(i::Logger::FUNCTION_TAG, "", "ddd", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
code_map.AddCode(ToAddress(0x1500), &entry1, 0x200);
|
||||
code_map.AddCode(ToAddress(0x1700), &entry2, 0x100);
|
||||
code_map.AddCode(ToAddress(0x1900), &entry3, 0x50);
|
||||
|
@ -525,10 +525,10 @@ TEST(CodeMapAddCode) {
|
|||
|
||||
TEST(CodeMapMoveAndDeleteCode) {
|
||||
CodeMap code_map;
|
||||
CodeEntry entry1(
|
||||
i::Logger::FUNCTION_TAG, "", "aaa", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry2(
|
||||
i::Logger::FUNCTION_TAG, "", "bbb", "", 0, CodeEntry::kNoSecurityToken);
|
||||
CodeEntry entry1(i::Logger::FUNCTION_TAG, "", "aaa", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
CodeEntry entry2(i::Logger::FUNCTION_TAG, "", "bbb", "", 0,
|
||||
TokenEnumerator::kNoSecurityToken);
|
||||
code_map.AddCode(ToAddress(0x1500), &entry1, 0x200);
|
||||
code_map.AddCode(ToAddress(0x1700), &entry2, 0x100);
|
||||
CHECK_EQ(&entry1, code_map.FindEntry(ToAddress(0x1500)));
|
||||
|
@ -601,7 +601,7 @@ TEST(RecordTickSample) {
|
|||
generator.RecordTickSample(sample3);
|
||||
|
||||
CpuProfile* profile =
|
||||
profiles.StopProfiling(CodeEntry::kNoSecurityToken, "", 1);
|
||||
profiles.StopProfiling(TokenEnumerator::kNoSecurityToken, "", 1);
|
||||
CHECK_NE(NULL, profile);
|
||||
ProfileTreeTestHelper top_down_test_helper(profile->top_down());
|
||||
CHECK_EQ(NULL, top_down_test_helper.Walk(entry2));
|
||||
|
|
|
@ -91,7 +91,7 @@ function testInitSlowCaseExtension() {
|
|||
var source = "";
|
||||
// Introduce 100 properties on the context extension object to force
|
||||
// it in slow case.
|
||||
for (var i = 0; i < 100; i++) source += ("var a" + i + " = i;");
|
||||
for (var i = 0; i < 100; i++) source += ("var a" + i + " = " + i + ";");
|
||||
source += "const x = 10; assertEquals(10, x); x = 11; assertEquals(10, x)";
|
||||
eval(source);
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ Debug.setListener(listener);
|
|||
count = 0;
|
||||
function f() {};
|
||||
function g() {h(count++)};
|
||||
function h(x) {var a=x;};
|
||||
function h(x) {var a=x; return a};
|
||||
|
||||
|
||||
// Conditional breakpoint which syntax error.
|
||||
|
@ -136,7 +136,7 @@ Debug.clearBreakPoint(bp);
|
|||
|
||||
// Conditional breakpoint which checks a local variable.
|
||||
break_point_hit_count = 0;
|
||||
bp = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
|
||||
bp = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
|
||||
for (var i = 0; i < 10; i++) {
|
||||
g();
|
||||
}
|
||||
|
@ -146,8 +146,8 @@ Debug.clearBreakPoint(bp);
|
|||
|
||||
// Multiple conditional breakpoint which the same condition.
|
||||
break_point_hit_count = 0;
|
||||
bp1 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
|
||||
bp2 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
|
||||
bp1 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
|
||||
bp2 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
|
||||
for (var i = 0; i < 10; i++) {
|
||||
g();
|
||||
}
|
||||
|
@ -159,8 +159,8 @@ Debug.clearBreakPoint(bp2);
|
|||
|
||||
// Multiple conditional breakpoint which different conditions.
|
||||
break_point_hit_count = 0;
|
||||
bp1 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
|
||||
bp2 = Debug.setBreakPoint(h, 0, 0, '(a + 1) % 2 == 0');
|
||||
bp1 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
|
||||
bp2 = Debug.setBreakPoint(h, 0, 23, '(a + 1) % 2 == 0');
|
||||
for (var i = 0; i < 10; i++) {
|
||||
g();
|
||||
}
|
||||
|
|
|
@ -55,8 +55,9 @@ Debug.setListener(listener);
|
|||
|
||||
// Test debug event for break point.
|
||||
function f() {
|
||||
for (i = 0; i < 1000; i++) { // Line 1.
|
||||
x = 1; // Line 2.
|
||||
var i; // Line 1.
|
||||
for (i = 0; i < 1000; i++) { // Line 2.
|
||||
x = 1; // Line 3.
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -74,7 +75,7 @@ assertEquals(499, result);
|
|||
// multiple steps have been requested.
|
||||
state = 0;
|
||||
result = -1;
|
||||
bp2 = Debug.setBreakPoint(f, 2);
|
||||
bp2 = Debug.setBreakPoint(f, 3);
|
||||
f();
|
||||
assertEquals(0, result);
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче