Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions nullability/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,60 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test")

package(default_applicable_licenses = ["//:license"])

cc_library(
name = "googlesql_value_nullability_lattice",
hdrs = ["googlesql_value_nullability_lattice.h"],
visibility = ["//visibility:public"],
deps = [
"@llvm-project//clang:analysis",
],
)

cc_library(
name = "googlesql_value_nullability_analysis",
srcs = ["googlesql_value_nullability_analysis.cc"],
hdrs = ["googlesql_value_nullability_analysis.h"],
visibility = ["//visibility:public"],
deps = [
":googlesql_value_nullability",
":googlesql_value_nullability_lattice",
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
"@llvm-project//clang:ast_matchers",
"@llvm-project//clang:basic",
"@llvm-project//llvm:Support",
],
)

cc_library(
name = "googlesql_value_nullability",
srcs = ["googlesql_value_nullability.cc"],
hdrs = ["googlesql_value_nullability.h"],
deps = [
"@abseil-cpp//absl/base:nullability",
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
],
)

cc_test(
name = "googlesql_value_nullability_analysis_test",
srcs = ["googlesql_value_nullability_analysis_test.cc"],
deps = [
":googlesql_value_nullability_analysis",
":googlesql_value_nullability_lattice",
"@llvm-project//clang:analysis",
"@llvm-project//clang:ast",
"@llvm-project//clang:ast_matchers",
"@llvm-project//clang:testing",
"@llvm-project//llvm:Support",
"@llvm-project//llvm:TestingAnnotations",
"@llvm-project//third-party/unittest:gmock",
"@llvm-project//third-party/unittest:gtest",
"@llvm-project//third-party/unittest:gtest_main",
],
)

