From 565e4df155629407631417e404ed01d513bdc897 Mon Sep 17 00:00:00 2001 From: Alexey Samsonov Date: Mon, 8 Sep 2014 17:22:45 +0000 Subject: [PATCH] Implement nonnull-attribute sanitizer Summary: This patch implements a new UBSan check, which verifies that function arguments declared to be nonnull with __attribute__((nonnull)) are actually nonnull in runtime. To implement this check, we pass FunctionDecl to CodeGenFunction::EmitCallArgs (where applicable) and if function declaration has nonnull attribute specified for a certain formal parameter, we compare the corresponding RValue to null as soon as it's calculated. Test Plan: regression test suite Reviewers: rsmith Reviewed By: rsmith Subscribers: cfe-commits, rnk Differential Revision: http://reviews.llvm.org/D5082 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@217389 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/UsersManual.rst | 2 + include/clang/Basic/Sanitizers.def | 14 ++++--- lib/CodeGen/CGCall.cpp | 57 +++++++++++++++++++++++------ lib/CodeGen/CGClass.cpp | 5 ++- lib/CodeGen/CGExpr.cpp | 3 +- lib/CodeGen/CGExprCXX.cpp | 23 ++++++------ lib/CodeGen/CodeGenFunction.h | 7 +++- test/CodeGen/catch-undef-behavior.c | 29 ++++++++++++++- test/Driver/fsanitize.c | 8 ++-- 9 files changed, 108 insertions(+), 40 deletions(-) diff --git a/docs/UsersManual.rst b/docs/UsersManual.rst index 1920f14542..90d2365ea7 100644 --- a/docs/UsersManual.rst +++ b/docs/UsersManual.rst @@ -977,6 +977,8 @@ are listed below. - ``-fsanitize=function``: Indirect call of a function through a function pointer of the wrong type (Linux, C++ and x86/x86_64 only). - ``-fsanitize=integer-divide-by-zero``: Integer division by zero. + - ``-fsanitize=nonnull-attribute``: Passing null pointer as a function + parameter which is declared to never be null. - ``-fsanitize=null``: Use of a null pointer or creation of a null reference. - ``-fsanitize=object-size``: An attempt to use bytes which the diff --git a/include/clang/Basic/Sanitizers.def b/include/clang/Basic/Sanitizers.def index 2646f05000..adb9b04072 100644 --- a/include/clang/Basic/Sanitizers.def +++ b/include/clang/Basic/Sanitizers.def @@ -59,6 +59,7 @@ SANITIZER("float-cast-overflow", FloatCastOverflow) SANITIZER("float-divide-by-zero", FloatDivideByZero) SANITIZER("function", Function) SANITIZER("integer-divide-by-zero", IntegerDivideByZero) +SANITIZER("nonnull-attribute", NonnullAttribute) SANITIZER("null", Null) SANITIZER("object-size", ObjectSize) SANITIZER("return", Return) @@ -79,9 +80,10 @@ SANITIZER("dataflow", DataFlow) // ABI or address space layout implications, and only catch undefined behavior. SANITIZER_GROUP("undefined", Undefined, Alignment | Bool | ArrayBounds | Enum | FloatCastOverflow | - FloatDivideByZero | Function | IntegerDivideByZero | Null | - ObjectSize | Return | ReturnsNonnullAttribute | Shift | - SignedIntegerOverflow | Unreachable | VLABound | Vptr) + FloatDivideByZero | Function | IntegerDivideByZero | + NonnullAttribute | Null | ObjectSize | Return | + ReturnsNonnullAttribute | Shift | SignedIntegerOverflow | + Unreachable | VLABound | Vptr) // -fsanitize=undefined-trap includes // all sanitizers included by -fsanitize=undefined, except those that require @@ -89,9 +91,9 @@ SANITIZER_GROUP("undefined", Undefined, // -fsanitize-undefined-trap-on-error flag. SANITIZER_GROUP("undefined-trap", UndefinedTrap, Alignment | Bool | ArrayBounds | Enum | FloatCastOverflow | - FloatDivideByZero | IntegerDivideByZero | Null | - ObjectSize | Return | ReturnsNonnullAttribute | Shift | - SignedIntegerOverflow | Unreachable | VLABound) + FloatDivideByZero | IntegerDivideByZero | NonnullAttribute | + Null | ObjectSize | Return | ReturnsNonnullAttribute | + Shift | SignedIntegerOverflow | Unreachable | VLABound) SANITIZER_GROUP("integer", Integer, SignedIntegerOverflow | UnsignedIntegerOverflow | Shift | diff --git a/lib/CodeGen/CGCall.cpp b/lib/CodeGen/CGCall.cpp index 3619725342..cf794d0de3 100644 --- a/lib/CodeGen/CGCall.cpp +++ b/lib/CodeGen/CGCall.cpp @@ -1417,7 +1417,10 @@ static llvm::Value *emitArgumentDemotion(CodeGenFunction &CGF, return CGF.Builder.CreateFPCast(value, varType, "arg.unpromote"); } -static bool shouldAddNonNullAttr(const Decl *FD, const ParmVarDecl *PVD) { +/// Returns the attribute (either parameter attribute, or function +/// attribute), which declares argument ArgNo to be non-null. +static const NonNullAttr *getNonNullAttr(const Decl *FD, const ParmVarDecl *PVD, + QualType ArgType, unsigned ArgNo) { // FIXME: __attribute__((nonnull)) can also be applied to: // - references to pointers, where the pointee is known to be // nonnull (apparently a Clang extension) @@ -1425,20 +1428,21 @@ static bool shouldAddNonNullAttr(const Decl *FD, const ParmVarDecl *PVD) { // In the former case, LLVM IR cannot represent the constraint. In // the latter case, we have no guarantee that the transparent union // is in fact passed as a pointer. - if (!PVD->getType()->isAnyPointerType() && - !PVD->getType()->isBlockPointerType()) - return false; + if (!ArgType->isAnyPointerType() && !ArgType->isBlockPointerType()) + return nullptr; // First, check attribute on parameter itself. - if (PVD->hasAttr()) - return true; + if (PVD) { + if (auto ParmNNAttr = PVD->getAttr()) + return ParmNNAttr; + } // Check function attributes. if (!FD) - return false; + return nullptr; for (const auto *NNAttr : FD->specific_attrs()) { - if (NNAttr->isNonNull(PVD->getFunctionScopeIndex())) - return true; + if (NNAttr->isNonNull(ArgNo)) + return NNAttr; } - return false; + return nullptr; } void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, @@ -1579,7 +1583,8 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, llvm::Value *V = AI; if (const ParmVarDecl *PVD = dyn_cast(Arg)) { - if (shouldAddNonNullAttr(CurCodeDecl, PVD)) + if (getNonNullAttr(CurCodeDecl, PVD, PVD->getType(), + PVD->getFunctionScopeIndex())) AI->addAttr(llvm::AttributeSet::get(getLLVMContext(), AI->getArgNo() + 1, llvm::Attribute::NonNull)); @@ -2412,10 +2417,36 @@ void CallArgList::freeArgumentMemory(CodeGenFunction &CGF) const { } } +static void emitNonNullArgCheck(CodeGenFunction &CGF, RValue RV, + QualType ArgType, SourceLocation ArgLoc, + const FunctionDecl *FD, unsigned ParmNum) { + if (!CGF.SanOpts->NonnullAttribute || !FD) + return; + auto PVD = ParmNum < FD->getNumParams() ? FD->getParamDecl(ParmNum) : nullptr; + unsigned ArgNo = PVD ? PVD->getFunctionScopeIndex() : ParmNum; + auto NNAttr = getNonNullAttr(FD, PVD, ArgType, ArgNo); + if (!NNAttr) + return; + CodeGenFunction::SanitizerScope SanScope(&CGF); + assert(RV.isScalar()); + llvm::Value *V = RV.getScalarVal(); + llvm::Value *Cond = + CGF.Builder.CreateICmpNE(V, llvm::Constant::getNullValue(V->getType())); + llvm::Constant *StaticData[] = { + CGF.EmitCheckSourceLocation(ArgLoc), + CGF.EmitCheckSourceLocation(NNAttr->getLocation()), + llvm::ConstantInt::get(CGF.Int32Ty, ArgNo + 1), + }; + CGF.EmitCheck(Cond, "nonnull_arg", StaticData, None, + CodeGenFunction::CRK_Recoverable); +} + void CodeGenFunction::EmitCallArgs(CallArgList &Args, ArrayRef ArgTypes, CallExpr::const_arg_iterator ArgBeg, CallExpr::const_arg_iterator ArgEnd, + const FunctionDecl *CalleeDecl, + unsigned ParamsToSkip, bool ForceColumnInfo) { CGDebugInfo *DI = getDebugInfo(); SourceLocation CallLoc; @@ -2439,6 +2470,8 @@ void CodeGenFunction::EmitCallArgs(CallArgList &Args, for (int I = ArgTypes.size() - 1; I >= 0; --I) { CallExpr::const_arg_iterator Arg = ArgBeg + I; EmitCallArg(Args, *Arg, ArgTypes[I]); + emitNonNullArgCheck(*this, Args.back().RV, ArgTypes[I], Arg->getExprLoc(), + CalleeDecl, ParamsToSkip + I); // Restore the debug location. if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); } @@ -2453,6 +2486,8 @@ void CodeGenFunction::EmitCallArgs(CallArgList &Args, CallExpr::const_arg_iterator Arg = ArgBeg + I; assert(Arg != ArgEnd); EmitCallArg(Args, *Arg, ArgTypes[I]); + emitNonNullArgCheck(*this, Args.back().RV, ArgTypes[I], Arg->getExprLoc(), + CalleeDecl, ParamsToSkip + I); // Restore the debug location. if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); } diff --git a/lib/CodeGen/CGClass.cpp b/lib/CodeGen/CGClass.cpp index 3277b416ed..73afe234e9 100644 --- a/lib/CodeGen/CGClass.cpp +++ b/lib/CodeGen/CGClass.cpp @@ -1674,7 +1674,7 @@ void CodeGenFunction::EmitCXXConstructorCall(const CXXConstructorDecl *D, // Add the rest of the user-supplied arguments. const FunctionProtoType *FPT = D->getType()->castAs(); - EmitCallArgs(Args, FPT, E->arg_begin(), E->arg_end()); + EmitCallArgs(Args, FPT, E->arg_begin(), E->arg_end(), E->getConstructor()); // Insert any ABI-specific implicit constructor arguments. unsigned ExtraArgs = CGM.getCXXABI().addImplicitConstructorArgs( @@ -1716,7 +1716,8 @@ CodeGenFunction::EmitSynthesizedCXXCopyCtorCall(const CXXConstructorDecl *D, Args.add(RValue::get(Src), QT); // Skip over first argument (Src). - EmitCallArgs(Args, FPT, E->arg_begin() + 1, E->arg_end(), /*ParamsToSkip*/ 1); + EmitCallArgs(Args, FPT, E->arg_begin() + 1, E->arg_end(), E->getConstructor(), + /*ParamsToSkip*/ 1); EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, RequiredArgs::All), Callee, ReturnValueSlot(), Args, D); diff --git a/lib/CodeGen/CGExpr.cpp b/lib/CodeGen/CGExpr.cpp index 426ec7496c..61818db285 100644 --- a/lib/CodeGen/CGExpr.cpp +++ b/lib/CodeGen/CGExpr.cpp @@ -3348,7 +3348,8 @@ RValue CodeGenFunction::EmitCall(QualType CalleeType, llvm::Value *Callee, CallArgList Args; EmitCallArgs(Args, dyn_cast(FnType), E->arg_begin(), - E->arg_end(), /*ParamsToSkip*/ 0, ForceColumnInfo); + E->arg_end(), E->getDirectCallee(), /*ParamsToSkip*/ 0, + ForceColumnInfo); const CGFunctionInfo &FnInfo = CGM.getTypes().arrangeFreeFunctionCall(Args, FnType); diff --git a/lib/CodeGen/CGExprCXX.cpp b/lib/CodeGen/CGExprCXX.cpp index 937a0438ad..a3e444a3f5 100644 --- a/lib/CodeGen/CGExprCXX.cpp +++ b/lib/CodeGen/CGExprCXX.cpp @@ -55,20 +55,18 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorCall( const FunctionProtoType *FPT = MD->getType()->castAs(); RequiredArgs required = RequiredArgs::forPrototypePlus(FPT, Args.size()); - + // And the rest of the call args. - CallExpr::const_arg_iterator ArgBeg, ArgEnd; - if (CE == nullptr) { - ArgBeg = ArgEnd = nullptr; - } else if (auto OCE = dyn_cast(CE)) { + if (CE) { // Special case: skip first argument of CXXOperatorCall (it is "this"). - ArgBeg = OCE->arg_begin() + 1; - ArgEnd = OCE->arg_end(); + unsigned ArgsToSkip = isa(CE) ? 1 : 0; + EmitCallArgs(Args, FPT, CE->arg_begin() + ArgsToSkip, CE->arg_end(), + CE->getDirectCallee()); } else { - ArgBeg = CE->arg_begin(); - ArgEnd = CE->arg_end(); + assert( + FPT->getNumParams() == 0 && + "No CallExpr specified for function with non-zero number of arguments"); } - EmitCallArgs(Args, FPT, ArgBeg, ArgEnd); return EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, required), Callee, ReturnValue, Args, MD); @@ -284,7 +282,7 @@ CodeGenFunction::EmitCXXMemberPointerCallExpr(const CXXMemberCallExpr *E, RequiredArgs required = RequiredArgs::forPrototypePlus(FPT, 1); // And the rest of the call args - EmitCallArgs(Args, FPT, E->arg_begin(), E->arg_end()); + EmitCallArgs(Args, FPT, E->arg_begin(), E->arg_end(), E->getDirectCallee()); return EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, required), Callee, ReturnValue, Args); } @@ -1238,7 +1236,8 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { // We start at 1 here because the first argument (the allocation size) // has already been emitted. EmitCallArgs(allocatorArgs, allocatorType, E->placement_arg_begin(), - E->placement_arg_end(), /*ParamsToSkip*/ 1); + E->placement_arg_end(), /* CalleeDecl */ nullptr, + /*ParamsToSkip*/ 1); // Emit the allocation call. If the allocator is a global placement // operator, just "inline" it directly. diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index 4841b53506..ff74ce9fd7 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -2625,6 +2625,7 @@ public: void EmitCallArgs(CallArgList &Args, const T *CallArgTypeInfo, CallExpr::const_arg_iterator ArgBeg, CallExpr::const_arg_iterator ArgEnd, + const FunctionDecl *CalleeDecl = nullptr, unsigned ParamsToSkip = 0, bool ForceColumnInfo = false) { SmallVector ArgTypes; CallExpr::const_arg_iterator Arg = ArgBeg; @@ -2673,13 +2674,15 @@ public: for (; Arg != ArgEnd; ++Arg) ArgTypes.push_back(Arg->getType()); - EmitCallArgs(Args, ArgTypes, ArgBeg, ArgEnd, ForceColumnInfo); + EmitCallArgs(Args, ArgTypes, ArgBeg, ArgEnd, CalleeDecl, ParamsToSkip, + ForceColumnInfo); } void EmitCallArgs(CallArgList &Args, ArrayRef ArgTypes, CallExpr::const_arg_iterator ArgBeg, CallExpr::const_arg_iterator ArgEnd, - bool ForceColumnInfo = false); + const FunctionDecl *CalleeDecl = nullptr, + unsigned ParamsToSkip = 0, bool ForceColumnInfo = false); private: const TargetCodeGenInfo &getTargetHooks() const { diff --git a/test/CodeGen/catch-undef-behavior.c b/test/CodeGen/catch-undef-behavior.c index 2b9aedd4b5..5250ea69c8 100644 --- a/test/CodeGen/catch-undef-behavior.c +++ b/test/CodeGen/catch-undef-behavior.c @@ -1,5 +1,5 @@ -// RUN: %clang_cc1 -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s -// RUN: %clang_cc1 -fsanitize-undefined-trap-on-error -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-TRAP +// RUN: %clang_cc1 -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s +// RUN: %clang_cc1 -fsanitize-undefined-trap-on-error -fsanitize=alignment,null,object-size,shift,return,signed-integer-overflow,vla-bound,float-cast-overflow,integer-divide-by-zero,bool,returns-nonnull-attribute,nonnull-attribute -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-TRAP // RUN: %clang_cc1 -fsanitize=null -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-NULL // RUN: %clang_cc1 -fsanitize=signed-integer-overflow -emit-llvm %s -o - -triple x86_64-linux-gnu | FileCheck %s --check-prefix=CHECK-OVERFLOW @@ -446,6 +446,31 @@ int *ret_nonnull(int *a) { return a; } +// CHECK-LABEL: @call_decl_nonnull +__attribute__((nonnull)) void decl_nonnull(int *a); +void call_decl_nonnull(int *a) { + // CHECK: [[OK:%.*]] = icmp ne i32* {{.*}}, null + // CHECK: br i1 [[OK]] + // CHECK: call void @__ubsan_handle_nonnull_arg + + // CHECK-TRAP: [[OK:%.*]] = icmp ne i32* {{.*}}, null + // CHECK-TRAP: br i1 [[OK]] + // CHECK-TRAP: call void @llvm.trap() [[NR_NUW]] + // CHECK-TRAP: unreachable + decl_nonnull(a); +} + +// CHECK-LABEL: @call_nonnull_variadic +__attribute__((nonnull)) void nonnull_variadic(int a, ...); +void call_nonnull_variadic(int a, int *b) { + // CHECK: [[OK:%.*]] = icmp ne i32* {{.*}}, null + // CHECK: br i1 [[OK]] + // CHECK: call void @__ubsan_handle_nonnull_arg + // CHECK-NOT: __ubsan_handle_nonnull_arg + // CHECK: call void (i32, ...)* @nonnull_variadic + nonnull_variadic(a, b); +} + // CHECK: ![[WEIGHT_MD]] = metadata !{metadata !"branch_weights", i32 1048575, i32 1} // CHECK-TRAP: attributes [[NR_NUW]] = { noreturn nounwind } diff --git a/test/Driver/fsanitize.c b/test/Driver/fsanitize.c index d157d26470..5099c04ce0 100644 --- a/test/Driver/fsanitize.c +++ b/test/Driver/fsanitize.c @@ -1,13 +1,13 @@ // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined-trap -fsanitize-undefined-trap-on-error %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP // RUN: %clang -target x86_64-linux-gnu -fsanitize-undefined-trap-on-error -fsanitize=undefined-trap %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP -// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|shift|unreachable|return|vla-bound|alignment|null|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute),?){15}"}} +// CHECK-UNDEFINED-TRAP: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|shift|unreachable|return|vla-bound|alignment|null|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute|nonnull-attribute),?){16}"}} // CHECK-UNDEFINED-TRAP: "-fsanitize-undefined-trap-on-error" // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED -// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|function|shift|unreachable|return|vla-bound|alignment|null|vptr|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute),?){17}"}} +// CHECK-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|function|shift|unreachable|return|vla-bound|alignment|null|vptr|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute|nonnull-attribute),?){18}"}} // RUN: %clang -target x86_64-apple-darwin10 -fsanitize=undefined %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-DARWIN -// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|shift|unreachable|return|vla-bound|alignment|null|vptr|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute),?){16}"}} +// CHECK-UNDEFINED-DARWIN: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|shift|unreachable|return|vla-bound|alignment|null|vptr|object-size|float-cast-overflow|array-bounds|enum|bool|returns-nonnull-attribute|nonnull-attribute),?){17}"}} // RUN: %clang -target x86_64-linux-gnu -fsanitize=integer %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-INTEGER // CHECK-INTEGER: "-fsanitize={{((signed-integer-overflow|unsigned-integer-overflow|integer-divide-by-zero|shift),?){4}"}} @@ -16,7 +16,7 @@ // CHECK-BOUNDS: "-fsanitize={{((array-bounds|local-bounds),?){2}"}} // RUN: %clang -target x86_64-linux-gnu -fsanitize=thread,undefined -fno-sanitize=thread -fno-sanitize=float-cast-overflow,vptr,bool,enum %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-PARTIAL-UNDEFINED -// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|function|shift|unreachable|return|vla-bound|alignment|null|object-size|array-bounds|returns-nonnull-attribute),?){13}"}} +// CHECK-PARTIAL-UNDEFINED: "-fsanitize={{((signed-integer-overflow|integer-divide-by-zero|float-divide-by-zero|function|shift|unreachable|return|vla-bound|alignment|null|object-size|array-bounds|returns-nonnull-attribute|nonnull-attribute),?){14}"}} // RUN: %clang -target x86_64-linux-gnu -fsanitize=undefined -fsanitize-undefined-trap-on-error %s -### 2>&1 | FileCheck %s --check-prefix=CHECK-UNDEFINED-TRAP-ON-ERROR-UNDEF // CHECK-UNDEFINED-TRAP-ON-ERROR-UNDEF: '-fsanitize=undefined' not allowed with '-fsanitize-undefined-trap-on-error' -- 2.40.0