Skip to content

Commit adb96d2

Browse files
committed
[WebAssembly] Fix leak in Emscripten SjLj
For SjLj, we allocate a table to record setjmp buffer info in the entry of each setjmp-calling function by inserting a `malloc` call, and insert a `free` call to free the buffer before each `ret` instruction. But this is not sufficient; we have to free the buffer before we throw. In SjLj handling, normal functions that can possibly throw or longjmp are wrapped with an invoke and caught within the function so they don't end up escaping the function. But three functions throw and escape the function: - `__resumeException` (Emscripten library function used for Emscripten EH) - `emscripten_longjmp` (Emscripten library function used for Emscripten SjLj) - `__cxa_throw` (libc++abi function called when for C++ `throw` keyword) The first two functions are used to rethrow the current exception/longjmp when the caught exception/longjmp is not for the current function. `__cxa_throw` is used for exception, and because we consider that a function that cannot longjmp, it escapes the function right away, before which we should free the buffer. Currently `lsan.test_longjmp3` and `lsan.test_exceptions_longjmp3` fail in Emscripten; this CL fixes these. Reviewed By: dschuff Differential Revision: https://reviews.llvm.org/D107852
1 parent aca198c commit adb96d2

File tree

3 files changed

+55
-11
lines changed

3 files changed

+55
-11
lines changed

llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,21 +1244,33 @@ bool WebAssemblyLowerEmscriptenEHSjLj::runSjLjOnFunction(Function &F) {
12441244
for (Instruction *I : ToErase)
12451245
I->eraseFromParent();
12461246

1247-
// Free setjmpTable buffer before each return instruction
1247+
// Free setjmpTable buffer before each return instruction + function-exiting
1248+
// call
1249+
SmallVector<Instruction *, 16> ExitingInsts;
12481250
for (BasicBlock &BB : F) {
12491251
Instruction *TI = BB.getTerminator();
1250-
if (isa<ReturnInst>(TI)) {
1251-
DebugLoc DL = getOrCreateDebugLoc(TI, F.getSubprogram());
1252-
auto *Free = CallInst::CreateFree(SetjmpTable, TI);
1253-
Free->setDebugLoc(DL);
1254-
// CallInst::CreateFree may create a bitcast instruction if its argument
1255-
// types mismatch. We need to set the debug loc for the bitcast too.
1256-
if (auto *FreeCallI = dyn_cast<CallInst>(Free)) {
1257-
if (auto *BitCastI = dyn_cast<BitCastInst>(FreeCallI->getArgOperand(0)))
1258-
BitCastI->setDebugLoc(DL);
1252+
if (isa<ReturnInst>(TI))
1253+
ExitingInsts.push_back(TI);
1254+
for (auto &I : BB) {
1255+
if (auto *CB = dyn_cast<CallBase>(&I)) {
1256+
StringRef CalleeName = CB->getCalledOperand()->getName();
1257+
if (CalleeName == "__resumeException" ||
1258+
CalleeName == "emscripten_longjmp" || CalleeName == "__cxa_throw")
1259+
ExitingInsts.push_back(&I);
12591260
}
12601261
}
12611262
}
1263+
for (auto *I : ExitingInsts) {
1264+
DebugLoc DL = getOrCreateDebugLoc(I, F.getSubprogram());
1265+
auto *Free = CallInst::CreateFree(SetjmpTable, I);
1266+
Free->setDebugLoc(DL);
1267+
// CallInst::CreateFree may create a bitcast instruction if its argument
1268+
// types mismatch. We need to set the debug loc for the bitcast too.
1269+
if (auto *FreeCallI = dyn_cast<CallInst>(Free)) {
1270+
if (auto *BitCastI = dyn_cast<BitCastInst>(FreeCallI->getArgOperand(0)))
1271+
BitCastI->setDebugLoc(DL);
1272+
}
1273+
}
12621274

12631275
// Every call to saveSetjmp can change setjmpTable and setjmpTableSize
12641276
// (when buffer reallocation occurs)

llvm/test/CodeGen/WebAssembly/lower-em-ehsjlj.ll

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ try.cont: ; preds = %entry, %lpad
8989

9090
; This function contains a setjmp call and no invoke, so we only handle longjmp
9191
; here. But @foo can also throw an exception, so we check if an exception is
92-
; thrown and if so rethrow it by calling @__resumeException.
92+
; thrown and if so rethrow it by calling @__resumeException. Also we have to
93+
; free the setjmpTable buffer before calling @__resumeException.
9394
define void @rethrow_exception() {
9495
; CHECK-LABEL: @rethrow_exception
9596
entry:
@@ -112,13 +113,41 @@ if.end: ; preds = %entry
112113

113114
; CHECK: eh.rethrow:
114115
; CHECK-NEXT: %exn = call i8* @__cxa_find_matching_catch_2()
116+
; CHECK-NEXT: %[[BUF:.*]] = bitcast i32* %setjmpTable1 to i8*
117+
; CHECK-NEXT: call void @free(i8* %[[BUF]])
115118
; CHECK-NEXT: call void @__resumeException(i8* %exn)
116119
; CHECK-NEXT: unreachable
117120

118121
return: ; preds = %entry, %if.end
119122
ret void
120123
}
121124

125+
; The same as 'rethrow_exception' but contains a __cxa_throw call. We have to
126+
; free the setjmpTable buffer before calling __cxa_throw.
127+
define void @rethrow_exception2() {
128+
; CHECK-LABEL: @rethrow_exception2
129+
entry:
130+
%buf = alloca [1 x %struct.__jmp_buf_tag], align 16
131+
%arraydecay = getelementptr inbounds [1 x %struct.__jmp_buf_tag], [1 x %struct.__jmp_buf_tag]* %buf, i32 0, i32 0
132+
%call = call i32 @setjmp(%struct.__jmp_buf_tag* %arraydecay) #0
133+
%cmp = icmp ne i32 %call, 0
134+
br i1 %cmp, label %throw, label %if.end
135+
136+
if.end: ; preds = %entry
137+
call void @foo()
138+
br label %throw
139+
140+
throw: ; preds = %entry, %if.end
141+
call void @__cxa_throw(i8* null, i8* null, i8* null) #1
142+
unreachable
143+
144+
; CHECK: throw:
145+
; CHECK: %[[BUF:.*]] = bitcast i32* %setjmpTable5 to i8*
146+
; CHECK-NEXT: call void @free(i8* %[[BUF]])
147+
; CHECK-NEXT: call void @__cxa_throw(i8* null, i8* null, i8* null)
148+
; CHECK-NEXT: unreachable
149+
}
150+
122151
declare void @foo()
123152
; Function Attrs: returns_twice
124153
declare i32 @setjmp(%struct.__jmp_buf_tag*)
@@ -127,6 +156,7 @@ declare void @longjmp(%struct.__jmp_buf_tag*, i32)
127156
declare i32 @__gxx_personality_v0(...)
128157
declare i8* @__cxa_begin_catch(i8*)
129158
declare void @__cxa_end_catch()
159+
declare void @__cxa_throw(i8*, i8*, i8*)
130160

131161
attributes #0 = { returns_twice }
132162
attributes #1 = { noreturn }

llvm/test/CodeGen/WebAssembly/lower-em-sjlj.ll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ entry:
7171
; CHECK-NEXT: ]
7272

7373
; CHECK: if.then2:
74+
; CHECK-NEXT: %[[BUF:.*]] = bitcast i32* %[[SETJMP_TABLE1]] to i8*
75+
; CHECK-NEXT: call void @free(i8* %[[BUF]])
7476
; CHECK-NEXT: call void @emscripten_longjmp([[PTR]] %[[__THREW__VAL]], i32 %[[THREWVALUE_VAL]])
7577
; CHECK-NEXT: unreachable
7678

0 commit comments

Comments
 (0)