1 // Copyright 2012 the V8 project authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #ifndef V8_DEBUG_LIVEEDIT_H_ 6 #define V8_DEBUG_LIVEEDIT_H_ 7 8 9 // Live Edit feature implementation. 10 // User should be able to change script on already running VM. This feature 11 // matches hot swap features in other frameworks. 12 // 13 // The basic use-case is when user spots some mistake in function body 14 // from debugger and wishes to change the algorithm without restart. 15 // 16 // A single change always has a form of a simple replacement (in pseudo-code): 17 // script.source[positions, positions+length] = new_string; 18 // Implementation first determines, which function's body includes this 19 // change area. Then both old and new versions of script are fully compiled 20 // in order to analyze, whether the function changed its outer scope 21 // expectations (or number of parameters). If it didn't, function's code is 22 // patched with a newly compiled code. If it did change, enclosing function 23 // gets patched. All inner functions are left untouched, whatever happened 24 // to them in a new script version. However, new version of code will 25 // instantiate newly compiled functions. 26 27 28 #include "src/allocation.h" 29 #include "src/ast/ast-traversal-visitor.h" 30 31 namespace v8 { 32 namespace internal { 33 34 // This class collects some specific information on structure of functions 35 // in a particular script. 36 // 37 // The primary interest of the Tracker is to record function scope structures 38 // in order to analyze whether function code may be safely patched (with new 39 // code successfully reading existing data from function scopes). The Tracker 40 // also collects compiled function codes. 41 class LiveEditFunctionTracker 42 : public AstTraversalVisitor<LiveEditFunctionTracker> { 43 public: 44 // Traverses the entire AST, and records information about all 45 // FunctionLiterals for further use by LiveEdit code patching. The collected 46 // information is returned as a serialized array. 47 static Handle<JSArray> Collect(FunctionLiteral* node, Handle<Script> script, 48 Zone* zone, Isolate* isolate); 49 50 protected: 51 friend AstTraversalVisitor<LiveEditFunctionTracker>; 52 void VisitFunctionLiteral(FunctionLiteral* node); 53 54 private: 55 LiveEditFunctionTracker(Handle<Script> script, Zone* zone, Isolate* isolate); 56 57 void FunctionStarted(FunctionLiteral* fun); 58 void FunctionDone(Handle<SharedFunctionInfo> shared, Scope* scope); 59 Handle<Object> SerializeFunctionScope(Scope* scope); 60 61 Handle<Script> script_; 62 Zone* zone_; 63 Isolate* isolate_; 64 65 Handle<JSArray> result_; 66 int len_; 67 int current_parent_index_; 68 69 DISALLOW_COPY_AND_ASSIGN(LiveEditFunctionTracker); 70 }; 71 72 73 class LiveEdit : AllStatic { 74 public: 75 static void InitializeThreadLocal(Debug* debug); 76 77 static bool SetAfterBreakTarget(Debug* debug); 78 79 MUST_USE_RESULT static MaybeHandle<JSArray> GatherCompileInfo( 80 Handle<Script> script, 81 Handle<String> source); 82 83 static void ReplaceFunctionCode(Handle<JSArray> new_compile_info_array, 84 Handle<JSArray> shared_info_array); 85 86 static void FunctionSourceUpdated(Handle<JSArray> shared_info_array); 87 88 // Updates script field in FunctionSharedInfo. 89 static void SetFunctionScript(Handle<JSValue> function_wrapper, 90 Handle<Object> script_handle); 91 92 static void PatchFunctionPositions(Handle<JSArray> shared_info_array, 93 Handle<JSArray> position_change_array); 94 95 // For a script updates its source field. If old_script_name is provided 96 // (i.e. is a String), also creates a copy of the script with its original 97 // source and sends notification to debugger. 98 static Handle<Object> ChangeScriptSource(Handle<Script> original_script, 99 Handle<String> new_source, 100 Handle<Object> old_script_name); 101 102 // In a code of a parent function replaces original function as embedded 103 // object with a substitution one. 104 static void ReplaceRefToNestedFunction(Handle<JSValue> parent_function_shared, 105 Handle<JSValue> orig_function_shared, 106 Handle<JSValue> subst_function_shared); 107 108 // Find open generator activations, and set corresponding "result" elements to 109 // FUNCTION_BLOCKED_ACTIVE_GENERATOR. 110 static bool FindActiveGenerators(Handle<FixedArray> shared_info_array, 111 Handle<FixedArray> result, int len); 112 113 // Checks listed functions on stack and return array with corresponding 114 // FunctionPatchabilityStatus statuses; extra array element may 115 // contain general error message. Modifies the current stack and 116 // has restart the lowest found frames and drops all other frames above 117 // if possible and if do_drop is true. 118 static Handle<JSArray> CheckAndDropActivations( 119 Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array, 120 bool do_drop); 121 122 // Restarts the call frame and completely drops all frames above it. 123 // Return error message or NULL. 124 static const char* RestartFrame(JavaScriptFrame* frame); 125 126 // A copy of this is in liveedit.js. 127 enum FunctionPatchabilityStatus { 128 FUNCTION_AVAILABLE_FOR_PATCH = 1, 129 FUNCTION_BLOCKED_ON_ACTIVE_STACK = 2, 130 FUNCTION_BLOCKED_ON_OTHER_STACK = 3, 131 FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4, 132 FUNCTION_REPLACED_ON_ACTIVE_STACK = 5, 133 FUNCTION_BLOCKED_UNDER_GENERATOR = 6, 134 FUNCTION_BLOCKED_ACTIVE_GENERATOR = 7, 135 FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART = 8 136 }; 137 138 // Compares 2 strings line-by-line, then token-wise and returns diff in form 139 // of array of triplets (pos1, pos1_end, pos2_end) describing list 140 // of diff chunks. 141 static Handle<JSArray> CompareStrings(Handle<String> s1, 142 Handle<String> s2); 143 144 // Architecture-specific constant. 145 static const bool kFrameDropperSupported; 146 147 /** 148 * Defines layout of a stack frame that supports padding. This is a regular 149 * internal frame that has a flexible stack structure. LiveEdit can shift 150 * its lower part up the stack, taking up the 'padding' space when additional 151 * stack memory is required. 152 * Such frame is expected immediately above the topmost JavaScript frame. 153 * 154 * Stack Layout: 155 * --- Top 156 * LiveEdit routine frames 157 * --- 158 * C frames of debug handler 159 * --- 160 * ... 161 * --- 162 * An internal frame that has n padding words: 163 * - any number of words as needed by code -- upper part of frame 164 * - padding size: a Smi storing n -- current size of padding 165 * - padding: n words filled with kPaddingValue in form of Smi 166 * - 3 context/type words of a regular InternalFrame 167 * - fp 168 * --- 169 * Topmost JavaScript frame 170 * --- 171 * ... 172 * --- Bottom 173 */ 174 // A number of words that should be reserved on stack for the LiveEdit use. 175 // Stored on stack in form of Smi. 176 static const int kFramePaddingInitialSize = 1; 177 // A value that padding words are filled with (in form of Smi). Going 178 // bottom-top, the first word not having this value is a counter word. 179 static const int kFramePaddingValue = kFramePaddingInitialSize + 1; 180 }; 181 182 183 // A general-purpose comparator between 2 arrays. 184 class Comparator { 185 public: 186 // Holds 2 arrays of some elements allowing to compare any pair of 187 // element from the first array and element from the second array. 188 class Input { 189 public: 190 virtual int GetLength1() = 0; 191 virtual int GetLength2() = 0; 192 virtual bool Equals(int index1, int index2) = 0; 193 194 protected: ~Input()195 virtual ~Input() {} 196 }; 197 198 // Receives compare result as a series of chunks. 199 class Output { 200 public: 201 // Puts another chunk in result list. Note that technically speaking 202 // only 3 arguments actually needed with 4th being derivable. 203 virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0; 204 205 protected: ~Output()206 virtual ~Output() {} 207 }; 208 209 // Finds the difference between 2 arrays of elements. 210 static void CalculateDifference(Input* input, 211 Output* result_writer); 212 }; 213 214 215 216 // Simple helper class that creates more or less typed structures over 217 // JSArray object. This is an adhoc method of passing structures from C++ 218 // to JavaScript. 219 template<typename S> 220 class JSArrayBasedStruct { 221 public: Create(Isolate * isolate)222 static S Create(Isolate* isolate) { 223 Factory* factory = isolate->factory(); 224 Handle<JSArray> array = factory->NewJSArray(S::kSize_); 225 return S(array); 226 } 227 cast(Object * object)228 static S cast(Object* object) { 229 JSArray* array = JSArray::cast(object); 230 Handle<JSArray> array_handle(array); 231 return S(array_handle); 232 } 233 JSArrayBasedStruct(Handle<JSArray> array)234 explicit JSArrayBasedStruct(Handle<JSArray> array) : array_(array) { 235 } 236 GetJSArray()237 Handle<JSArray> GetJSArray() { 238 return array_; 239 } 240 isolate()241 Isolate* isolate() const { 242 return array_->GetIsolate(); 243 } 244 245 protected: SetField(int field_position,Handle<Object> value)246 void SetField(int field_position, Handle<Object> value) { 247 Object::SetElement(isolate(), array_, field_position, value, SLOPPY) 248 .Assert(); 249 } 250 SetSmiValueField(int field_position,int value)251 void SetSmiValueField(int field_position, int value) { 252 SetField(field_position, Handle<Smi>(Smi::FromInt(value), isolate())); 253 } 254 GetField(int field_position)255 Handle<Object> GetField(int field_position) { 256 return JSReceiver::GetElement(isolate(), array_, field_position) 257 .ToHandleChecked(); 258 } 259 GetSmiValueField(int field_position)260 int GetSmiValueField(int field_position) { 261 Handle<Object> res = GetField(field_position); 262 return Handle<Smi>::cast(res)->value(); 263 } 264 265 private: 266 Handle<JSArray> array_; 267 }; 268 269 270 // Represents some function compilation details. This structure will be used 271 // from JavaScript. It contains Code object, which is kept wrapped 272 // into a BlindReference for sanitizing reasons. 273 class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> { 274 public: FunctionInfoWrapper(Handle<JSArray> array)275 explicit FunctionInfoWrapper(Handle<JSArray> array) 276 : JSArrayBasedStruct<FunctionInfoWrapper>(array) { 277 } 278 279 void SetInitialProperties(Handle<String> name, int start_position, 280 int end_position, int param_num, int literal_count, 281 int parent_index); 282 SetFunctionScopeInfo(Handle<Object> scope_info_array)283 void SetFunctionScopeInfo(Handle<Object> scope_info_array) { 284 this->SetField(kFunctionScopeInfoOffset_, scope_info_array); 285 } 286 287 void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info); 288 289 Handle<SharedFunctionInfo> GetSharedFunctionInfo(); 290 GetLiteralCount()291 int GetLiteralCount() { 292 return this->GetSmiValueField(kLiteralNumOffset_); 293 } 294 GetParentIndex()295 int GetParentIndex() { 296 return this->GetSmiValueField(kParentIndexOffset_); 297 } 298 GetStartPosition()299 int GetStartPosition() { 300 return this->GetSmiValueField(kStartPositionOffset_); 301 } 302 GetEndPosition()303 int GetEndPosition() { return this->GetSmiValueField(kEndPositionOffset_); } 304 305 private: 306 static const int kFunctionNameOffset_ = 0; 307 static const int kStartPositionOffset_ = 1; 308 static const int kEndPositionOffset_ = 2; 309 static const int kParamNumOffset_ = 3; 310 static const int kFunctionScopeInfoOffset_ = 4; 311 static const int kParentIndexOffset_ = 5; 312 static const int kSharedFunctionInfoOffset_ = 6; 313 static const int kLiteralNumOffset_ = 7; 314 static const int kSize_ = 8; 315 316 friend class JSArrayBasedStruct<FunctionInfoWrapper>; 317 }; 318 319 320 // Wraps SharedFunctionInfo along with some of its fields for passing it 321 // back to JavaScript. SharedFunctionInfo object itself is additionally 322 // wrapped into BlindReference for sanitizing reasons. 323 class SharedInfoWrapper : public JSArrayBasedStruct<SharedInfoWrapper> { 324 public: IsInstance(Handle<JSArray> array)325 static bool IsInstance(Handle<JSArray> array) { 326 if (array->length() != Smi::FromInt(kSize_)) return false; 327 Handle<Object> element( 328 JSReceiver::GetElement(array->GetIsolate(), array, kSharedInfoOffset_) 329 .ToHandleChecked()); 330 if (!element->IsJSValue()) return false; 331 return Handle<JSValue>::cast(element)->value()->IsSharedFunctionInfo(); 332 } 333 SharedInfoWrapper(Handle<JSArray> array)334 explicit SharedInfoWrapper(Handle<JSArray> array) 335 : JSArrayBasedStruct<SharedInfoWrapper>(array) { 336 } 337 338 void SetProperties(Handle<String> name, 339 int start_position, 340 int end_position, 341 Handle<SharedFunctionInfo> info); 342 343 Handle<SharedFunctionInfo> GetInfo(); 344 345 private: 346 static const int kFunctionNameOffset_ = 0; 347 static const int kStartPositionOffset_ = 1; 348 static const int kEndPositionOffset_ = 2; 349 static const int kSharedInfoOffset_ = 3; 350 static const int kSize_ = 4; 351 352 friend class JSArrayBasedStruct<SharedInfoWrapper>; 353 }; 354 355 } // namespace internal 356 } // namespace v8 357 358 #endif /* V8_DEBUG_LIVEEDIT_H_ */ 359