DebugDocument.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. //-------------------------------------------------------------------------------------------------------
  2. // Copyright (C) Microsoft. All rights reserved.
  3. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
  4. //-------------------------------------------------------------------------------------------------------
  5. #include "RuntimeDebugPch.h"
  6. #ifdef ENABLE_SCRIPT_DEBUGGING
  7. namespace Js
  8. {
  9. DebugDocument::DebugDocument(Utf8SourceInfo* utf8SourceInfo, Js::FunctionBody* functionBody) :
  10. utf8SourceInfo(utf8SourceInfo),
  11. m_breakpointList(nullptr)
  12. {
  13. Assert(utf8SourceInfo != nullptr);
  14. if (functionBody != nullptr)
  15. {
  16. this->functionBody.Root(functionBody, this->utf8SourceInfo->GetScriptContext()->GetRecycler());
  17. }
  18. }
  19. DebugDocument::~DebugDocument()
  20. {
  21. Assert(this->utf8SourceInfo == nullptr);
  22. Assert(this->m_breakpointList == nullptr);
  23. }
  24. void DebugDocument::CloseDocument()
  25. {
  26. if (this->m_breakpointList != nullptr)
  27. {
  28. this->ClearAllBreakPoints();
  29. }
  30. Assert(this->utf8SourceInfo != nullptr);
  31. if (functionBody)
  32. {
  33. functionBody.Unroot(this->utf8SourceInfo->GetScriptContext()->GetRecycler());
  34. }
  35. this->utf8SourceInfo = nullptr;
  36. }
  37. BreakpointProbeList* DebugDocument::GetBreakpointList()
  38. {
  39. if (m_breakpointList != nullptr)
  40. {
  41. return m_breakpointList;
  42. }
  43. ScriptContext * scriptContext = this->utf8SourceInfo->GetScriptContext();
  44. if (scriptContext == nullptr || scriptContext->IsClosed())
  45. {
  46. return nullptr;
  47. }
  48. ArenaAllocator* diagnosticArena = scriptContext->AllocatorForDiagnostics();
  49. Assert(diagnosticArena);
  50. m_breakpointList = this->NewBreakpointList(diagnosticArena);
  51. return m_breakpointList;
  52. }
  53. BreakpointProbeList* DebugDocument::NewBreakpointList(ArenaAllocator* arena)
  54. {
  55. return BreakpointProbeList::New(arena);
  56. }
  57. HRESULT DebugDocument::SetBreakPoint(int32 ibos, BREAKPOINT_STATE breakpointState)
  58. {
  59. ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext();
  60. if (scriptContext == nullptr || scriptContext->IsClosed())
  61. {
  62. return E_UNEXPECTED;
  63. }
  64. StatementLocation statement;
  65. if (!this->GetStatementLocation(ibos, &statement))
  66. {
  67. return E_FAIL;
  68. }
  69. this->SetBreakPoint(statement, breakpointState);
  70. return S_OK;
  71. }
  72. BreakpointProbe* DebugDocument::SetBreakPoint(StatementLocation statement, BREAKPOINT_STATE bps)
  73. {
  74. ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext();
  75. if (scriptContext == nullptr || scriptContext->IsClosed())
  76. {
  77. return nullptr;
  78. }
  79. switch (bps)
  80. {
  81. default:
  82. AssertMsg(FALSE, "Bad breakpoint state");
  83. // Fall thru
  84. case BREAKPOINT_DISABLED:
  85. case BREAKPOINT_DELETED:
  86. {
  87. BreakpointProbeList* pBreakpointList = this->GetBreakpointList();
  88. if (pBreakpointList)
  89. {
  90. ArenaAllocator arena(_u("TemporaryBreakpointList"), scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticPageAllocator(), Throw::OutOfMemory);
  91. BreakpointProbeList* pDeleteList = this->NewBreakpointList(&arena);
  92. pBreakpointList->Map([&statement, scriptContext, pDeleteList](int index, BreakpointProbe * breakpointProbe)
  93. {
  94. if (breakpointProbe->Matches(statement.function, statement.statement.begin))
  95. {
  96. scriptContext->GetDebugContext()->GetProbeContainer()->RemoveProbe(breakpointProbe);
  97. pDeleteList->Add(breakpointProbe);
  98. }
  99. });
  100. pDeleteList->Map([pBreakpointList](int index, BreakpointProbe * breakpointProbe)
  101. {
  102. pBreakpointList->Remove(breakpointProbe);
  103. });
  104. pDeleteList->Clear();
  105. }
  106. break;
  107. }
  108. case BREAKPOINT_ENABLED:
  109. {
  110. BreakpointProbe* pProbe = Anew(scriptContext->AllocatorForDiagnostics(), BreakpointProbe, this, statement,
  111. scriptContext->GetThreadContext()->GetDebugManager()->GetNextBreakpointId());
  112. scriptContext->GetDebugContext()->GetProbeContainer()->AddProbe(pProbe);
  113. BreakpointProbeList* pBreakpointList = this->GetBreakpointList();
  114. pBreakpointList->Add(pProbe);
  115. return pProbe;
  116. break;
  117. }
  118. }
  119. return nullptr;
  120. }
  121. void DebugDocument::RemoveBreakpointProbe(BreakpointProbe *probe)
  122. {
  123. Assert(probe);
  124. if (m_breakpointList)
  125. {
  126. m_breakpointList->Remove(probe);
  127. }
  128. }
  129. void DebugDocument::ClearAllBreakPoints(void)
  130. {
  131. if (m_breakpointList != nullptr)
  132. {
  133. m_breakpointList->Clear();
  134. m_breakpointList = nullptr;
  135. }
  136. }
  137. #if ENABLE_TTD
  138. BreakpointProbe* DebugDocument::SetBreakPoint_TTDWbpId(int64 bpId, StatementLocation statement)
  139. {
  140. ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext();
  141. BreakpointProbe* pProbe = Anew(scriptContext->AllocatorForDiagnostics(), BreakpointProbe, this, statement, (uint32)bpId);
  142. scriptContext->GetDebugContext()->GetProbeContainer()->AddProbe(pProbe);
  143. BreakpointProbeList* pBreakpointList = this->GetBreakpointList();
  144. pBreakpointList->Add(pProbe);
  145. return pProbe;
  146. }
  147. #endif
  148. Js::BreakpointProbe* DebugDocument::FindBreakpoint(StatementLocation statement)
  149. {
  150. Js::BreakpointProbe* probe = nullptr;
  151. if (m_breakpointList != nullptr)
  152. {
  153. m_breakpointList->MapUntil([&](int index, BreakpointProbe* bpProbe) -> bool
  154. {
  155. if (bpProbe != nullptr && bpProbe->Matches(statement))
  156. {
  157. probe = bpProbe;
  158. return true;
  159. }
  160. return false;
  161. });
  162. }
  163. return probe;
  164. }
  165. bool DebugDocument::FindBPStatementLocation(UINT bpId, StatementLocation * statement)
  166. {
  167. bool foundStatement = false;
  168. if (m_breakpointList != nullptr)
  169. {
  170. m_breakpointList->MapUntil([&](int index, BreakpointProbe* bpProbe) -> bool
  171. {
  172. if (bpProbe != nullptr && bpProbe->GetId() == bpId)
  173. {
  174. bpProbe->GetStatementLocation(statement);
  175. foundStatement = true;
  176. return true;
  177. }
  178. return false;
  179. });
  180. }
  181. return foundStatement;
  182. }
  183. BOOL DebugDocument::GetStatementSpan(int32 ibos, StatementSpan* pStatement)
  184. {
  185. StatementLocation statement;
  186. if (GetStatementLocation(ibos, &statement))
  187. {
  188. pStatement->ich = statement.statement.begin;
  189. pStatement->cch = statement.statement.end - statement.statement.begin;
  190. return TRUE;
  191. }
  192. return FALSE;
  193. }
  194. FunctionBody * DebugDocument::GetFunctionBodyAt(int32 ibos)
  195. {
  196. StatementLocation location = {};
  197. if (GetStatementLocation(ibos, &location))
  198. {
  199. return location.function;
  200. }
  201. return nullptr;
  202. }
  203. BOOL DebugDocument::HasLineBreak(int32 _start, int32 _end)
  204. {
  205. return this->functionBody->HasLineBreak(_start, _end);
  206. }
  207. BOOL DebugDocument::GetStatementLocation(int32 ibos, StatementLocation* plocation)
  208. {
  209. if (ibos < 0)
  210. {
  211. return FALSE;
  212. }
  213. ScriptContext* scriptContext = this->utf8SourceInfo->GetScriptContext();
  214. if (scriptContext == nullptr || scriptContext->IsClosed())
  215. {
  216. return FALSE;
  217. }
  218. uint32 ubos = static_cast<uint32>(ibos);
  219. // Getting the appropriate statement on the asked position works on the heuristic which requires two
  220. // probable candidates. These candidates will be closest to the ibos where first.range.start < ibos and
  221. // second.range.start >= ibos. They will be fetched out by going into each FunctionBody.
  222. StatementLocation candidateMatch1 = {};
  223. StatementLocation candidateMatch2 = {};
  224. this->utf8SourceInfo->MapFunction([&](FunctionBody* pFuncBody)
  225. {
  226. uint32 functionStart = pFuncBody->StartInDocument();
  227. uint32 functionEnd = functionStart + pFuncBody->LengthInBytes();
  228. // For the first candidate, we should allow the current function to participate if its range
  229. // (instead of just start offset) is closer to the ubos compared to already found candidate1.
  230. if (candidateMatch1.function == nullptr ||
  231. ((candidateMatch1.statement.begin <= static_cast<int>(functionStart) ||
  232. candidateMatch1.statement.end <= static_cast<int>(functionEnd)) &&
  233. ubos > functionStart) ||
  234. candidateMatch2.function == nullptr ||
  235. (candidateMatch2.statement.begin > static_cast<int>(functionStart) &&
  236. ubos <= functionStart) ||
  237. (functionStart <= ubos &&
  238. ubos < functionEnd))
  239. {
  240. // We need to find out two possible candidate from the current FunctionBody.
  241. pFuncBody->FindClosestStatements(ibos, &candidateMatch1, &candidateMatch2);
  242. }
  243. });
  244. if (candidateMatch1.function == nullptr && candidateMatch2.function == nullptr)
  245. {
  246. return FALSE; // No Match found
  247. }
  248. if (candidateMatch1.function == nullptr || candidateMatch2.function == nullptr)
  249. {
  250. *plocation = (candidateMatch1.function == nullptr) ? candidateMatch2 : candidateMatch1;
  251. return TRUE;
  252. }
  253. // If one of the func is inner to another one, and ibos is in the inner one, disregard the outer one/let the inner one win.
  254. // See WinBlue 575634. Scenario is like this: var foo = function () {this;} -- and BP is set to 'this;' 'function'.
  255. if (candidateMatch1.function != candidateMatch2.function)
  256. {
  257. Assert(candidateMatch1.function && candidateMatch2.function);
  258. regex::Interval func1Range(candidateMatch1.function->StartInDocument());
  259. func1Range.End(func1Range.Begin() + candidateMatch1.function->LengthInBytes());
  260. regex::Interval func2Range(candidateMatch2.function->StartInDocument());
  261. func2Range.End(func2Range.Begin() + candidateMatch2.function->LengthInBytes());
  262. // If cursor (ibos) is just after the closing braces of the inner function then we can't
  263. // directly choose inner function and have to make line break check, so fallback
  264. // function foo(){function bar(){var y=1;}#var x=1;bar();}foo(); - ibos is #
  265. if (func1Range.Includes(func2Range) && func2Range.Includes(ibos) && func2Range.End() != ibos)
  266. {
  267. *plocation = candidateMatch2;
  268. return TRUE;
  269. }
  270. else if (func2Range.Includes(func1Range) && func1Range.Includes(ibos) && func1Range.End() != ibos)
  271. {
  272. *plocation = candidateMatch1;
  273. return TRUE;
  274. }
  275. }
  276. // At this point we have both candidate to consider.
  277. Assert(candidateMatch1.statement.begin < candidateMatch2.statement.begin);
  278. Assert(candidateMatch1.statement.begin < ibos);
  279. Assert(candidateMatch2.statement.begin >= ibos);
  280. // Default selection
  281. *plocation = candidateMatch1;
  282. // If the second candidate start at ibos or
  283. // if the first candidate has line break between ibos and the second candidate is on the same line as ibos
  284. // then consider the second one.
  285. BOOL fNextHasLineBreak = this->HasLineBreak(ibos, candidateMatch2.statement.begin);
  286. if ((candidateMatch2.statement.begin == ibos)
  287. || (this->HasLineBreak(candidateMatch1.statement.begin, ibos) && !fNextHasLineBreak))
  288. {
  289. *plocation = candidateMatch2;
  290. }
  291. // If ibos is out of the range of first candidate, choose second candidate if ibos is on the same line as second candidate
  292. // or ibos is not on the same line of the end of the first candidate.
  293. else if (candidateMatch1.statement.end < ibos && (!fNextHasLineBreak || this->HasLineBreak(candidateMatch1.statement.end, ibos)))
  294. {
  295. *plocation = candidateMatch2;
  296. }
  297. return TRUE;
  298. }
  299. }
  300. #endif