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 MUST_USE_RESULT static MaybeHandle<JSArray> GatherCompileInfo( 78 Handle<Script> script, 79 Handle<String> source); 80 81 static void ReplaceFunctionCode(Handle<JSArray> new_compile_info_array, 82 Handle<JSArray> shared_info_array); 83 84 static void FixupScript(Handle<Script> script, int max_function_literal_id); 85 86 static void FunctionSourceUpdated(Handle<JSArray> shared_info_array, 87 int new_function_literal_id); 88 89 // Updates script field in FunctionSharedInfo. 90 static void SetFunctionScript(Handle<JSValue> function_wrapper, 91 Handle<Object> script_handle); 92 93 static void PatchFunctionPositions(Handle<JSArray> shared_info_array, 94 Handle<JSArray> position_change_array); 95 96 // For a script updates its source field. If old_script_name is provided 97 // (i.e. is a String), also creates a copy of the script with its original 98 // source and sends notification to debugger. 99 static Handle<Object> ChangeScriptSource(Handle<Script> original_script, 100 Handle<String> new_source, 101 Handle<Object> old_script_name); 102 103 // In a code of a parent function replaces original function as embedded 104 // object with a substitution one. 105 static void ReplaceRefToNestedFunction(Handle<JSValue> parent_function_shared, 106 Handle<JSValue> orig_function_shared, 107 Handle<JSValue> subst_function_shared); 108 109 // Find open generator activations, and set corresponding "result" elements to 110 // FUNCTION_BLOCKED_ACTIVE_GENERATOR. 111 static bool FindActiveGenerators(Handle<FixedArray> shared_info_array, 112 Handle<FixedArray> result, int len); 113 114 // Checks listed functions on stack and return array with corresponding 115 // FunctionPatchabilityStatus statuses; extra array element may 116 // contain general error message. Modifies the current stack and 117 // has restart the lowest found frames and drops all other frames above 118 // if possible and if do_drop is true. 119 static Handle<JSArray> CheckAndDropActivations( 120 Handle<JSArray> old_shared_array, Handle<JSArray> new_shared_array, 121 bool do_drop); 122 123 // Restarts the call frame and completely drops all frames above it. 124 // Return error message or NULL. 125 static const char* RestartFrame(JavaScriptFrame* frame); 126 127 // A copy of this is in liveedit.js. 128 enum FunctionPatchabilityStatus { 129 FUNCTION_AVAILABLE_FOR_PATCH = 1, 130 FUNCTION_BLOCKED_ON_ACTIVE_STACK = 2, 131 FUNCTION_BLOCKED_ON_OTHER_STACK = 3, 132 FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4, 133 FUNCTION_REPLACED_ON_ACTIVE_STACK = 5, 134 FUNCTION_BLOCKED_UNDER_GENERATOR = 6, 135 FUNCTION_BLOCKED_ACTIVE_GENERATOR = 7, 136 FUNCTION_BLOCKED_NO_NEW_TARGET_ON_RESTART = 8 137 }; 138 139 // Compares 2 strings line-by-line, then token-wise and returns diff in form 140 // of array of triplets (pos1, pos1_end, pos2_end) describing list 141 // of diff chunks. 142 static Handle<JSArray> CompareStrings(Handle<String> s1, 143 Handle<String> s2); 144 145 // Architecture-specific constant. 146 static const bool kFrameDropperSupported; 147 }; 148 149 150 // A general-purpose comparator between 2 arrays. 151 class Comparator { 152 public: 153 // Holds 2 arrays of some elements allowing to compare any pair of 154 // element from the first array and element from the second array. 155 class Input { 156 public: 157 virtual int GetLength1() = 0; 158 virtual int GetLength2() = 0; 159 virtual bool Equals(int index1, int index2) = 0; 160 161 protected: ~Input()162 virtual ~Input() {} 163 }; 164 165 // Receives compare result as a series of chunks. 166 class Output { 167 public: 168 // Puts another chunk in result list. Note that technically speaking 169 // only 3 arguments actually needed with 4th being derivable. 170 virtual void AddChunk(int pos1, int pos2, int len1, int len2) = 0; 171 172 protected: ~Output()173 virtual ~Output() {} 174 }; 175 176 // Finds the difference between 2 arrays of elements. 177 static void CalculateDifference(Input* input, 178 Output* result_writer); 179 }; 180 181 182 183 // Simple helper class that creates more or less typed structures over 184 // JSArray object. This is an adhoc method of passing structures from C++ 185 // to JavaScript. 186 template<typename S> 187 class JSArrayBasedStruct { 188 public: Create(Isolate * isolate)189 static S Create(Isolate* isolate) { 190 Factory* factory = isolate->factory(); 191 Handle<JSArray> array = factory->NewJSArray(S::kSize_); 192 return S(array); 193 } 194 cast(Object * object)195 static S cast(Object* object) { 196 JSArray* array = JSArray::cast(object); 197 Handle<JSArray> array_handle(array); 198 return S(array_handle); 199 } 200 JSArrayBasedStruct(Handle<JSArray> array)201 explicit JSArrayBasedStruct(Handle<JSArray> array) : array_(array) { 202 } 203 GetJSArray()204 Handle<JSArray> GetJSArray() { 205 return array_; 206 } 207 isolate()208 Isolate* isolate() const { 209 return array_->GetIsolate(); 210 } 211 212 protected: SetField(int field_position,Handle<Object> value)213 void SetField(int field_position, Handle<Object> value) { 214 Object::SetElement(isolate(), array_, field_position, value, SLOPPY) 215 .Assert(); 216 } 217 SetSmiValueField(int field_position,int value)218 void SetSmiValueField(int field_position, int value) { 219 SetField(field_position, Handle<Smi>(Smi::FromInt(value), isolate())); 220 } 221 GetField(int field_position)222 Handle<Object> GetField(int field_position) { 223 return JSReceiver::GetElement(isolate(), array_, field_position) 224 .ToHandleChecked(); 225 } 226 GetSmiValueField(int field_position)227 int GetSmiValueField(int field_position) { 228 Handle<Object> res = GetField(field_position); 229 return Handle<Smi>::cast(res)->value(); 230 } 231 232 private: 233 Handle<JSArray> array_; 234 }; 235 236 237 // Represents some function compilation details. This structure will be used 238 // from JavaScript. It contains Code object, which is kept wrapped 239 // into a BlindReference for sanitizing reasons. 240 class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> { 241 public: FunctionInfoWrapper(Handle<JSArray> array)242 explicit FunctionInfoWrapper(Handle<JSArray> array) 243 : JSArrayBasedStruct<FunctionInfoWrapper>(array) { 244 } 245 246 void SetInitialProperties(Handle<String> name, int start_position, 247 int end_position, int param_num, int parent_index, 248 int function_literal_id); 249 SetFunctionScopeInfo(Handle<Object> scope_info_array)250 void SetFunctionScopeInfo(Handle<Object> scope_info_array) { 251 this->SetField(kFunctionScopeInfoOffset_, scope_info_array); 252 } 253 254 void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info); 255 256 Handle<SharedFunctionInfo> GetSharedFunctionInfo(); 257 GetParentIndex()258 int GetParentIndex() { 259 return this->GetSmiValueField(kParentIndexOffset_); 260 } 261 GetStartPosition()262 int GetStartPosition() { 263 return this->GetSmiValueField(kStartPositionOffset_); 264 } 265 GetEndPosition()266 int GetEndPosition() { return this->GetSmiValueField(kEndPositionOffset_); } 267 268 private: 269 static const int kFunctionNameOffset_ = 0; 270 static const int kStartPositionOffset_ = 1; 271 static const int kEndPositionOffset_ = 2; 272 static const int kParamNumOffset_ = 3; 273 static const int kFunctionScopeInfoOffset_ = 4; 274 static const int kParentIndexOffset_ = 5; 275 static const int kSharedFunctionInfoOffset_ = 6; 276 static const int kFunctionLiteralIdOffset_ = 7; 277 static const int kSize_ = 8; 278 279 friend class JSArrayBasedStruct<FunctionInfoWrapper>; 280 }; 281 282 283 // Wraps SharedFunctionInfo along with some of its fields for passing it 284 // back to JavaScript. SharedFunctionInfo object itself is additionally 285 // wrapped into BlindReference for sanitizing reasons. 286 class SharedInfoWrapper : public JSArrayBasedStruct<SharedInfoWrapper> { 287 public: IsInstance(Handle<JSArray> array)288 static bool IsInstance(Handle<JSArray> array) { 289 if (array->length() != Smi::FromInt(kSize_)) return false; 290 Handle<Object> element( 291 JSReceiver::GetElement(array->GetIsolate(), array, kSharedInfoOffset_) 292 .ToHandleChecked()); 293 if (!element->IsJSValue()) return false; 294 return Handle<JSValue>::cast(element)->value()->IsSharedFunctionInfo(); 295 } 296 SharedInfoWrapper(Handle<JSArray> array)297 explicit SharedInfoWrapper(Handle<JSArray> array) 298 : JSArrayBasedStruct<SharedInfoWrapper>(array) { 299 } 300 301 void SetProperties(Handle<String> name, 302 int start_position, 303 int end_position, 304 Handle<SharedFunctionInfo> info); 305 306 Handle<SharedFunctionInfo> GetInfo(); 307 308 private: 309 static const int kFunctionNameOffset_ = 0; 310 static const int kStartPositionOffset_ = 1; 311 static const int kEndPositionOffset_ = 2; 312 static const int kSharedInfoOffset_ = 3; 313 static const int kSize_ = 4; 314 315 friend class JSArrayBasedStruct<SharedInfoWrapper>; 316 }; 317 318 } // namespace internal 319 } // namespace v8 320 321 #endif /* V8_DEBUG_LIVEEDIT_H_ */ 322