| // Part of the Crubit project, under the Apache License v2.0 with LLVM | |
| // Exceptions. See /LICENSE for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| #include "nullability/pointer_nullability.h" | |
| #include <cassert> | |
| #include <optional> | |
| #include "absl/base/nullability.h" | |
| #include "nullability/type_nullability.h" | |
| #include "clang/AST/Type.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h" | |
| #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h" | |
| #include "clang/Analysis/FlowSensitive/Formula.h" | |
| #include "clang/Analysis/FlowSensitive/StorageLocation.h" | |
| #include "clang/Analysis/FlowSensitive/Value.h" | |
| #include "clang/Basic/LLVM.h" | |
| #include "clang/Basic/Specifiers.h" | |
| #include "llvm/ADT/StringRef.h" | |
| namespace clang::tidy::nullability { | |
| using dataflow::BoolValue; | |
| using dataflow::DataflowAnalysisContext; | |
| using dataflow::Environment; | |
| using dataflow::Formula; | |
| using dataflow::PointerValue; | |
| using dataflow::RecordStorageLocation; | |
| using dataflow::StorageLocation; | |
| using dataflow::TopBoolValue; | |
| using dataflow::Value; | |
| /// The nullness information of a pointer is represented by two properties | |
| /// which indicate if its source was nullable, and if its value is null. | |
| constexpr llvm::StringLiteral kFromNullable = "from_nullable"; | |
| constexpr llvm::StringLiteral kNull = "is_null"; | |
| absl::Nullable<PointerValue *> getRawPointerValue( | |
| absl::Nonnull<const Expr *> PointerExpr, const Environment &Env) { | |
| return Env.get<PointerValue>(*PointerExpr); | |
| } | |
| absl::Nullable<PointerValue *> getPointerValueFromSmartPointer( | |
| absl::Nullable<RecordStorageLocation *> SmartPointerLoc, | |
| const Environment &Env) { | |
| if (SmartPointerLoc == nullptr) return nullptr; | |
| return Env.get<PointerValue>(SmartPointerLoc->getSyntheticField(PtrField)); | |
| } | |
| absl::Nullable<PointerValue *> getSmartPointerValue( | |
| absl::Nonnull<const Expr *> SmartPointerExpr, const Environment &Env) { | |
| RecordStorageLocation *Loc = nullptr; | |
| if (SmartPointerExpr->isPRValue()) | |
| Loc = &Env.getResultObjectLocation(*SmartPointerExpr); | |
| else | |
| Loc = Env.get<RecordStorageLocation>(*SmartPointerExpr); | |
| return getPointerValueFromSmartPointer(Loc, Env); | |
| } | |
| absl::Nullable<dataflow::PointerValue *> getPointerValue( | |
| absl::Nonnull<const Expr *> PointerExpr, const Environment &Env) { | |
| QualType Ty = PointerExpr->getType(); | |
| if (Ty->isNullPtrType() || isSupportedRawPointerType(Ty)) | |
| return getRawPointerValue(PointerExpr, Env); | |
| return getSmartPointerValue(PointerExpr, Env); | |
| } | |
| void setSmartPointerValue(dataflow::RecordStorageLocation &SmartPointerLoc, | |
| absl::Nullable<dataflow::PointerValue *> Val, | |
| Environment &Env) { | |
| StorageLocation &PointerLoc = SmartPointerLoc.getSyntheticField(PtrField); | |
| if (Val) | |
| Env.setValue(PointerLoc, *Val); | |
| else | |
| Env.clearValue(PointerLoc); | |
| } | |
| void setSmartPointerToNull(dataflow::RecordStorageLocation &SmartPointerLoc, | |
| Environment &Env) { | |
| StorageLocation &PointerLoc = SmartPointerLoc.getSyntheticField(PtrField); | |
| Env.setValue(PointerLoc, | |
| createNullPointer(PointerLoc.getType()->getPointeeType(), Env)); | |
| } | |
| bool hasPointerNullState(const dataflow::PointerValue &PointerVal) { | |
| return PointerVal.getProperty(kFromNullable) != nullptr && | |
| PointerVal.getProperty(kNull) != nullptr; | |
| } | |
| PointerNullState getPointerNullState(const PointerValue &PointerVal) { | |
| Value *FromNullableProp = PointerVal.getProperty(kFromNullable); | |
| Value *NullProp = PointerVal.getProperty(kNull); | |
| assert(FromNullableProp != nullptr && NullProp != nullptr && | |
| "PointerVal is missing null state!"); | |
| return { | |
| isa<TopBoolValue>(FromNullableProp) | |
| ? nullptr | |
| : &cast<BoolValue>(FromNullableProp)->formula(), | |
| isa<TopBoolValue>(NullProp) ? nullptr | |
| : &cast<BoolValue>(NullProp)->formula(), | |
| }; | |
| } | |
| static bool tryCreatePointerNullState( | |
| PointerValue &PointerVal, dataflow::Arena &A, | |
| absl::Nullable<const Formula *> FromNullable = nullptr, | |
| absl::Nullable<const Formula *> IsNull = nullptr) { | |
| if (hasPointerNullState(PointerVal)) return false; | |
| if (!FromNullable) FromNullable = &A.makeAtomRef(A.makeAtom()); | |
| if (!IsNull) IsNull = &A.makeAtomRef(A.makeAtom()); | |
| PointerVal.setProperty(kFromNullable, A.makeBoolValue(*FromNullable)); | |
| PointerVal.setProperty(kNull, A.makeBoolValue(*IsNull)); | |
| return true; | |
| } | |
| void initPointerNullState(PointerValue &PointerVal, | |
| DataflowAnalysisContext &Ctx, | |
| std::optional<PointerTypeNullability> Source) { | |
| auto &A = Ctx.arena(); | |
| if (tryCreatePointerNullState( | |
| PointerVal, A, Source ? &Source->isNullable(A) : nullptr, | |
| Source == NullabilityKind::NonNull ? &A.makeLiteral(false) | |
| : nullptr)) { | |
| // The `isSymbolic()` check is not needed for correctness, but it avoids | |
| // adding meaningless (false => !null) or (true => true) invariant clauses. | |
| // TODO: remove this once such clauses are recognized and dropped. | |
| if (Source && Source->isSymbolic()) { | |
| if (const Formula *IsNull = getPointerNullState(PointerVal).IsNull) | |
| Ctx.addInvariant( | |
| A.makeImplies(Source->isNonnull(A), A.makeNot(*IsNull))); | |
| } | |
| } | |
| } | |
| void initPointerNullState(PointerValue &PointerVal, | |
| DataflowAnalysisContext &Ctx, | |
| PointerNullState State) { | |
| assert(!hasPointerNullState(PointerVal)); | |
| auto &A = Ctx.arena(); | |
| // Internally, we encode "top" as `TopBoolValue`. | |
| BoolValue &FromNullable = State.FromNullable != nullptr | |
| ? A.makeBoolValue(*State.FromNullable) | |
| : A.makeTopValue(); | |
| BoolValue &IsNull = State.IsNull != nullptr ? A.makeBoolValue(*State.IsNull) | |
| : A.makeTopValue(); | |
| PointerVal.setProperty(kFromNullable, FromNullable); | |
| PointerVal.setProperty(kNull, IsNull); | |
| } | |
| void initNullPointer(PointerValue &PointerVal, DataflowAnalysisContext &Ctx) { | |
| tryCreatePointerNullState(PointerVal, Ctx.arena(), | |
| /*FromNullable=*/&Ctx.arena().makeLiteral(true), | |
| /*IsNull=*/&Ctx.arena().makeLiteral(true)); | |
| } | |
| PointerValue &createNullPointer(QualType PointeeType, Environment &Env) { | |
| PointerValue &PointerVal = Env.getOrCreateNullPointerValue(PointeeType); | |
| initNullPointer(PointerVal, Env.getDataflowAnalysisContext()); | |
| return PointerVal; | |
| } | |
| bool isNullable( | |
| const PointerValue &PointerVal, const Environment &Env, | |
| absl::Nullable<const dataflow::Formula *> AdditionalConstraints) { | |
| auto &A = Env.getDataflowAnalysisContext().arena(); | |
| auto [FromNullable, Null] = getPointerNullState(PointerVal); | |
| // A value is nullable if either of two things is true: | |
| // - The value is provably null under satisfiable flow conditions | |
| // (a value from an unknown source becomes nullable if provably null) | |
| // - We got it from a nullable source and it may be actually null | |
| // (if a value from a nullable source was checked, it's not nullable) | |
| // | |
| // Notably, a value from an unknown source that may be null, but is not | |
| // provably null, is not considered nullable. Values from non-null sources are | |
| // never considered nullable because being able to prove them null can only | |
| // occur under unsatisfiable flow conditions. | |
| if (Null) { | |
| const Formula *ProvablyNull = Null; | |
| if (AdditionalConstraints) | |
| ProvablyNull = &A.makeImplies(*AdditionalConstraints, *ProvablyNull); | |
| if (Env.proves(*ProvablyNull)) { | |
| // If we're in an environment with false flow conditions, we can prove | |
| // anything, but don't want to consider this value Nullable and end up | |
| // producing diagnostics in unreachable code. | |
| if (Env.proves(A.makeLiteral(false))) return false; | |
| return true; | |
| } | |
| } | |
| const Formula *NullableAndMaybeNull = &A.makeLiteral(true); | |
| if (FromNullable) | |
| NullableAndMaybeNull = &A.makeAnd(*NullableAndMaybeNull, *FromNullable); | |
| if (Null) NullableAndMaybeNull = &A.makeAnd(*NullableAndMaybeNull, *Null); | |
| if (AdditionalConstraints) | |
| NullableAndMaybeNull = | |
| &A.makeAnd(*NullableAndMaybeNull, *AdditionalConstraints); | |
| return Env.allows(*NullableAndMaybeNull); | |
| } | |
| NullabilityKind getNullability( | |
| const dataflow::PointerValue &PointerVal, const dataflow::Environment &Env, | |
| absl::Nullable<const dataflow::Formula *> AdditionalConstraints) { | |
| auto &A = Env.getDataflowAnalysisContext().arena(); | |
| if (auto *Null = getPointerNullState(PointerVal).IsNull) { | |
| if (AdditionalConstraints) Null = &A.makeAnd(*AdditionalConstraints, *Null); | |
| if (Env.proves(A.makeNot(*Null))) return NullabilityKind::NonNull; | |
| } | |
| return isNullable(PointerVal, Env, AdditionalConstraints) | |
| ? NullabilityKind::Nullable | |
| : NullabilityKind::Unspecified; | |
| } | |
| NullabilityKind getNullability( | |
| absl::Nonnull<const Expr *> E, const dataflow::Environment &Env, | |
| absl::Nullable<const dataflow::Formula *> AdditionalConstraints) { | |
| if (dataflow::PointerValue *P = getPointerValue(E, Env)) | |
| return getNullability(*P, Env, AdditionalConstraints); | |
| return clang::NullabilityKind::Unspecified; | |
| } | |
| } // namespace clang::tidy::nullability |