From 828e7a8a20c2ef478ba253c47cd6aa19c8318da2 Mon Sep 17 00:00:00 2001 From: liuxinyi Date: Thu, 19 Dec 2024 10:15:34 +0800 Subject: [PATCH 1/4] [NullabilityCheck]bugfix of implicit or explicit cast between nullable and nonnull pointer 1. add error report when explicit type cast from nullable to nonnull pointer 2. delete warning when explicit cast between type with nullability qulifier and type without nullability qualifier, such as (T*_Nullable)(T*)p 3. int *p = foo(); if return type of foo is void* which is nullable, p should also be nullable --- clang/include/clang/AST/Expr.h | 4 ++ .../Analysis/Analyses/BSCNullabilityCheck.h | 4 ++ .../clang/Basic/BSC/DiagnosticBSCSemaKinds.td | 2 + clang/lib/AST/BSC/ExprBSC.cpp | 22 ++++++++ clang/lib/Analysis/BSCNullabilityCheck.cpp | 50 ++++++++++--------- clang/lib/Parse/ParseDecl.cpp | 2 +- .../Negative/NullabilityCheck/type_cast.cbs | 35 +++++++++++++ 7 files changed, 94 insertions(+), 25 deletions(-) create mode 100644 clang/test/BSC/Negative/NullabilityCheck/type_cast.cbs diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index d51d08878bf9..867dede1da1b 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -590,6 +590,10 @@ public: SmallVectorImpl< PartialDiagnosticAt> &Diags); +#if ENABLE_BSC + bool isNullExpr(ASTContext &Ctx) const; +#endif + /// isConstantInitializer - Returns true if this expression can be emitted to /// IR as a constant, and thus can be used as a constant initializer in C. /// If this expression is not constant and Culprit is non-null, diff --git a/clang/include/clang/Analysis/Analyses/BSCNullabilityCheck.h b/clang/include/clang/Analysis/Analyses/BSCNullabilityCheck.h index 301900e468bb..d7cec1207cd5 100644 --- a/clang/include/clang/Analysis/Analyses/BSCNullabilityCheck.h +++ b/clang/include/clang/Analysis/Analyses/BSCNullabilityCheck.h @@ -25,6 +25,7 @@ enum NullabilityCheckDiagKind { NonnullAssignedByNullable, PassNullableArgument, ReturnNullable, + NullableCastNonnull, NullablePointerDereference, NullablePointerAccessMember, }; @@ -84,6 +85,9 @@ public: case ReturnNullable: S.Diag(DI.Loc, diag::err_return_nullable); break; + case NullableCastNonnull: + S.Diag(DI.Loc, diag::err_nullable_cast_nonnull); + break; case NullablePointerDereference: S.Diag(DI.Loc, diag::err_nullable_pointer_dereference); break; diff --git a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td index e027a4609fc7..da4060a40f0e 100644 --- a/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td +++ b/clang/include/clang/Basic/BSC/DiagnosticBSCSemaKinds.td @@ -209,6 +209,8 @@ def err_pass_nullable_argument : Error< "cannot pass nullable pointer argument">; def err_return_nullable : Error< "cannot return nullable pointer type">; +def err_nullable_cast_nonnull : Error< + "cannot cast nullable pointer to nonnull type">; def err_nullable_pointer_access_member : Error< "cannot access member througn nullable pointer">; def err_nonnull_assigned_by_nullable : Error< diff --git a/clang/lib/AST/BSC/ExprBSC.cpp b/clang/lib/AST/BSC/ExprBSC.cpp index 61ab018bcd62..339d2cb1b3ca 100644 --- a/clang/lib/AST/BSC/ExprBSC.cpp +++ b/clang/lib/AST/BSC/ExprBSC.cpp @@ -18,4 +18,26 @@ using namespace clang; +/// If current expr is equal to 0, return true. +/// Such as : nullptr, 0, (void*)0, ((void*)0), (int*)0, +/// (int* borrow)(void*)0, (int* owned)(void*)0, +/// also include constant value which equals to 0. +bool Expr::isNullExpr(ASTContext &Ctx) const { + if (getType()->isNullPtrType()) { + return true; + } else if (const IntegerLiteral *IL = dyn_cast(this)) { + if (IL->getValue().getZExtValue() == 0) + return true; + } else if (const CStyleCastExpr *CSCE = dyn_cast(this)) { + return CSCE->getSubExpr()->isNullExpr(Ctx); + } else if (const ImplicitCastExpr *ICE = dyn_cast(this)) { + return ICE->getSubExpr()->isNullExpr(Ctx); + } else if (const ParenExpr *PE = dyn_cast(this)) { + return PE->getSubExpr()->isNullExpr(Ctx); + } else if (Optional I = this->getIntegerConstantExpr(Ctx)) { + if (*I == 0) + return true; + } + return false; +} #endif // ENABLE_BSC \ No newline at end of file diff --git a/clang/lib/Analysis/BSCNullabilityCheck.cpp b/clang/lib/Analysis/BSCNullabilityCheck.cpp index e2569721cf38..fac9bc2c1bfe 100644 --- a/clang/lib/Analysis/BSCNullabilityCheck.cpp +++ b/clang/lib/Analysis/BSCNullabilityCheck.cpp @@ -121,6 +121,7 @@ public: void VisitMemberExpr(MemberExpr *ME); void VisitCallExpr(CallExpr *CE); void VisitReturnStmt(ReturnStmt *RS); + void VisitCStyleCastExpr(CStyleCastExpr *CSCE); NullabilityKind getExprPathNullability(Expr *E); void PassConditionStatusToSuccBlocks(Stmt *Cond); }; @@ -180,27 +181,15 @@ static MemberExpr *getMemberExprFromExpr(Expr *E) { return nullptr; } -// If current expr is equal to 0, return true. -// Such as : nullptr, 0, (void*)0, ((void*)0), (int*)0, -// (int* borrow)(void*)0, (int* owned)(void*)0, -// also include constant value which equals to 0 -static bool isNullExpr(const Expr *E, ASTContext &Ctx) { - if (isa(E)) { - return true; - } else if (const IntegerLiteral *IL = dyn_cast(E)) { - if (IL->getValue().getZExtValue() == 0) - return true; - } else if (const CStyleCastExpr *CSCE = dyn_cast(E)) { - return isNullExpr(CSCE->getSubExpr(), Ctx); - } else if (const ImplicitCastExpr *ICE = dyn_cast(E)) { - return isNullExpr(ICE->getSubExpr(), Ctx); - } else if (const ParenExpr *PE = dyn_cast(E)) { - return isNullExpr(PE->getSubExpr(), Ctx); - } else if (Optional I = E->getIntegerConstantExpr(Ctx)) { - if (*I == 0) - return true; +static CallExpr *getCallExprFromExpr(Expr *E) { + if (auto CE = dyn_cast(E)) { + return CE; + } else if (auto ICE = dyn_cast(E)) { + return getCallExprFromExpr(ICE->getSubExpr()); + } else if (auto PE = dyn_cast(E)) { + return getCallExprFromExpr(PE->getSubExpr()); } - return false; + return nullptr; } // We can get PathNullability for these exprs: @@ -214,10 +203,10 @@ NullabilityKind TransferFunctions::getExprPathNullability(Expr *E) { QualType QT = E->getType(); QualType CanQT = QT.getCanonicalType(); if (CanQT->isPointerType()) { - if (isNullExpr(E, Ctx)) + if (E->isNullExpr(Ctx)) return NullabilityKind::Nullable; - if (isa(E)) - return getDefNullability(QT, Ctx); + if (auto CE = getCallExprFromExpr(E)) + return getDefNullability(CE->getType(), Ctx); if (auto CSCE = dyn_cast(E)) return getDefNullability(CSCE->getTypeAsWritten(), Ctx); if (auto UO = dyn_cast(E)) { @@ -429,6 +418,19 @@ void TransferFunctions::VisitMemberExpr(MemberExpr *ME) { } } +// (int *_Nonnull)p, (int *borrow)p, (int *owned)p is not allowed +// when p has nullable PathNullability. +void TransferFunctions::VisitCStyleCastExpr(CStyleCastExpr *CSCE) { + if (getDefNullability(CSCE->getTypeAsWritten(), Ctx) == NullabilityKind::NonNull) { + if (getExprPathNullability(CSCE->getSubExpr()) == NullabilityKind::Nullable && + ShouldReportNullPtrError(CSCE)) { + NullabilityCheckDiagInfo DI(CSCE->getBeginLoc(), + NullableCastNonnull); + Reporter.addDiagInfo(DI); + } + } +} + // If function return type is NonNull, cannot return pointer // which has nullable PathNullability. void TransferFunctions::VisitReturnStmt(ReturnStmt *RS) { @@ -462,7 +464,7 @@ void TransferFunctions::PassConditionStatusToSuccBlocks(Stmt *Cond) { // Condition expr is BinaryOperator, such as: // if (p != nullptr), if (s.p != nullptr), if (p == nullptr), if (s.p != // nullptr), ... - if (BO->isEqualityOp() && isNullExpr(BO->getRHS(), Ctx)) { + if (BO->isEqualityOp() && BO->getRHS()->isNullExpr(Ctx)) { Expr *LHS = BO->getLHS(); if (VarDecl *VD = getVarDeclFromExpr(LHS)) { if (getDefNullability(VD->getType(), Ctx) == diff --git a/clang/lib/Parse/ParseDecl.cpp b/clang/lib/Parse/ParseDecl.cpp index 37d5cf5efec4..3faa4ceba9ca 100644 --- a/clang/lib/Parse/ParseDecl.cpp +++ b/clang/lib/Parse/ParseDecl.cpp @@ -939,7 +939,7 @@ void Parser::ParseNullabilityTypeSpecifiers(ParsedAttributes &attrs) { case tok::kw__Null_unspecified: { IdentifierInfo *AttrName = Tok.getIdentifierInfo(); SourceLocation AttrNameLoc = ConsumeToken(); - if (!getLangOpts().ObjC) + if (!getLangOpts().ObjC && !getLangOpts().BSC) Diag(AttrNameLoc, diag::ext_nullability) << AttrName; attrs.addNew(AttrName, AttrNameLoc, nullptr, AttrNameLoc, nullptr, 0, diff --git a/clang/test/BSC/Negative/NullabilityCheck/type_cast.cbs b/clang/test/BSC/Negative/NullabilityCheck/type_cast.cbs new file mode 100644 index 000000000000..4c0bcde9ac93 --- /dev/null +++ b/clang/test/BSC/Negative/NullabilityCheck/type_cast.cbs @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -nullability-check=all -verify %s + +safe int *borrow return_and_take_nonnull(int *borrow a) { + unsafe { + int *borrow _Nullable p = nullptr; + return (int *borrow)p; // expected-error {{cannot cast nullable pointer to nonnull type}} + } +} + +safe void test1(void) { + unsafe { + int a = 10; + int *borrow _Nullable p = &mut a; + return_and_take_nonnull((int *borrow)p); + int *borrow k1 = (int *borrow)p; + p = nullptr; + return_and_take_nonnull((int *borrow)p); // expected-error {{cannot cast nullable pointer to nonnull type}} + int *borrow k2 = (int *borrow)p; // expected-error {{cannot cast nullable pointer to nonnull type}} + } +} + +void *get_nullable(); +void free_nonnull(int *_Nonnull p); +safe void test2(void) { + unsafe { + int *_Nonnull p1 = (int *_Nonnull)get_nullable(); // expected-error {{cannot cast nullable pointer to nonnull type}} + + int *p = get_nullable(); //implicit cast void* to int* + *p = 10; // expected-error {{nullable pointer cannot be dereferenced}} + if (p != nullptr) { + *p = 20; + } + free_nonnull((int *_Nonnull)p); // expected-error {{cannot cast nullable pointer to nonnull type}} + } +} \ No newline at end of file -- Gitee From 68c94ab6b6ac6d56f177c6131eaf5521a6035e0f Mon Sep 17 00:00:00 2001 From: liuxinyi Date: Thu, 19 Dec 2024 10:16:22 +0800 Subject: [PATCH 2/4] bugfix of lose safe specifier when instantiating a safe function --- clang/lib/Sema/SemaTemplateInstantiateDecl.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index bdbfe7ce0e0a..d065f753f948 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2127,6 +2127,10 @@ Decl *TemplateDeclInstantiator::VisitFunctionDecl( D->isInlineSpecified(), D->hasWrittenPrototype(), D->getConstexprKind(), TrailingRequiresClause); Function->setRangeEnd(D->getSourceRange().getEnd()); +#if ENABLE_BSC + if (SemaRef.getLangOpts().BSC) + Function->setSafeZoneSpecifier(D->getSafeZoneSpecifier()); +#endif } if (D->isInlined()) -- Gitee From cbe12cb83fc2297f77ad16f07e91b8f790458b36 Mon Sep 17 00:00:00 2001 From: liuxinyi Date: Thu, 19 Dec 2024 10:47:10 +0800 Subject: [PATCH 3/4] [stdcbs]add safe API forget to forget unmoved owned variable without calling its destructor --- clang/docs/BiShengCLanguageUserManual.md | 32 +++++++++++++++++ .../generic_func_and_struct.cbs | 15 +++++--- .../NullabilityCheck/init_nullptr.cbs | 25 +++++++++---- .../cast_to_void_pointer_owned.cbs | 9 ++++- .../src/bishengc_safety/bishengc_safety.hbs | 35 ++++++++++++------- libcbs/src/option/option.hbs | 8 ++--- libcbs/src/result/result.hbs | 16 ++++----- .../bishengc_safety/bishengc_safety_test.cbs | 17 +++++++++ 8 files changed, 119 insertions(+), 38 deletions(-) diff --git a/clang/docs/BiShengCLanguageUserManual.md b/clang/docs/BiShengCLanguageUserManual.md index ea0ae367622c..11370960eff8 100644 --- a/clang/docs/BiShengCLanguageUserManual.md +++ b/clang/docs/BiShengCLanguageUserManual.md @@ -4435,6 +4435,38 @@ safe void example(void) { } ``` +#### `forget` + +`forget` 主要用于获取变量的所有权并且“忘记”它,该函数是一个泛型函数,接收一个类型为泛型类型`T`的变量,表示要“忘记”的值: +1. 如果该变量是 owned 指针,那么该指针指向的内存不会被释放; +2. 如果该变量是 owned struct 类型,那么不会调用其析构函数。 + +在一些特殊的场景,用户希望取得变量的所有权而不通过该变量来释放管理的底层资源(如堆内存或文件句柄,这些资源可能已经通过裸指针操作被转移或释放),例如: + +```c +#include "bishengc_safety.hbs" +#include +owned struct Resource { +public: + char *owned s; + ~Resource(This this) { + safe_free((void *owned)this.s); + } +}; + +void get_resource(char* val) { + Resource r = { .s = safe_malloc(100) }; + memcpy(val, (const void *)&r, sizeof(Resource)); // Resource中的资源被转移 + forget(r); //此时 forget 函数会获取 r 的所有权,但是并不会调用 Resource 的析构函数来释放堆内存 +} + +int main() { + char val[sizeof(Resource)]; + get_resource(val); + return 0; +} +``` + ### 安全数据结构 #### `Vec` diff --git a/clang/test/BSC/Positive/NullabilityCheck/generic_func_and_struct.cbs b/clang/test/BSC/Positive/NullabilityCheck/generic_func_and_struct.cbs index 00a8412a74dc..e11d7591529a 100644 --- a/clang/test/BSC/Positive/NullabilityCheck/generic_func_and_struct.cbs +++ b/clang/test/BSC/Positive/NullabilityCheck/generic_func_and_struct.cbs @@ -6,11 +6,18 @@ // expected-no-diagnostics #include -safe T *owned safe_malloc(T a) { +safe T *owned safe_malloc(T t) { unsafe { - T *owned p = (T *owned)malloc(sizeof(T)); - *p = a; - return p; + T *addr = (T *)malloc(sizeof(T)); + if (!addr) { + addr = &t; + T* forget = &t; + *forget = t; + exit(EXIT_FAILURE); + } else { + *addr = t; + } + return (T *owned)addr; } } safe void use(T *borrow p) {} diff --git a/clang/test/BSC/Positive/NullabilityCheck/init_nullptr.cbs b/clang/test/BSC/Positive/NullabilityCheck/init_nullptr.cbs index f68c7669dc6c..9d0105ac9a97 100644 --- a/clang/test/BSC/Positive/NullabilityCheck/init_nullptr.cbs +++ b/clang/test/BSC/Positive/NullabilityCheck/init_nullptr.cbs @@ -6,22 +6,35 @@ // expected-no-diagnostics #include +safe T *owned safe_malloc(T t) { + unsafe { + T *addr = (T *)malloc(sizeof(T)); + if (!addr) { + addr = &t; + T* forget = &t; + *forget = t; + exit(EXIT_FAILURE); + } else { + *addr = t; + } + return (T *owned)addr; + } +} + safe int get_current_status(void) { return 1; } safe int *owned get_nonnull(int a) { - unsafe { - int *owned p = (int *owned)malloc(sizeof(int)); - *p = a; - return p; - } + int *owned p = safe_malloc(5); + *p = a; + return p; } safe int *owned _Nullable get_nullable(int a) { if (get_current_status > 0) { unsafe { - int *owned p = (int *owned)malloc(sizeof(int)); + int *owned p = safe_malloc(5); *p = a; return p; } diff --git a/clang/test/BSC/Positive/SafeZone/cast_to_void_pointer_owned/cast_to_void_pointer_owned.cbs b/clang/test/BSC/Positive/SafeZone/cast_to_void_pointer_owned/cast_to_void_pointer_owned.cbs index 209d0223900f..484c038cb179 100644 --- a/clang/test/BSC/Positive/SafeZone/cast_to_void_pointer_owned/cast_to_void_pointer_owned.cbs +++ b/clang/test/BSC/Positive/SafeZone/cast_to_void_pointer_owned/cast_to_void_pointer_owned.cbs @@ -7,7 +7,14 @@ safe T *owned safe_malloc(T t) { unsafe { T *addr = (T *)malloc(sizeof(T)); - *addr = t; + if (!addr) { + addr = &t; + T* forget = &t; + *forget = t; + exit(EXIT_FAILURE); + } else { + *addr = t; + } return (T *owned)addr; } } diff --git a/libcbs/src/bishengc_safety/bishengc_safety.hbs b/libcbs/src/bishengc_safety/bishengc_safety.hbs index eca490739845..0c54a0d0f4e2 100644 --- a/libcbs/src/bishengc_safety/bishengc_safety.hbs +++ b/libcbs/src/bishengc_safety/bishengc_safety.hbs @@ -25,23 +25,34 @@ safe void bsc_refcell_immut_borrow_failed(void); safe void safe_free(void * owned p); +// Takes ownership and “forgets” about the value without running its destructor. +safe void forget(T t) { + unsafe { + T* forget = &t; + *forget = t; + } +} + safe T *owned safe_malloc(T t) { - unsafe { - T *addr = (T *)malloc(sizeof(T)); - if (!addr) { - bsc_bad_alloc_handler(sizeof(T)); - } - *addr = t; - return (T *owned)addr; - } + unsafe { + T *addr = (T *)malloc(sizeof(T)); + if (!addr) { + addr = &t; + forget(t); + bsc_bad_alloc_handler(sizeof(T)); + } else { + *addr = t; + } + return (T *owned)addr; + } } safe void safe_swap(T* borrow left, T* borrow right) { - unsafe { - T tmp = *(T *)left; - *left = *(T *)right; + unsafe { + T tmp = *(T *_Nonnull)left; + *left = *(T *_Nonnull)right; *right = tmp; - } + } } #endif diff --git a/libcbs/src/option/option.hbs b/libcbs/src/option/option.hbs index f61eea580173..28d3b0add1c5 100644 --- a/libcbs/src/option/option.hbs +++ b/libcbs/src/option/option.hbs @@ -55,8 +55,7 @@ safe Option Option::Some(T t) { .val = {0} }; memcpy(o.val, (const void *)&t, sizeof(T)); - T* forget = &t; // forget value `t` to avoid calling its destructor - *forget = t; + forget(t); // forget value `t` to avoid calling its destructor return o; } } @@ -84,10 +83,9 @@ safe T option_unwrap(Option option) { bsc_unwrap_failed(); } unsafe { - T* ptr = (T *)option.val; + T* ptr = (T *_Nonnull)option.val; T t = *ptr; - Option* forget = &option; // forget value `option` to avoid calling its destructor - *forget = option; + forget>(option); // forget value `option` to avoid calling its destructor return t; } } diff --git a/libcbs/src/result/result.hbs b/libcbs/src/result/result.hbs index a8313d730a0e..91ef47be6853 100644 --- a/libcbs/src/result/result.hbs +++ b/libcbs/src/result/result.hbs @@ -66,8 +66,7 @@ safe Result Result::Ok(T t) { .e = {0} }; memcpy(r.t, (const void *)&t, sizeof(T)); - T* forget = &t; // forget value `t` to avoid calling its destructor - *forget = t; + forget(t); // forget value `t` to avoid calling its destructor return r; } } @@ -89,8 +88,7 @@ safe Result Result::Err(E e) { .e = {0} }; memcpy(r.e, (const void *)&e, sizeof(E)); - E* forget = &e; // forget value `e` to avoid calling its destructor - *forget = e; + forget(e); // forget value `e` to avoid calling its destructor return r; } } @@ -103,10 +101,9 @@ safe T result_unwrap(Result result) { bsc_unwrap_failed(); } unsafe { - T* ptr = (T *)result.t; + T* ptr = (T *_Nonnull)result.t; T t = *ptr; - Result* forget = &result; // forget value `result` to avoid calling its destructor - *forget = result; + forget>(result); // forget value `result` to avoid calling its destructor return t; } } @@ -119,10 +116,9 @@ safe E result_unwrap_err(Result result) { bsc_unwrap_failed(); } unsafe { - E* ptr = (E *)result.e; + E* ptr = (E *_Nonnull)result.e; E e = *ptr; - Result* forget = &result; // forget value `result` to avoid calling its destructor - *forget = result; + forget>(result); // forget value `result` to avoid calling its destructor return e; } } diff --git a/libcbs/test/bishengc_safety/bishengc_safety_test.cbs b/libcbs/test/bishengc_safety/bishengc_safety_test.cbs index a1a697a5b623..a54e3838765d 100644 --- a/libcbs/test/bishengc_safety/bishengc_safety_test.cbs +++ b/libcbs/test/bishengc_safety/bishengc_safety_test.cbs @@ -19,7 +19,24 @@ void test1() { safe_free((void* owned)sp); } +owned struct Resource { +public: + int * owned value; + ~Resource(This this) { + safe_free((void * owned)this.value); + } +}; + +safe void test2(void) { + Resource r = { .value = safe_malloc(1) }; + forget(r); + + int *owned p = safe_malloc(1); + forget(p); +} + int main() { test1(); + test2(); return 0; } -- Gitee From 1dab3d95413e8fe819ff6bd09be36c0f3b0bc49d Mon Sep 17 00:00:00 2001 From: liuxinyi Date: Thu, 19 Dec 2024 10:20:36 +0800 Subject: [PATCH 4/4] [ownership]add logic of owned pointer is assigned by nullptr 1. when owned pointer variable is assigned by nullptr, its status will be set to NULL. 2. when owned field of a struct variable is assigned by nullptr, this field will be add to SNullOwnedFields set. 3. owned pointer with NULL status can be reassigned or moved. --- .../clang/Analysis/Analyses/BSCOwnership.h | 12 +- clang/lib/Analysis/BSCOwnership.cpp | 180 +++++++++++------- clang/lib/Sema/BSC/SemaDeclBSC.cpp | 2 +- .../NullabilityCheck/owned_ptr_nullptr.cbs | 39 ++++ .../Maple/if_int_owned/if_int_owned.cbs | 19 -- .../Ownership/RuleCheck/struct/struct.cbs | 5 +- .../NullabilityCheck/owned_ptr_nullptr.cbs | 131 +++++++++++++ 7 files changed, 287 insertions(+), 101 deletions(-) create mode 100644 clang/test/BSC/Negative/NullabilityCheck/owned_ptr_nullptr.cbs delete mode 100644 clang/test/BSC/Negative/Ownership/RuleCheck/Maple/if_int_owned/if_int_owned.cbs create mode 100644 clang/test/BSC/Positive/NullabilityCheck/owned_ptr_nullptr.cbs diff --git a/clang/include/clang/Analysis/Analyses/BSCOwnership.h b/clang/include/clang/Analysis/Analyses/BSCOwnership.h index 1443a0ba1362..b42484560591 100644 --- a/clang/include/clang/Analysis/Analyses/BSCOwnership.h +++ b/clang/include/clang/Analysis/Analyses/BSCOwnership.h @@ -62,6 +62,7 @@ public: llvm::DenseMap SStatus; llvm::DenseMap SAllOwnedFields; llvm::DenseMap SOwnedOwnedFields; + llvm::DenseMap SNullOwnedFields; // basic owned pointer status, e.g. int * owned p using BOPOwnedField = llvm::SmallSet; @@ -79,7 +80,8 @@ public: void resetAll(const VarDecl *VD); void init(const VarDecl *VD); void setToOwned(const VarDecl *VD); - void setToMoved(const Expr *E); + void setToNull(const VarDecl *VD); + void setToNull(const Expr *E); llvm::SmallVector checkOPSUse(const VarDecl *VD, const SourceLocation &Loc, bool isGetAddr, @@ -138,13 +140,14 @@ public: llvm::DenseMap ss, llvm::DenseMap saof, llvm::DenseMap soof, + llvm::DenseMap snof, llvm::DenseMap bops, llvm::DenseMap bopaof, llvm::DenseMap bopoof) : OPSStatus(opss), OPSAllOwnedFields(opsaof), OPSOwnedOwnedFields(opsoof), SStatus(ss), SAllOwnedFields(saof), - SOwnedOwnedFields(soof), BOPStatus(bops), BOPAllOwnedFields(bopaof), - BOPOwnedOwnedFields(bopoof) {} + SOwnedOwnedFields(soof), SNullOwnedFields(snof), BOPStatus(bops), + BOPAllOwnedFields(bopaof), BOPOwnedOwnedFields(bopoof) {} private: void initOPS(const RecordDecl *RD, const VarDecl *VD, Source source, @@ -319,7 +322,8 @@ public: void runOwnershipAnalysis(const FunctionDecl &fd, const CFG &cfg, AnalysisDeclContext &ac, - OwnershipDiagReporter &reporter); + OwnershipDiagReporter &reporter, + ASTContext &ctx); } // end namespace clang diff --git a/clang/lib/Analysis/BSCOwnership.cpp b/clang/lib/Analysis/BSCOwnership.cpp index 0a970e3d0cd3..78916fe86575 100644 --- a/clang/lib/Analysis/BSCOwnership.cpp +++ b/clang/lib/Analysis/BSCOwnership.cpp @@ -28,6 +28,7 @@ namespace { class OwnershipImpl { public: AnalysisDeclContext &analysisContext; + ASTContext &ctx; llvm::DenseMap blocksBeginStatus; llvm::DenseMap blocksEndStatus; @@ -41,8 +42,8 @@ public: void MaybeSetNull(const CFGBlock *block, Ownership::OwnershipStatus &status); - OwnershipImpl(AnalysisDeclContext &ac) - : analysisContext(ac), blocksBeginStatus(0), blocksEndStatus(0) {} + OwnershipImpl(AnalysisDeclContext &ac, ASTContext &context) + : analysisContext(ac), ctx(context), blocksBeginStatus(0), blocksEndStatus(0) {} }; } // namespace @@ -170,8 +171,8 @@ bool Ownership::OwnershipStatus::empty() const { return OPSStatus.empty() && OPSAllOwnedFields.empty() && OPSOwnedOwnedFields.empty() && SStatus.empty() && SAllOwnedFields.empty() && SOwnedOwnedFields.empty() && - BOPStatus.empty() && BOPAllOwnedFields.empty() && - BOPOwnedOwnedFields.empty(); + SNullOwnedFields.empty() && BOPStatus.empty() && + BOPAllOwnedFields.empty() && BOPOwnedOwnedFields.empty(); } Ownership::OwnershipStatus @@ -181,6 +182,7 @@ OwnershipImpl::merge(Ownership::OwnershipStatus statsA, return Ownership::OwnershipStatus( statsB.OPSStatus, statsB.OPSAllOwnedFields, statsB.OPSOwnedOwnedFields, statsB.SStatus, statsB.SAllOwnedFields, statsB.SOwnedOwnedFields, + statsB.SNullOwnedFields, statsB.BOPStatus, statsB.BOPAllOwnedFields, statsB.BOPOwnedOwnedFields); for (auto it = statsB.OPSStatus.begin(), ei = statsB.OPSStatus.end(); @@ -223,14 +225,23 @@ OwnershipImpl::merge(Ownership::OwnershipStatus statsA, ei = statsB.SAllOwnedFields.end(); it != ei; ++it) { const VarDecl *VD = it->first; - llvm::SmallSet value = statsB.SOwnedOwnedFields[VD]; - if (statsA.SAllOwnedFields.count(VD) && !statsA.SOwnedOwnedFields.empty()) { - if (!value.empty()) { - for (auto s : value) + llvm::SmallSet OwnedValue = statsB.SOwnedOwnedFields[VD]; + llvm::SmallSet NullValue = statsB.SNullOwnedFields[VD]; + if (statsA.SAllOwnedFields.count(VD) && + (!statsA.SOwnedOwnedFields.empty() || !statsA.SNullOwnedFields.empty())) { + if (!statsA.SOwnedOwnedFields.empty() && !OwnedValue.empty()) { + for (auto s : OwnedValue) statsA.SOwnedOwnedFields[VD].insert(s); } + if (!statsA.SNullOwnedFields.empty() && !NullValue.empty()) { + for (auto s : NullValue) + statsA.SNullOwnedFields[VD].insert(s); + } } else { - statsA.SAllOwnedFields[VD] = value; + for (auto s : OwnedValue) + statsA.SAllOwnedFields[VD].insert(s); + for (auto s : NullValue) + statsA.SAllOwnedFields[VD].insert(s); } } @@ -263,6 +274,7 @@ OwnershipImpl::merge(Ownership::OwnershipStatus statsA, return Ownership::OwnershipStatus( statsA.OPSStatus, statsA.OPSAllOwnedFields, statsA.OPSOwnedOwnedFields, statsA.SStatus, statsA.SAllOwnedFields, statsA.SOwnedOwnedFields, + statsA.SNullOwnedFields, statsA.BOPStatus, statsA.BOPAllOwnedFields, statsA.BOPOwnedOwnedFields); } @@ -270,7 +282,8 @@ bool Ownership::OwnershipStatus::equals(const OwnershipStatus &V) const { return OPSStatus == V.OPSStatus && OPSAllOwnedFields == V.OPSAllOwnedFields && OPSOwnedOwnedFields == V.OPSOwnedOwnedFields && SStatus == V.SStatus && SAllOwnedFields == V.SAllOwnedFields && - SOwnedOwnedFields == V.SOwnedOwnedFields && BOPStatus == V.BOPStatus && + SOwnedOwnedFields == V.SOwnedOwnedFields && + SNullOwnedFields == V.SNullOwnedFields && BOPStatus == V.BOPStatus && BOPAllOwnedFields == V.BOPAllOwnedFields && BOPOwnedOwnedFields == V.BOPOwnedOwnedFields; } @@ -352,7 +365,7 @@ bool Ownership::OwnershipStatus::has(const VarDecl *VD, Status S) const { bool Ownership::OwnershipStatus::canAssign(const VarDecl *VD) const { unsigned uninitIndex = getIndex(Uninitialized); unsigned movedIndex = getIndex(Moved); - + unsigned nullIndex = getIndex(Null); if (OPSStatus.count(VD)) { auto it = OPSStatus.find(VD); llvm::BitVector status = it->second; @@ -363,6 +376,9 @@ bool Ownership::OwnershipStatus::canAssign(const VarDecl *VD) const { if (status.test(movedIndex)) { status.reset(movedIndex); } + if (status.test(nullIndex)) { + status.reset(nullIndex); + } return !status.any(); } @@ -376,6 +392,9 @@ bool Ownership::OwnershipStatus::canAssign(const VarDecl *VD) const { if (status.test(movedIndex)) { status.reset(movedIndex); } + if (status.test(nullIndex)) { + status.reset(nullIndex); + } return !status.any(); } @@ -486,6 +505,7 @@ void Ownership::OwnershipStatus::initS(const RecordDecl *RD, const VarDecl *VD, if (SAllOwnedFields[VD].empty()) { SAllOwnedFields[VD] = {}; SOwnedOwnedFields[VD] = {}; + SNullOwnedFields[VD] = {}; } } } @@ -503,6 +523,7 @@ void Ownership::OwnershipStatus::initS(const RecordDecl *RD, const VarDecl *VD, if (SAllOwnedFields[VD].empty()) { SAllOwnedFields[VD] = {}; SOwnedOwnedFields[VD] = {}; + SNullOwnedFields[VD] = {}; } if (!FT->isRecordType() || FT->isOwnedStructureType()) SAllOwnedFields[VD].insert(fieldName); @@ -663,19 +684,23 @@ void Ownership::OwnershipStatus::setToOwned(const VarDecl *VD) { } } -void Ownership::OwnershipStatus::setToMoved(const Expr *E) { +void Ownership::OwnershipStatus::setToNull(const VarDecl *VD) { + if (OPSStatus.count(VD)) { + OPSOwnedOwnedFields[VD].clear(); + resetAll(VD); + set(VD, Ownership::Status::Null); + } + if (BOPStatus.count(VD)) { + BOPOwnedOwnedFields[VD].clear(); + resetAll(VD); + set(VD, Ownership::Status::Null); + } +} + +void Ownership::OwnershipStatus::setToNull(const Expr *E) { if (const DeclRefExpr *DRE = dyn_cast(E)) { const VarDecl *VD = dyn_cast(DRE->getDecl()); - if (OPSStatus.count(VD)) { - OPSOwnedOwnedFields[VD].clear(); - resetAll(VD); - set(VD, Moved); - } - if (BOPStatus.count(VD)) { - BOPOwnedOwnedFields[VD].clear(); - resetAll(VD); - set(VD, Moved); - } + setToNull(VD); return; } if (const MemberExpr *ME = dyn_cast(E)) { @@ -690,27 +715,17 @@ void Ownership::OwnershipStatus::setToMoved(const Expr *E) { for (const string &str : allPrefixStrs) { OPSOwnedOwnedFields[VD].erase(str); } - if (OPSAllOwnedFields[VD].size() != OPSOwnedOwnedFields[VD].size()) { - if (!is(VD, Moved)) { - resetAll(VD); - set(VD, PartialMoved); - } - } - if (OPSOwnedOwnedFields[VD].empty()) { - if (!is(VD, Moved)) { - resetAll(VD); - set(VD, AllMoved); - } - } } } if (SStatus.count(VD)) { if (SAllOwnedFields[VD].count(memberField.second)) { SOwnedOwnedFields[VD].erase(memberField.second); + SNullOwnedFields[VD].insert(memberField.second); auto allPrefixStrs = findPrefixStrings(SAllOwnedFields[VD], memberField.second + "."); for (const string &str : allPrefixStrs) { SOwnedOwnedFields[VD].erase(str); + SNullOwnedFields[VD].insert(str); } } } @@ -1133,7 +1148,9 @@ SmallVector Ownership::OwnershipStatus::checkSUse( } } - if (SAllOwnedFields[VD].size() != SOwnedOwnedFields[VD].size() && diags.empty()) { + if (SAllOwnedFields[VD].size() - SOwnedOwnedFields[VD].size() - SNullOwnedFields[VD].size() != 0 && + SAllOwnedFields[VD].size()!= SOwnedOwnedFields[VD].size() && + diags.empty()) { diags.push_back(OwnershipDiagInfo(Loc, InvalidUseOfPartiallyMoved, VD->getNameAsString(), collectMovedFields(VD))); @@ -1863,6 +1880,10 @@ void TransferFunctions::VisitBinaryOperator(BinaryOperator *BO) { op = Assign; Visit(LHS); op = None; + + if (RHS->isNullExpr(OS.ctx)) { + stat.setToNull(LHS); + } } } @@ -1920,13 +1941,53 @@ void TransferFunctions::VisitCStyleCastExpr(CStyleCastExpr *CSCE) { void TransferFunctions::VisitDeclStmt(DeclStmt *DS) { for (Decl *D : DS->decls()) { if (VarDecl *VD = dyn_cast(D)) { - if (IsTrackedType(VD->getType())) { + QualType VQT = VD->getType().getCanonicalType(); + if (IsTrackedType(VQT)) { stat.init(VD); } if (Expr *Init = VD->getInit()) { - // if has init expr, change the status of VD from UNINIT to OWNED - stat.setToOwned(VD); - + // if has init expr, change the status of VD from UNINIT to OWNED or NULL + if (VQT->isPointerType() && VQT.isOwnedQualified()) { + if (Init->isNullExpr(OS.ctx)) { + stat.setToNull(VD); + } else { + stat.setToOwned(VD); + } + } else if (VQT->isRecordType() && VQT->hasOwnedFields() && + isa(Init)) { + stat.setToOwned(VD); + if (stat.SStatus.count(VD)) { + // If we init owned fields of a struct var with nullptr, + // for example `struct S s = { .p = nullptr };`, here + // we reset the status of s.p. + auto RD = dyn_cast(VQT)->getDecl(); + auto ILE = dyn_cast(Init); + Expr **Inits = ILE->getInits(); + for (const auto &FD : RD->fields()) { + Expr *FieldInit = Inits[FD->getFieldIndex()]; + if (FieldInit->isNullExpr(OS.ctx)) { + auto memberField = FD->getNameAsString(); + if (stat.SAllOwnedFields[VD].count(memberField)) { + stat.SOwnedOwnedFields[VD].erase(memberField); + stat.SNullOwnedFields[VD].insert(memberField); + auto allPrefixStrs = + findPrefixStrings(stat.SAllOwnedFields[VD], memberField + "."); + for (const string &str : allPrefixStrs) { + stat.SOwnedOwnedFields[VD].erase(str); + stat.SNullOwnedFields[VD].insert(str); + } + } + } + } + if (stat.SOwnedOwnedFields[VD].size() == 0) { + stat.set(VD, Ownership::Status::AllMoved); + } else if (stat.SAllOwnedFields[VD].size() != stat.SOwnedOwnedFields[VD].size()) { + stat.set(VD, Ownership::Status::PartialMoved); + } + } + } else { + stat.setToOwned(VD); + } // manipulate the init expr if it is not CallExpr bool IsCall = IsCallExpr(Init); if (!IsCall) { @@ -2116,36 +2177,6 @@ void TransferFunctions::HandleDREUse(const DeclRefExpr *DRE, } } -static bool IsNull(const Expr *E) { - if (isa(E)) - return true; - if (const ImplicitCastExpr *ICE = dyn_cast(E)) { - if (const ParenExpr *PE = dyn_cast(ICE->getSubExpr())) { - if (const CStyleCastExpr *CSCE = - dyn_cast(PE->getSubExpr())) { - if (CSCE->getCastKind() == CK_NullToPointer) { - if (const IntegerLiteral *IL = - dyn_cast(CSCE->getSubExpr())) { - return IL->getValue().getZExtValue() == 0; - } - } - } - } - } - if (const ParenExpr *PE = dyn_cast(E)) { - if (const CStyleCastExpr *CSCE = - dyn_cast(PE->getSubExpr())) { - if (CSCE->getCastKind() == CK_NullToPointer) { - if (const IntegerLiteral *IL = - dyn_cast(CSCE->getSubExpr())) { - return IL->getValue().getZExtValue() == 0; - } - } - } - } - return false; -} - void OwnershipImpl::MaybeSetNull(const CFGBlock *block, Ownership::OwnershipStatus &status) { if (block->pred_empty()) @@ -2156,14 +2187,14 @@ void OwnershipImpl::MaybeSetNull(const CFGBlock *block, } if (const IfStmt *IS = dyn_cast_or_null(pred->getTerminatorStmt())) { if (const BinaryOperator *BO = dyn_cast(IS->getCond())) { - if (IsNull(BO->getRHS())) { + if (BO->getRHS()->isNullExpr(ctx)) { if (const ImplicitCastExpr *ICE = dyn_cast(BO->getLHS())) { if (BO->getOpcode() == BO_EQ) { if (*pred->succ_begin() == block) // block is True branch. - status.setToMoved(ICE->getSubExpr()); + status.setToNull(ICE->getSubExpr()); } else if (BO->getOpcode() == BO_NE) { if (*(pred->succ_begin() + 1) == block) // block is False branch. - status.setToMoved(ICE->getSubExpr()); + status.setToNull(ICE->getSubExpr()); } } } @@ -2205,13 +2236,14 @@ OwnershipImpl::runOnBlock(const CFGBlock *block, void clang::runOwnershipAnalysis(const FunctionDecl &fd, const CFG &cfg, AnalysisDeclContext &ac, - OwnershipDiagReporter &reporter) { + OwnershipDiagReporter &reporter, + ASTContext &ctx) { // The analysis currently has scalability issues for very large CFGs. // Bail out if it looks too large. if (cfg.getNumBlockIDs() > 300000) return; - OwnershipImpl *OS = new OwnershipImpl(ac); + OwnershipImpl *OS = new OwnershipImpl(ac, ctx); // Proceed with the worklist. ForwardDataflowWorklist worklist(cfg, ac); diff --git a/clang/lib/Sema/BSC/SemaDeclBSC.cpp b/clang/lib/Sema/BSC/SemaDeclBSC.cpp index b2669218305f..06b38b53b1b0 100644 --- a/clang/lib/Sema/BSC/SemaDeclBSC.cpp +++ b/clang/lib/Sema/BSC/SemaDeclBSC.cpp @@ -220,7 +220,7 @@ void Sema::BSCDataflowAnalysis(const Decl *D, bool EnableOwnershipCheck, // function. if (EnableOwnershipCheck && !NumNullabilityCheckErrorsInCurrFD) { OwnershipDiagReporter OwnershipReporter(*this); - runOwnershipAnalysis(*FD, *AC.getCFG(), AC, OwnershipReporter); + runOwnershipAnalysis(*FD, *AC.getCFG(), AC, OwnershipReporter, Context); OwnershipReporter.flushDiagnostics(); // Run borrow check when there is no other ownership errors in current // function. diff --git a/clang/test/BSC/Negative/NullabilityCheck/owned_ptr_nullptr.cbs b/clang/test/BSC/Negative/NullabilityCheck/owned_ptr_nullptr.cbs new file mode 100644 index 000000000000..18af5f91c5a5 --- /dev/null +++ b/clang/test/BSC/Negative/NullabilityCheck/owned_ptr_nullptr.cbs @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -verify %s + +safe T *owned safe_malloc(T t); +safe void safe_free(void *owned p); + +safe void test1(void) { + int *owned _Nullable p = nullptr; + int cond = 1; + if (cond) { + p = safe_malloc(10); + *p = 10; + } + safe_free((void *owned)p); // expected-error {{cannot cast nullable pointer to nonnull type}} +} + +safe void test2(void) { + int *owned _Nullable p = nullptr; + int cond = 1; + if (cond) { + p = safe_malloc(10); + *p = 10; + } + p = nullptr; // expected-error {{assign to owned value: `p`}} +} + +safe int *owned _Nullable get_owned(int cond) { + int *owned _Nullable p = nullptr; + if (cond) { + p = safe_malloc(10); + } + return p; +} +safe void test3(void) { + int *owned _Nullable p = get_owned(5); + if (p != nullptr) { + *p = 10; + } + safe_free((void *owned)p); // expected-error {{cannot cast nullable pointer to nonnull type}} +} \ No newline at end of file diff --git a/clang/test/BSC/Negative/Ownership/RuleCheck/Maple/if_int_owned/if_int_owned.cbs b/clang/test/BSC/Negative/Ownership/RuleCheck/Maple/if_int_owned/if_int_owned.cbs deleted file mode 100644 index 127d54f7dd90..000000000000 --- a/clang/test/BSC/Negative/Ownership/RuleCheck/Maple/if_int_owned/if_int_owned.cbs +++ /dev/null @@ -1,19 +0,0 @@ -// RUN: %clang_cc1 -verify %s - -void free(void * p); - -long * owned test() { - long b = 0; - long * owned a; - a = (long * owned)&b; - return a; -} - -int main() { - const long * owned a = test(); - if (a != 0) { - free((void *)(void * owned)a); - return 1; - } - return 0; // expected-error {{memory leak of value: `a`}} -} \ No newline at end of file diff --git a/clang/test/BSC/Negative/Ownership/RuleCheck/struct/struct.cbs b/clang/test/BSC/Negative/Ownership/RuleCheck/struct/struct.cbs index a10dd3a1b24f..b8a8b162063c 100644 --- a/clang/test/BSC/Negative/Ownership/RuleCheck/struct/struct.cbs +++ b/clang/test/BSC/Negative/Ownership/RuleCheck/struct/struct.cbs @@ -144,12 +144,11 @@ void func25(struct A a, struct B b) { //--------------------------owned-->null----------------------------- void func26(struct A *owned a) { a = (struct A* owned)(void *owned)NULL; // expected-error {{assign to owned value: `a`}} -} // expected-error {{memory leak of value: `a`}} - // expected-error@-1 {{field memory leak of value: `a`, a.a1.b1, a.a1.b2, a.a1.b3, a.a1.b3.c1, a.a1.b3.c2, a.a2, a.a2.b1, a.a2.b2, a.a2.b3... are leak}} +} void func27(struct A a) { a.a2 = (struct B* owned)(void *owned)NULL; // expected-error {{assign to part of owned value: `a.a2`}} -} // expected-error {{field memory leak of value: `a`, a.a1.b1, a.a1.b2, a.a1.b3, a.a1.b3.c1, a.a1.b3.c2, a.a2, a.a2.b1, a.a2.b2, a.a2.b3... are leak}} +} // expected-error {{field memory leak of value: `a`, a.a1.b1, a.a1.b2, a.a1.b3, a.a1.b3.c1, a.a1.b3.c2, a.a3.b1, a.a3.b2, a.a3.b3, a.a3.b3.c1... are leak}} //----------------------------------uninitialized-->owned---------------------------------- void func28(struct C * owned c) { diff --git a/clang/test/BSC/Positive/NullabilityCheck/owned_ptr_nullptr.cbs b/clang/test/BSC/Positive/NullabilityCheck/owned_ptr_nullptr.cbs new file mode 100644 index 000000000000..89d2c57f8ee8 --- /dev/null +++ b/clang/test/BSC/Positive/NullabilityCheck/owned_ptr_nullptr.cbs @@ -0,0 +1,131 @@ +// RUN: %clang %s -o %t.output +// RUN: %t.output +// RUN: %clang -rewrite-bsc %s -o %t-rw.c +// RUN: %clang %t-rw.c -o %t-rw.output +// RUN: %t-rw.output +// expected-no-diagnostics + +#include +safe T *owned safe_malloc(T t) { + unsafe { + T *addr = (T *)malloc(sizeof(T)); + if (!addr) { + addr = &t; + T* forget = &t; + *forget = t; + exit(EXIT_FAILURE); + } else { + *addr = t; + } + return (T *owned)addr; + } +} + +safe void safe_free(void *owned p) { + unsafe { + free((void *)p); + } +} + +safe void test1(void) { + int *owned _Nullable p = nullptr; + int cond = 1; + if (cond) { + p = safe_malloc(10); + *p = 10; + } + if (p != nullptr) { + *p = 10; + safe_free((void *owned)p); + } +} + +safe void test2(void) { + int *owned _Nullable p = nullptr; + p = nullptr; +} + +struct S { + int *owned _Nullable p; +}; +safe void test3(void) { + struct S s = { nullptr }; + int cond = 1; + if (cond) { + s.p = safe_malloc(10); + } + if (s.p != nullptr) { + *s.p = 10; + safe_free((void *owned)s.p); + } +} + +safe void test4(void) { + struct S s = { nullptr }; + struct S *owned _Nullable p = safe_malloc(s); + if (p->p != nullptr) + safe_free((void *owned)p->p); + safe_free((void *owned)p); +} + +safe void test5(void) { + struct S s = { nullptr }; + struct S *owned _Nullable p = nullptr; + int cond = 1; + if (cond) { + p = safe_malloc(s); + } + if (p != nullptr) { + if (p->p != nullptr) { + *p->p = 10; + safe_free((void *owned)p->p); + } + } +} + +long * owned return_nonnull() { + long * owned p = safe_malloc(10); + return p; +} + +safe int test6(void) { + unsafe { + const long * owned a = return_nonnull(); + if (a != 0) { + free((void *)(void * owned)a); + return 1; + } + return 0; + } +} + +owned struct K { +public: + int *owned _Nullable p; + ~K(This this) { + if (this.p != nullptr) { + safe_free((void *owned)this.p); + } + } +}; +safe void test7(void) { + K k = { nullptr }; + int cond = 1; + if (cond) { + k.p = safe_malloc(10); + } + if (k.p != nullptr) { + *k.p = 10; + } +} + +int main() { + test1(); + test2(); + test3(); + test4(); + test5(); + test6(); + test7(); + return 0; +} \ No newline at end of file -- Gitee