You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

575 lines
21 KiB

  1. // Copyright (c) 2017 Daniel Grunwald
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy of this
  4. // software and associated documentation files (the "Software"), to deal in the Software
  5. // without restriction, including without limitation the rights to use, copy, modify, merge,
  6. // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
  7. // to whom the Software is furnished to do so, subject to the following conditions:
  8. //
  9. // The above copyright notice and this permission notice shall be included in all copies or
  10. // substantial portions of the Software.
  11. //
  12. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  13. // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  14. // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
  15. // FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  16. // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  17. // DEALINGS IN THE SOFTWARE.
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Collections.Immutable;
  21. using System.Diagnostics;
  22. using System.Linq;
  23. using ICSharpCode.Decompiler.CSharp.Syntax;
  24. using ICSharpCode.Decompiler.DebugInfo;
  25. using ICSharpCode.Decompiler.IL;
  26. using ICSharpCode.Decompiler.Util;
  27. namespace ICSharpCode.Decompiler.CSharp
  28. {
  29. /// <summary>
  30. /// Given a SyntaxTree that was output from the decompiler, constructs the list of sequence points.
  31. /// </summary>
  32. // Each statement / expression AST node is annotated with the ILInstruction(s) it was constructed from.
  33. // Each ILInstruction has a list of IL offsets corresponding to the original IL range(s). Note that the ILAst
  34. // instructions form a tree.
  35. //
  36. // This visitor constructs a list of sequence points from the syntax tree by visiting each node,
  37. // calling
  38. // 1. StartSequencePoint(AstNode)
  39. // 2. AddToSequencePoint(AstNode) (possibly multiple times)
  40. // 3. EndSequencePoint(TextLocation, TextLocation)
  41. // on each node.
  42. //
  43. // The VisitAsSequencePoint(AstNode) method encapsulates the steps above.
  44. //
  45. // The state we record for each sequence point is decribed in StatePerSequencePoint:
  46. // 1. primary AST node
  47. // 2. IL range intervals
  48. // 3. parent ILFunction (either a method or lambda)
  49. //
  50. // For each statement (at least) one sequence point is created and all expressions and their IL ranges
  51. // are added to it. Currently the debugger seems not to support breakpoints at an expression level, so
  52. // we stop at the statement level and add all sub-expressions to the same sequence point.
  53. //
  54. // LambdaExpression is one exception: we create new sequence points for the expression/statements of the lambda,
  55. // note however, that these are added to a different ILFunction.
  56. //
  57. // AddToSequencePoint(AstNode) handles the list of ILInstructions and visits each ILInstruction and its descendants.
  58. // We do not descend into nested ILFunctions as these create their own list of sequence points.
  59. class SequencePointBuilder : DepthFirstAstVisitor
  60. {
  61. struct StatePerSequencePoint
  62. {
  63. /// <summary>
  64. /// Main AST node associated with this sequence point.
  65. /// </summary>
  66. internal readonly AstNode PrimaryNode;
  67. /// <summary>
  68. /// List of IL intervals that are associated with this sequence point.
  69. /// </summary>
  70. internal readonly List<Interval> Intervals;
  71. /// <summary>
  72. /// The function containing this sequence point.
  73. /// </summary>
  74. internal ILFunction Function;
  75. public StatePerSequencePoint(AstNode primaryNode)
  76. {
  77. this.PrimaryNode = primaryNode;
  78. this.Intervals = new List<Interval>();
  79. this.Function = null;
  80. }
  81. }
  82. readonly List<(ILFunction, DebugInfo.SequencePoint)> sequencePoints = new List<(ILFunction, DebugInfo.SequencePoint)>();
  83. readonly HashSet<ILInstruction> mappedInstructions = new HashSet<ILInstruction>();
  84. // Stack holding information for outer statements.
  85. readonly Stack<StatePerSequencePoint> outerStates = new Stack<StatePerSequencePoint>();
  86. // Collects information for the current sequence point.
  87. StatePerSequencePoint current;
  88. void VisitAsSequencePoint(AstNode node)
  89. {
  90. if (node.IsNull)
  91. return;
  92. StartSequencePoint(node);
  93. node.AcceptVisitor(this);
  94. EndSequencePoint(node.StartLocation, node.EndLocation);
  95. }
  96. protected override void VisitChildren(AstNode node)
  97. {
  98. base.VisitChildren(node);
  99. AddToSequencePoint(node);
  100. }
  101. public override void VisitBlockStatement(BlockStatement blockStatement)
  102. {
  103. // enhanced using variables need special-casing here, because we omit the block syntax from the
  104. // text output, so we cannot use positions of opening/closing braces here.
  105. bool isEnhancedUsing = blockStatement.Parent is UsingStatement us && us.IsEnhanced;
  106. if (!isEnhancedUsing)
  107. {
  108. var blockContainer = blockStatement.Annotation<BlockContainer>();
  109. if (blockContainer != null)
  110. {
  111. StartSequencePoint(blockStatement.LBraceToken);
  112. int intervalStart;
  113. if (blockContainer.Parent is TryCatchHandler handler && !handler.ExceptionSpecifierILRange.IsEmpty)
  114. {
  115. // if this block container is part of a TryCatchHandler, do not steal the
  116. // exception-specifier IL range
  117. intervalStart = handler.ExceptionSpecifierILRange.End;
  118. }
  119. else
  120. {
  121. intervalStart = blockContainer.StartILOffset;
  122. }
  123. // The end will be set to the first sequence point candidate location before the first
  124. // statement of the function when the seqeunce point is adjusted
  125. int intervalEnd = intervalStart + 1;
  126. Interval interval = new Interval(intervalStart, intervalEnd);
  127. List<Interval> intervals = new List<Interval>();
  128. intervals.Add(interval);
  129. current.Intervals.AddRange(intervals);
  130. current.Function = blockContainer.Ancestors.OfType<ILFunction>().FirstOrDefault();
  131. EndSequencePoint(blockStatement.LBraceToken.StartLocation, blockStatement.LBraceToken.EndLocation);
  132. }
  133. else
  134. {
  135. // Ideally, we'd be able to address this case. Blocks that are not the top-level function
  136. // block have no ILInstruction annotations. It isn't clear to me how to determine the il range.
  137. // For now, do not add the opening brace sequence in this case.
  138. }
  139. }
  140. foreach (var stmt in blockStatement.Statements)
  141. {
  142. VisitAsSequencePoint(stmt);
  143. }
  144. var implicitReturn = blockStatement.Annotation<ImplicitReturnAnnotation>();
  145. if (implicitReturn != null && !isEnhancedUsing)
  146. {
  147. StartSequencePoint(blockStatement.RBraceToken);
  148. AddToSequencePoint(implicitReturn.Leave);
  149. EndSequencePoint(blockStatement.RBraceToken.StartLocation, blockStatement.RBraceToken.EndLocation);
  150. }
  151. }
  152. public override void VisitForStatement(ForStatement forStatement)
  153. {
  154. // Every element of a for-statement is its own sequence point.
  155. foreach (var init in forStatement.Initializers)
  156. {
  157. VisitAsSequencePoint(init);
  158. }
  159. VisitAsSequencePoint(forStatement.Condition);
  160. foreach (var inc in forStatement.Iterators)
  161. {
  162. VisitAsSequencePoint(inc);
  163. }
  164. VisitAsSequencePoint(forStatement.EmbeddedStatement);
  165. }
  166. public override void VisitSwitchStatement(SwitchStatement switchStatement)
  167. {
  168. StartSequencePoint(switchStatement);
  169. switchStatement.Expression.AcceptVisitor(this);
  170. foreach (var section in switchStatement.SwitchSections)
  171. {
  172. // note: sections will not contribute to the current sequence point
  173. section.AcceptVisitor(this);
  174. }
  175. // add switch statement itself to sequence point
  176. // (call only after the sections are visited)
  177. AddToSequencePoint(switchStatement);
  178. EndSequencePoint(switchStatement.StartLocation, switchStatement.RParToken.EndLocation);
  179. }
  180. public override void VisitSwitchSection(Syntax.SwitchSection switchSection)
  181. {
  182. // every statement in the switch section is its own sequence point
  183. foreach (var stmt in switchSection.Statements)
  184. {
  185. VisitAsSequencePoint(stmt);
  186. }
  187. }
  188. public override void VisitLambdaExpression(LambdaExpression lambdaExpression)
  189. {
  190. AddToSequencePoint(lambdaExpression);
  191. VisitAsSequencePoint(lambdaExpression.Body);
  192. }
  193. public override void VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause)
  194. {
  195. AddToSequencePoint(queryContinuationClause);
  196. VisitAsSequencePoint(queryContinuationClause.PrecedingQuery);
  197. }
  198. public override void VisitQueryFromClause(QueryFromClause queryFromClause)
  199. {
  200. if (queryFromClause.Parent.FirstChild != queryFromClause)
  201. {
  202. AddToSequencePoint(queryFromClause);
  203. VisitAsSequencePoint(queryFromClause.Expression);
  204. }
  205. else
  206. {
  207. base.VisitQueryFromClause(queryFromClause);
  208. }
  209. }
  210. public override void VisitQueryGroupClause(QueryGroupClause queryGroupClause)
  211. {
  212. AddToSequencePoint(queryGroupClause);
  213. VisitAsSequencePoint(queryGroupClause.Projection);
  214. VisitAsSequencePoint(queryGroupClause.Key);
  215. }
  216. public override void VisitQueryJoinClause(QueryJoinClause queryJoinClause)
  217. {
  218. AddToSequencePoint(queryJoinClause);
  219. VisitAsSequencePoint(queryJoinClause.OnExpression);
  220. VisitAsSequencePoint(queryJoinClause.EqualsExpression);
  221. }
  222. public override void VisitQueryLetClause(QueryLetClause queryLetClause)
  223. {
  224. AddToSequencePoint(queryLetClause);
  225. VisitAsSequencePoint(queryLetClause.Expression);
  226. }
  227. public override void VisitQueryOrdering(QueryOrdering queryOrdering)
  228. {
  229. AddToSequencePoint(queryOrdering);
  230. VisitAsSequencePoint(queryOrdering.Expression);
  231. }
  232. public override void VisitQuerySelectClause(QuerySelectClause querySelectClause)
  233. {
  234. AddToSequencePoint(querySelectClause);
  235. VisitAsSequencePoint(querySelectClause.Expression);
  236. }
  237. public override void VisitQueryWhereClause(QueryWhereClause queryWhereClause)
  238. {
  239. AddToSequencePoint(queryWhereClause);
  240. VisitAsSequencePoint(queryWhereClause.Condition);
  241. }
  242. public override void VisitUsingStatement(UsingStatement usingStatement)
  243. {
  244. StartSequencePoint(usingStatement);
  245. usingStatement.ResourceAcquisition.AcceptVisitor(this);
  246. VisitAsSequencePoint(usingStatement.EmbeddedStatement);
  247. AddToSequencePoint(usingStatement);
  248. if (usingStatement.IsEnhanced)
  249. EndSequencePoint(usingStatement.StartLocation, usingStatement.ResourceAcquisition.EndLocation);
  250. else
  251. EndSequencePoint(usingStatement.StartLocation, usingStatement.RParToken.EndLocation);
  252. }
  253. public override void VisitForeachStatement(ForeachStatement foreachStatement)
  254. {
  255. var foreachInfo = foreachStatement.Annotation<ForeachAnnotation>();
  256. if (foreachInfo == null)
  257. {
  258. base.VisitForeachStatement(foreachStatement);
  259. return;
  260. }
  261. // TODO : Add a sequence point on foreach token (mapped to nop before using instruction).
  262. StartSequencePoint(foreachStatement);
  263. foreachStatement.InExpression.AcceptVisitor(this);
  264. AddToSequencePoint(foreachInfo.GetEnumeratorCall);
  265. EndSequencePoint(foreachStatement.InExpression.StartLocation, foreachStatement.InExpression.EndLocation);
  266. StartSequencePoint(foreachStatement);
  267. AddToSequencePoint(foreachInfo.MoveNextCall);
  268. EndSequencePoint(foreachStatement.InToken.StartLocation, foreachStatement.InToken.EndLocation);
  269. StartSequencePoint(foreachStatement);
  270. AddToSequencePoint(foreachInfo.GetCurrentCall);
  271. EndSequencePoint(foreachStatement.VariableType.StartLocation, foreachStatement.VariableDesignation.EndLocation);
  272. VisitAsSequencePoint(foreachStatement.EmbeddedStatement);
  273. }
  274. public override void VisitLockStatement(LockStatement lockStatement)
  275. {
  276. StartSequencePoint(lockStatement);
  277. lockStatement.Expression.AcceptVisitor(this);
  278. VisitAsSequencePoint(lockStatement.EmbeddedStatement);
  279. AddToSequencePoint(lockStatement);
  280. EndSequencePoint(lockStatement.StartLocation, lockStatement.RParToken.EndLocation);
  281. }
  282. public override void VisitIfElseStatement(IfElseStatement ifElseStatement)
  283. {
  284. StartSequencePoint(ifElseStatement);
  285. ifElseStatement.Condition.AcceptVisitor(this);
  286. VisitAsSequencePoint(ifElseStatement.TrueStatement);
  287. VisitAsSequencePoint(ifElseStatement.FalseStatement);
  288. AddToSequencePoint(ifElseStatement);
  289. EndSequencePoint(ifElseStatement.StartLocation, ifElseStatement.RParToken.EndLocation);
  290. }
  291. public override void VisitWhileStatement(WhileStatement whileStatement)
  292. {
  293. StartSequencePoint(whileStatement);
  294. whileStatement.Condition.AcceptVisitor(this);
  295. VisitAsSequencePoint(whileStatement.EmbeddedStatement);
  296. AddToSequencePoint(whileStatement);
  297. EndSequencePoint(whileStatement.StartLocation, whileStatement.RParToken.EndLocation);
  298. }
  299. public override void VisitDoWhileStatement(DoWhileStatement doWhileStatement)
  300. {
  301. StartSequencePoint(doWhileStatement);
  302. VisitAsSequencePoint(doWhileStatement.EmbeddedStatement);
  303. doWhileStatement.Condition.AcceptVisitor(this);
  304. AddToSequencePoint(doWhileStatement);
  305. EndSequencePoint(doWhileStatement.WhileToken.StartLocation, doWhileStatement.RParToken.EndLocation);
  306. }
  307. public override void VisitFixedStatement(FixedStatement fixedStatement)
  308. {
  309. foreach (var v in fixedStatement.Variables)
  310. {
  311. VisitAsSequencePoint(v);
  312. }
  313. VisitAsSequencePoint(fixedStatement.EmbeddedStatement);
  314. }
  315. public override void VisitTryCatchStatement(TryCatchStatement tryCatchStatement)
  316. {
  317. VisitAsSequencePoint(tryCatchStatement.TryBlock);
  318. foreach (var c in tryCatchStatement.CatchClauses)
  319. {
  320. VisitAsSequencePoint(c);
  321. }
  322. VisitAsSequencePoint(tryCatchStatement.FinallyBlock);
  323. }
  324. public override void VisitCatchClause(CatchClause catchClause)
  325. {
  326. if (catchClause.Condition.IsNull)
  327. {
  328. var tryCatchHandler = catchClause.Annotation<TryCatchHandler>();
  329. if (tryCatchHandler != null && !tryCatchHandler.ExceptionSpecifierILRange.IsEmpty)
  330. {
  331. StartSequencePoint(catchClause.CatchToken);
  332. var function = tryCatchHandler.Ancestors.OfType<ILFunction>().FirstOrDefault();
  333. AddToSequencePointRaw(function, new[] { tryCatchHandler.ExceptionSpecifierILRange });
  334. EndSequencePoint(catchClause.CatchToken.StartLocation, catchClause.RParToken.IsNull ? catchClause.CatchToken.EndLocation : catchClause.RParToken.EndLocation);
  335. }
  336. }
  337. else
  338. {
  339. StartSequencePoint(catchClause.WhenToken);
  340. AddToSequencePoint(catchClause.Condition);
  341. EndSequencePoint(catchClause.WhenToken.StartLocation, catchClause.CondRParToken.EndLocation);
  342. }
  343. VisitAsSequencePoint(catchClause.Body);
  344. }
  345. /// <summary>
  346. /// Start a new C# statement = new sequence point.
  347. /// </summary>
  348. void StartSequencePoint(AstNode primaryNode)
  349. {
  350. outerStates.Push(current);
  351. current = new StatePerSequencePoint(primaryNode);
  352. }
  353. void EndSequencePoint(TextLocation startLocation, TextLocation endLocation)
  354. {
  355. Debug.Assert(!startLocation.IsEmpty, "missing startLocation");
  356. Debug.Assert(!endLocation.IsEmpty, "missing endLocation");
  357. if (current.Intervals.Count > 0 && current.Function != null)
  358. {
  359. // use LongSet to deduplicate and merge the intervals
  360. var longSet = new LongSet(current.Intervals.Select(i => new LongInterval(i.Start, i.End)));
  361. Debug.Assert(!longSet.IsEmpty);
  362. sequencePoints.Add((current.Function, new DebugInfo.SequencePoint {
  363. Offset = (int)longSet.Intervals[0].Start,
  364. EndOffset = (int)longSet.Intervals[0].End,
  365. StartLine = startLocation.Line,
  366. StartColumn = startLocation.Column,
  367. EndLine = endLocation.Line,
  368. EndColumn = endLocation.Column
  369. }));
  370. }
  371. current = outerStates.Pop();
  372. }
  373. void AddToSequencePointRaw(ILFunction function, IEnumerable<Interval> ranges)
  374. {
  375. current.Intervals.AddRange(ranges);
  376. Debug.Assert(current.Function == null || current.Function == function);
  377. current.Function = function;
  378. }
  379. /// <summary>
  380. /// Add the ILAst instruction associated with the AstNode to the sequence point.
  381. /// Also add all its ILAst sub-instructions (unless they were already added to another sequence point).
  382. /// </summary>
  383. void AddToSequencePoint(AstNode node)
  384. {
  385. foreach (var inst in node.Annotations.OfType<ILInstruction>())
  386. {
  387. AddToSequencePoint(inst);
  388. }
  389. }
  390. void AddToSequencePoint(ILInstruction inst)
  391. {
  392. if (!mappedInstructions.Add(inst))
  393. {
  394. // inst was already used by a nested sequence point within this sequence point
  395. return;
  396. }
  397. // Add the IL range associated with this instruction to the current sequence point.
  398. if (HasUsableILRange(inst) && current.Intervals != null)
  399. {
  400. current.Intervals.AddRange(inst.ILRanges);
  401. var function = inst.Parent.Ancestors.OfType<ILFunction>().FirstOrDefault();
  402. Debug.Assert(current.Function == null || current.Function == function);
  403. current.Function = function;
  404. }
  405. // Do not add instructions of lambdas/delegates.
  406. if (inst is ILFunction)
  407. return;
  408. // Also add the child IL instructions, unless they were already processed by
  409. // another C# expression.
  410. foreach (var child in inst.Children)
  411. {
  412. AddToSequencePoint(child);
  413. }
  414. }
  415. internal static bool HasUsableILRange(ILInstruction inst)
  416. {
  417. if (inst.ILRangeIsEmpty)
  418. return false;
  419. if (inst.Parent == null)
  420. return false;
  421. return !(inst is BlockContainer || inst is Block);
  422. }
  423. /// <summary>
  424. /// Called after the visitor is done to return the results.
  425. /// </summary>
  426. internal Dictionary<ILFunction, List<DebugInfo.SequencePoint>> GetSequencePoints()
  427. {
  428. var dict = new Dictionary<ILFunction, List<DebugInfo.SequencePoint>>();
  429. foreach (var (function, sequencePoint) in this.sequencePoints)
  430. {
  431. if (!dict.TryGetValue(function, out var list))
  432. {
  433. dict.Add(function, list = new List<DebugInfo.SequencePoint>());
  434. }
  435. list.Add(sequencePoint);
  436. }
  437. foreach (var (function, list) in dict.ToList())
  438. {
  439. // For each function, sort sequence points and fix overlaps
  440. var newList = new List<DebugInfo.SequencePoint>();
  441. int pos = 0;
  442. IOrderedEnumerable<DebugInfo.SequencePoint> currFunctionSequencePoints = list.OrderBy(sp => sp.Offset).ThenBy(sp => sp.EndOffset);
  443. foreach (DebugInfo.SequencePoint sequencePoint in currFunctionSequencePoints)
  444. {
  445. if (sequencePoint.Offset < pos)
  446. {
  447. // overlapping sequence point?
  448. // delete previous sequence points that are after sequencePoint.Offset
  449. while (newList.Count > 0 && newList.Last().EndOffset > sequencePoint.Offset)
  450. {
  451. var last = newList.Last();
  452. if (last.Offset >= sequencePoint.Offset)
  453. {
  454. newList.RemoveAt(newList.Count - 1);
  455. }
  456. else
  457. {
  458. last.EndOffset = sequencePoint.Offset;
  459. newList[newList.Count - 1] = last;
  460. }
  461. }
  462. }
  463. newList.Add(sequencePoint);
  464. pos = sequencePoint.EndOffset;
  465. }
  466. // Add a hidden sequence point to account for the epilog of the function
  467. if (pos < function.CodeSize)
  468. {
  469. var hidden = new DebugInfo.SequencePoint();
  470. hidden.Offset = pos;
  471. hidden.EndOffset = function.CodeSize;
  472. hidden.SetHidden();
  473. newList.Add(hidden);
  474. }
  475. List<int> sequencePointCandidates = function.SequencePointCandidates;
  476. int currSPCandidateIndex = 0;
  477. for (int i = 0; i < newList.Count - 1; i++)
  478. {
  479. DebugInfo.SequencePoint currSequencePoint = newList[i];
  480. DebugInfo.SequencePoint nextSequencePoint = newList[i + 1];
  481. // Adjust the end offset of the current sequence point to the closest sequence point candidate
  482. // but do not create an overlapping sequence point. Moving the start of the current sequence
  483. // point is not required as it is 0 for the first sequence point and is moved during the last
  484. // iteration for all others.
  485. while (currSPCandidateIndex < sequencePointCandidates.Count &&
  486. sequencePointCandidates[currSPCandidateIndex] < currSequencePoint.EndOffset)
  487. {
  488. currSPCandidateIndex++;
  489. }
  490. if (currSPCandidateIndex < sequencePointCandidates.Count && sequencePointCandidates[currSPCandidateIndex] <= nextSequencePoint.Offset)
  491. {
  492. currSequencePoint.EndOffset = sequencePointCandidates[currSPCandidateIndex];
  493. }
  494. // Adjust the start offset of the next sequence point to the closest previous sequence point candidate
  495. // but do not create an overlapping sequence point.
  496. while (currSPCandidateIndex < sequencePointCandidates.Count &&
  497. sequencePointCandidates[currSPCandidateIndex] < nextSequencePoint.Offset)
  498. {
  499. currSPCandidateIndex++;
  500. }
  501. if (currSPCandidateIndex < sequencePointCandidates.Count && sequencePointCandidates[currSPCandidateIndex - 1] >= currSequencePoint.EndOffset)
  502. {
  503. nextSequencePoint.Offset = sequencePointCandidates[currSPCandidateIndex - 1];
  504. currSPCandidateIndex--;
  505. }
  506. // Fill in any gaps with a hidden sequence point
  507. if (currSequencePoint.EndOffset != nextSequencePoint.Offset)
  508. {
  509. SequencePoint newSP = new SequencePoint() { Offset = currSequencePoint.EndOffset, EndOffset = nextSequencePoint.Offset };
  510. newSP.SetHidden();
  511. newList.Insert(++i, newSP);
  512. }
  513. }
  514. dict[function] = newList;
  515. }
  516. return dict;
  517. }
  518. }
  519. }