Coverage Report

Created: 2025-08-28 06:48

/src/hermes/lib/BCGen/Exceptions.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#define DEBUG_TYPE "exceptions"
9
10
#include "hermes/BCGen/Exceptions.h"
11
#include "hermes/IR/CFG.h"
12
#include "hermes/IR/IR.h"
13
#include "hermes/IR/IRBuilder.h"
14
15
#include "llvh/Support/raw_ostream.h"
16
17
using namespace hermes;
18
19
/// Construct the list of basic blocks covered by each catch instruction.
20
/// Use recursion to handle nested catches.
21
llvh::Optional<llvh::SmallPtrSet<BasicBlock *, 4>> hermes::constructCatchMap(
22
    Function *F,
23
    CatchInfoMap &catchInfoMap,
24
    llvh::SmallVectorImpl<CatchInst *> &aliveCatches,
25
    llvh::SmallPtrSetImpl<BasicBlock *> &visited,
26
    BasicBlock *startBlock,
27
7.36k
    uint32_t maxRecursionDepth) {
28
7.36k
  assert(
29
7.36k
      !llvh::isa<CatchInst>(startBlock->front()) &&
30
7.36k
      "Functions and try bodies should never start with a catch instruction.");
31
7.36k
  if (maxRecursionDepth == 0) {
32
0
    F->getContext().getSourceErrorManager().error(
33
0
        F->getSourceRange(), "Too deeply nested try/catch");
34
0
    return llvh::None;
35
0
  }
36
37
  // Stack to DFS through the CFG.
38
7.36k
  llvh::SmallVector<BasicBlock *, 4> stack;
39
  // Track each of the basic blocks that start with a TryEndInst corresponding
40
  // to the current try.
41
7.36k
  llvh::SmallPtrSet<BasicBlock *, 4> tryEndBlocks;
42
43
7.36k
  visited.insert(startBlock);
44
7.36k
  stack.push_back(startBlock);
45
181k
  while (!stack.empty()) {
46
174k
    BasicBlock *currentBlock = stack.pop_back_val();
47
174k
    assert(
48
174k
        visited.count(currentBlock) &&
49
174k
        "All blocks on the stack must be marked as visited.");
50
51
    // For every nested try that's alive, we add the basic block into it.
52
174k
    for (const auto &aliveCatch : aliveCatches)
53
1.86M
      catchInfoMap[aliveCatch].coveredBlockList.push_back(currentBlock);
54
55
174k
    auto *tryStartInst =
56
174k
        llvh::dyn_cast<TryStartInst>(currentBlock->getTerminator());
57
58
174k
    if (!tryStartInst) {
59
      // Common case: no TryStartInst, we add successors to the stack.
60
179k
      for (BasicBlock *successor : successors(currentBlock)) {
61
        // If this block marks the end of the try, then add it to tryEndBlocks,
62
        // but not to the stack. It will be visited by the caller.
63
        // Otherwise, add unvisited blocks to the stack.
64
179k
        if (llvh::isa<TryEndInst>(&successor->front()))
65
7.35k
          tryEndBlocks.insert(successor);
66
171k
        else if (visited.insert(successor).second)
67
152k
          stack.push_back(successor);
68
179k
      }
69
166k
      continue;
70
166k
    }
71
72
    // Hit a TryStartInst, marking the start of a new try region.
73
    // The first instruction of the catch target block must be CatchInst.
74
7.35k
    auto catchInst = cast<CatchInst>(&tryStartInst->getCatchTarget()->front());
75
7.35k
    catchInfoMap[catchInst].depth = aliveCatches.size();
76
77
    // Pushing the CatchInst to the try stack, and continue scan the try body.
78
7.35k
    aliveCatches.push_back(catchInst);
79
7.35k
    auto endBlocks = constructCatchMap(
80
7.35k
        F,
81
7.35k
        catchInfoMap,
82
7.35k
        aliveCatches,
83
7.35k
        visited,
84
7.35k
        tryStartInst->getTryBody(),
85
7.35k
        maxRecursionDepth - 1);
86
7.35k
    if (!endBlocks)
87
0
      return llvh::None;
88
7.35k
    aliveCatches.pop_back();
89
90
7.35k
    for (BasicBlock *endBlock : *endBlocks) {
91
7.35k
      assert(
92
7.35k
          visited.count(endBlock) == 0 &&
93
7.35k
          "End blocks must be dominated by the try start.");
94
7.35k
      assert(
95
7.35k
          llvh::isa<TryEndInst>(&endBlock->front()) &&
96
7.35k
          "End blocks must start with TryEndInst.");
97
7.35k
      visited.insert(endBlock);
98
7.35k
      stack.push_back(endBlock);
99
7.35k
    }
100
101
    // We also want to continue scan into the catch blocks.
102
7.35k
    BasicBlock *catchTarget = tryStartInst->getCatchTarget();
103
7.35k
    assert(
104
7.35k
        visited.count(catchTarget) == 0 &&
105
7.35k
        "Catch block must be dominated by the try start.");
106
7.35k
    visited.insert(catchTarget);
107
7.35k
    stack.push_back(catchTarget);
108
7.35k
  }
109
7.36k
  assert(
110
7.36k
      (aliveCatches.size() || !tryEndBlocks.size()) &&
111
7.36k
      "Block without live catches cannot have TryEndInst.");
112
7.36k
  return tryEndBlocks;
113
7.36k
}
114
115
ExceptionEntryList hermes::generateExceptionHandlers(
116
    CatchInfoMap &catchInfoMap,
117
    BasicBlockInfoMap &bbMap,
118
8
    Function *F) {
119
  // Construct the list of blocks and depth covered by each CatchInst.
120
8
  llvh::SmallVector<CatchInst *, 4> aliveCatches{};
121
8
  llvh::SmallPtrSet<BasicBlock *, 32> visited{};
122
8
  static constexpr uint32_t MAX_RECURSION_DEPTH = 1024;
123
8
  if (!constructCatchMap(
124
8
          F,
125
8
          catchInfoMap,
126
8
          aliveCatches,
127
8
          visited,
128
8
          &F->front(),
129
8
          MAX_RECURSION_DEPTH))
130
0
    return {};
131
132
8
  ExceptionEntryList exception_entries;
133
7.35k
  for (auto I : catchInfoMap) {
134
7.35k
    auto &catchInfo = I.second;
135
    // The basic blocks covered by a catch instruction may not be continuous.
136
    // For each basic block, we walk through the current list of ranges,
137
    // and try to merge them into a minimum number of ranges.
138
7.35k
    llvh::SmallVector<std::pair<uint32_t, uint32_t>, 4> catch_ranges;
139
1.86M
    for (auto BB : catchInfo.coveredBlockList) {
140
1.86M
      auto it = bbMap.find(BB);
141
1.86M
      assert(it != bbMap.end() && "Basic Block missing.");
142
143
1.86M
      auto resolved_loc = it->second;
144
1.86M
      if (resolved_loc.first == resolved_loc.second) {
145
        // Empty basic block, skip.
146
586k
        continue;
147
586k
      }
148
1.28M
      catch_ranges.push_back(resolved_loc);
149
1.28M
    }
150
7.35k
    std::sort(catch_ranges.begin(), catch_ranges.end());
151
    // After ranges are sorted, a range could only be merged with it's
152
    // previous range, if they are adjacent.
153
    // Note: no range can overlap, as basic blocks do not overlap.
154
7.35k
    int nextIndex = 0;
155
1.28M
    for (auto resolved_loc : catch_ranges) {
156
      // If we are looking at the first range, or the range cannot
157
      // be merged with the previous range, we store this range.
158
1.28M
      if (nextIndex == 0 ||
159
1.28M
          catch_ranges[nextIndex - 1].second != resolved_loc.first) {
160
6.94k
        catch_ranges[nextIndex++] = resolved_loc;
161
6.94k
        continue;
162
6.94k
      }
163
      // Otherwise we merge with the previous range.
164
1.27M
      catch_ranges[nextIndex - 1].second = resolved_loc.second;
165
1.27M
    }
166
    // The merging happened in-place. Hence we need to throw away the rest.
167
7.35k
    catch_ranges.resize(nextIndex);
168
    // For each range, we register it as an exception handler entry.
169
7.35k
    for (auto range : catch_ranges) {
170
6.94k
      exception_entries.push_back(
171
6.94k
          {(uint32_t)range.first,
172
6.94k
           (uint32_t)range.second,
173
6.94k
           (uint32_t)catchInfo.catchLocation,
174
6.94k
           catchInfo.depth});
175
6.94k
    }
176
7.35k
  }
177
  // Sort ranges by depth. In hermes, depth increase when you nest try inside
178
  // try/catch/finally.
179
8
  std::sort(exception_entries.begin(), exception_entries.end());
180
8
  return exception_entries;
181
8
}
182
183
#undef DEBUG_TYPE