Coverage Report

Created: 2025-12-11 06:40

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/hermes/lib/IRGen/ESTreeIRGen-except.cpp
Line
Count
Source
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
#include "ESTreeIRGen.h"
9
10
#include "llvh/Support/SaveAndRestore.h"
11
12
#include <variant>
13
14
namespace hermes {
15
namespace irgen {
16
17
0
void ESTreeIRGen::genTryStatement(ESTree::TryStatementNode *tryStmt) {
18
0
  LLVM_DEBUG(llvh::dbgs() << "IRGen 'try' statement\n");
19
20
  // try-catch-finally statements must have been transformed by the validator
21
  // into two nested try statements with only "catch" or "finally" each.
22
0
  assert(
23
0
      (!tryStmt->_handler || !tryStmt->_finalizer) &&
24
0
      "Try statement can't have both catch and finally");
25
26
0
  auto *nextBlock = emitTryCatchScaffolding(
27
0
      nullptr,
28
      // emitBody.
29
0
      [this, tryStmt]() {
30
0
        llvh::Optional<SurroundingTry> thisTry;
31
32
0
        if (tryStmt->_finalizer) {
33
0
          thisTry.emplace(
34
0
              curFunction(),
35
0
              tryStmt,
36
0
              tryStmt->_finalizer->getDebugLoc(),
37
0
              [this](ESTree::Node *node, ControlFlowChange, BasicBlock *) {
38
0
                genStatement(
39
0
                    cast<ESTree::TryStatementNode>(node)->_finalizer,
40
0
                    IsLoopBody::No);
41
0
              });
42
0
        } else {
43
0
          thisTry.emplace(curFunction(), tryStmt);
44
0
        }
45
46
0
        genStatement(tryStmt->_block, IsLoopBody::No);
47
48
0
        Builder.setLocation(
49
0
            SourceErrorManager::convertEndToLocation(
50
0
                tryStmt->_block->getSourceRange()));
51
0
      },
52
      // emitNormalCleanup.
53
0
      [this, tryStmt]() {
54
0
        if (tryStmt->_finalizer) {
55
0
          genStatement(tryStmt->_finalizer, IsLoopBody::No);
56
0
          Builder.setLocation(
57
0
              SourceErrorManager::convertEndToLocation(
58
0
                  tryStmt->_finalizer->getSourceRange()));
59
0
        }
60
0
      },
61
      // emitHandler.
62
0
      [this, tryStmt](BasicBlock *nextBlock) {
63
        // If we have a catch block.
64
0
        if (tryStmt->_handler) {
65
0
          auto *catchClauseNode =
66
0
              llvh::dyn_cast<ESTree::CatchClauseNode>(tryStmt->_handler);
67
68
          // Catch takes a exception variable, hence we need to create a new
69
          // scope for it. The --block-scoping flag controls how the catch
70
          // variable will be created. N.B.: newScope isn't used directly after
71
          // its contents are emplaced, but the constructors/destructors of its
72
          // payload have important side-effects (named, pushing/popping the
73
          // scope information from the IR generation).
74
0
          std::variant<std::monostate, NameTableScopeTy, EnterBlockScope>
75
0
              newScope;
76
77
0
          if (!Mod->getContext()
78
0
                   .getCodeGenerationSettings()
79
0
                   .enableBlockScoping) {
80
0
            newScope.emplace<NameTableScopeTy>(nameTable_);
81
0
          } else {
82
0
            newScope.emplace<EnterBlockScope>(curFunction());
83
0
          }
84
85
0
          Builder.setLocation(tryStmt->_handler->getDebugLoc());
86
0
          prepareCatch(catchClauseNode);
87
88
0
          genCatchHandler(catchClauseNode->_body);
89
90
0
          Builder.setLocation(
91
0
              SourceErrorManager::convertEndToLocation(
92
0
                  tryStmt->_handler->getSourceRange()));
93
0
          Builder.createBranchInst(nextBlock);
94
0
        } else {
95
          // A finally block catches the exception and rethrows is.
96
0
          Builder.setLocation(tryStmt->_finalizer->getDebugLoc());
97
0
          auto *catchReg = Builder.createCatchInst();
98
99
0
          genStatement(tryStmt->_finalizer, IsLoopBody::No);
100
101
0
          Builder.setLocation(
102
0
              SourceErrorManager::convertEndToLocation(
103
0
                  tryStmt->_finalizer->getSourceRange()));
104
0
          Builder.createThrowInst(catchReg);
105
0
        }
106
0
      });
107
108
0
  Builder.setInsertionBlock(nextBlock);
109
0
}
110
111
0
CatchInst *ESTreeIRGen::prepareCatch(ESTree::CatchClauseNode *catchHandler) {
112
0
  auto *catchInst = Builder.createCatchInst();
113
114
0
  if (Mod->getContext().getCodeGenerationSettings().enableBlockScoping) {
115
    // Create the catch scope after the Catch instruction so it is part of the
116
    // Catch handler range in the exception tables.
117
0
    blockDeclarationInstantiation(catchHandler);
118
0
  }
119
120
0
  ESTree::NodePtr catchParam = catchHandler->_param;
121
0
  if (!catchParam) {
122
    // Optional catch binding allows us to emit no extra code for the catch.
123
0
    return catchInst;
124
0
  }
125
126
0
  if (!llvh::isa<ESTree::IdentifierNode>(catchParam)) {
127
0
    Builder.getModule()->getContext().getSourceErrorManager().error(
128
0
        catchParam->getSourceRange(),
129
0
        Twine("Destructuring in catch parameters is currently unsupported"));
130
0
    return nullptr;
131
0
  }
132
133
0
  Variable *errorVar{};
134
135
0
  if (!Mod->getContext().getCodeGenerationSettings().enableBlockScoping) {
136
0
    auto catchVariableName =
137
0
        getNameFieldFromID(cast<ESTree::IdentifierNode>(catchParam));
138
139
    // Generate a unique catch variable name and use this name for IRGen purpose
140
    // only. The variable lookup in the catch clause will continue to be done
141
    // using the declared name.
142
0
    auto uniquedCatchVariableName =
143
0
        genAnonymousLabelName(catchVariableName.str());
144
145
0
    errorVar = Builder.createVariable(
146
0
        curFunction()->function->getFunctionScopeDesc(),
147
0
        Variable::DeclKind::Var,
148
0
        uniquedCatchVariableName);
149
150
    /// Insert the synthesized variable into the function name table, so it can
151
    /// be looked up internally.
152
0
    nameTable_.insertIntoScope(
153
0
        curFunction()->functionScope, errorVar->getName(), errorVar);
154
155
    // Alias the lexical name to the synthesized variable.
156
0
    nameTable_.insert(catchVariableName, errorVar);
157
0
  } else {
158
0
    auto catchVariableName =
159
0
        getNameFieldFromID(cast<ESTree::IdentifierNode>(catchParam));
160
161
0
    errorVar = Builder.createVariable(
162
0
        currentIRScopeDesc_, Variable::DeclKind::Var, catchVariableName);
163
164
    /// Insert the synthesized variable into the function name table, so it can
165
    /// be looked up internally.
166
0
    nameTable_.insertIntoScope(
167
0
        curFunction()->blockScope, errorVar->getName(), errorVar);
168
0
  }
169
170
0
  emitStore(catchInst, errorVar, true);
171
0
  return catchInst;
172
0
}
173
174
void ESTreeIRGen::genFinallyBeforeControlChange(
175
    SurroundingTry *sourceTry,
176
    SurroundingTry *targetTry,
177
    ControlFlowChange cfc,
178
1.78k
    BasicBlock *continueTarget) {
179
1.78k
  assert(
180
1.78k
      (cfc == ControlFlowChange::Break || continueTarget != nullptr) &&
181
1.78k
      "Continue ControlFlowChange must have a target");
182
  // We walk the nested try statements starting from the source, until we reach
183
  // the target, generating the finally statements on the way.
184
1.78k
  for (; sourceTry != targetTry; sourceTry = sourceTry->outer) {
185
0
    assert(sourceTry && "invalid try chain");
186
187
    // Emit an end of the try statement.
188
0
    auto *tryEndBlock = Builder.createBasicBlock(curFunction()->function);
189
0
    Builder.createBranchInst(tryEndBlock);
190
0
    Builder.setInsertionBlock(tryEndBlock);
191
192
    // Make sure we use the correct debug location for tryEndInst.
193
0
    if (sourceTry->tryEndLoc.isValid()) {
194
0
      hermes::IRBuilder::ScopedLocationChange slc(
195
0
          Builder, sourceTry->tryEndLoc);
196
0
      Builder.createTryEndInst();
197
0
    } else {
198
0
      Builder.createTryEndInst();
199
0
    }
200
201
0
    if (sourceTry->genFinalizer) {
202
      // Recreate the state of the try stack on entrance to the finally block.
203
0
      llvh::SaveAndRestore<SurroundingTry *> sr{
204
0
          curFunction()->surroundingTry, sourceTry->outer};
205
0
      sourceTry->genFinalizer(sourceTry->node, cfc, continueTarget);
206
0
    }
207
0
  }
208
1.78k
}
209
210
} // namespace irgen
211
} // namespace hermes