cc_library(
name = "ast_helpers",
hdrs = ["ast_helpers.h"],
Expand Down
95 changes: 95 additions & 0 deletions nullability/googlesql_value_nullability.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// 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/googlesql_value_nullability.h"

#include <cassert>

#include "absl/base/nullability.h"
#include "clang/AST/DeclCXX.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysisContext.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"

namespace clang {
namespace tidy {
namespace nullability {
namespace {

static constexpr char kGoogleSqlIsNull[] = "googlesql_is_null";

} // namespace

dataflow::LatticeJoinEffect GoogleSqlValueNullState::join(
const GoogleSqlValueNullState& Other) {
if (*this == Other) return dataflow::LatticeJoinEffect::Unchanged;
if (IsNull == Other.IsNull) return dataflow::LatticeJoinEffect::Unchanged;
IsNull = nullptr;
return dataflow::LatticeJoinEffect::Changed;
}

static dataflow::StorageLocation& getOrAddGoogleSqlNullField(
dataflow::RecordStorageLocation& Loc, dataflow::Environment& Env) {
for (const auto& Entry : Loc.synthetic_fields()) {
if (Entry.getKey() == kGoogleSqlIsNull) return *Entry.getValue();
}
auto& Ctx = Loc.getType()->getAsCXXRecordDecl()->getASTContext();
auto& FieldLoc = Env.createStorageLocation(Ctx.BoolTy);
Loc.addSyntheticField(kGoogleSqlIsNull, FieldLoc);
return FieldLoc;
}

bool hasGoogleSqlValueNullState(const dataflow::RecordStorageLocation& Loc,
const dataflow::Environment& Env) {
for (const auto& Entry : Loc.synthetic_fields()) {
if (Entry.getKey() == kGoogleSqlIsNull) {
return Env.getValue(*Entry.getValue()) != nullptr;
}
}
return false;
}

GoogleSqlValueNullState getGoogleSqlValueNullState(
const dataflow::RecordStorageLocation& Loc,
const dataflow::Environment& Env) {
const dataflow::StorageLocation* FieldLoc = nullptr;
for (const auto& Entry : Loc.synthetic_fields()) {
if (Entry.getKey() == kGoogleSqlIsNull) {
FieldLoc = Entry.getValue();
break;
}
}
if (!FieldLoc) return GoogleSqlValueNullState::getTop();

auto* Val = Env.get<dataflow::BoolValue>(*FieldLoc);
if (!Val) return GoogleSqlValueNullState::getTop();

return {&Val->formula()};
}

void initGoogleSqlValueNullState(
dataflow::RecordStorageLocation& Loc, dataflow::Environment& Env,
const dataflow::Formula* absl_nullable IsNull) {
dataflow::StorageLocation& FieldLoc = getOrAddGoogleSqlNullField(Loc, Env);
auto& A = Env.arena();
assert(Env.getValue(FieldLoc) == nullptr);
Env.setValue(FieldLoc,
IsNull != nullptr ? A.makeBoolValue(*IsNull) : A.makeTopValue());
}

void setGoogleSqlValueNullState(dataflow::RecordStorageLocation& Loc,
dataflow::Environment& Env,
const dataflow::Formula* absl_nullable IsNull) {
dataflow::StorageLocation& FieldLoc = getOrAddGoogleSqlNullField(Loc, Env);
auto& A = Env.arena();
Env.setValue(FieldLoc,
IsNull != nullptr ? A.makeBoolValue(*IsNull) : A.makeTopValue());
}

} // namespace nullability
} // namespace tidy
} // namespace clang
75 changes: 75 additions & 0 deletions nullability/googlesql_value_nullability.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// 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

#ifndef CRUBIT_NULLABILITY_GOOGLESQL_VALUE_NULLABILITY_H_
#define CRUBIT_NULLABILITY_GOOGLESQL_VALUE_NULLABILITY_H_

#include "absl/base/nullability.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/DataflowLattice.h"
#include "clang/Analysis/FlowSensitive/Formula.h"

namespace clang {
namespace dataflow {
class Arena;
class RecordStorageLocation;
} // namespace dataflow

namespace tidy {
namespace nullability {

// Represents the nullability state of a `googlesql::Value` object.
//
// This state is tracked via a boolean formula stored as a synthetic field on
// the `RecordStorageLocation` representing the `googlesql::Value` instance.
struct GoogleSqlValueNullState {
// A boolean formula representing whether the value is null.
// If null, the state is unknown (equivalent to Top).
const dataflow::Formula* absl_nullable IsNull = nullptr;

bool operator==(const GoogleSqlValueNullState& Other) const {
return IsNull == Other.IsNull;
}
bool operator!=(const GoogleSqlValueNullState& Other) const {
return !(*this == Other);
}

// Returns an unknown state (Top).
static GoogleSqlValueNullState getTop() { return {}; }

// Joins this state with another state.
// If the states differ, the result is unknown (Top).
dataflow::LatticeJoinEffect join(const GoogleSqlValueNullState& Other);
};

// Returns true if the given location has a mapped GoogleSQL value null state
// in the environment.
bool hasGoogleSqlValueNullState(const dataflow::RecordStorageLocation& Loc,
const dataflow::Environment& Env);

// Retrieves the GoogleSQL value null state for the given location.
// Returns an unknown state (Top) if not found.
GoogleSqlValueNullState getGoogleSqlValueNullState(
const dataflow::RecordStorageLocation& Loc,
const dataflow::Environment& Env);

// Initializes the GoogleSQL value null state for the given location.
// This should be called when a `googlesql::Value` object is newly created
// and we need to establish its initial nullability state.
void initGoogleSqlValueNullState(dataflow::RecordStorageLocation& Loc,
dataflow::Environment& Env,
const dataflow::Formula* absl_nullable IsNull);

// Sets the GoogleSQL value null state for the given location.
// This can be used to update the state of an existing object, e.g., after
// an assignment.
void setGoogleSqlValueNullState(dataflow::RecordStorageLocation& Loc,
dataflow::Environment& Env,
const dataflow::Formula* absl_nullable IsNull);

} // namespace nullability
} // namespace tidy
} // namespace clang

#endif // CRUBIT_NULLABILITY_GOOGLESQL_VALUE_NULLABILITY_H_
Loading