Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conditional operators: infer rvalue bounds [2/n] #924

Merged
merged 3 commits into from
Nov 10, 2020
Merged
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
33 changes: 21 additions & 12 deletions clang/lib/Sema/SemaBounds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3487,20 +3487,16 @@ namespace {
Check(E->getCond(), CSS, State);

// Check the "true" arm `e2`.
// TODO: save the rvalue bounds from checking `e2`. These bounds will
// be used to determine the rvalue bounds of `e`.
CheckingState StateTrueArm;
StateTrueArm.EquivExprs = State.EquivExprs;
StateTrueArm.ObservedBounds = State.ObservedBounds;
Check(E->getTrueExpr(), CSS, StateTrueArm);
BoundsExpr *BoundsTrueArm = Check(E->getTrueExpr(), CSS, StateTrueArm);

// Check the "false" arm `e3`.
// TODO: save the rvalue bounds from checking `e3`. These bounds will
// be used to determine the rvalue bounds of `e`.
CheckingState StateFalseArm;
StateFalseArm.EquivExprs = State.EquivExprs;
StateFalseArm.ObservedBounds = State.ObservedBounds;
Check(E->getFalseExpr(), CSS, StateFalseArm);
BoundsExpr *BoundsFalseArm = Check(E->getFalseExpr(), CSS, StateFalseArm);

// TODO: handle uses of temporaries bounds in only one arm.

Expand Down Expand Up @@ -3572,12 +3568,25 @@ namespace {
if (!CreatesNewObject(E) && CheckIsNonModifying(E) &&
!EqualExprsContainsExpr(State.SameValue, E))
State.SameValue.push_back(E);

// TODO: infer correct bounds for conditional operators.
// The rvalue bounds for a conditional operator `e1 ? e2 : e3` is the
// greatest lower bound of the rvalue bounds of `e2` and the rvalue
// bounds of `e3`.
return CreateBoundsAllowedButNotComputed();

// The bounds of `e` are the greatest lower bound of the bounds of `e2`
// and the bounds of `e3`.

// If bounds expressions B1 and B2 are equivalent, the greatest lower
// bound of B1 and B2 is B1.
if (S.Context.EquivalentBounds(BoundsTrueArm, BoundsFalseArm, &State.EquivExprs))
return BoundsTrueArm;

// The greatest lower bound of bounds(any) and B is B, where B is an
// arbitrary bounds expression.
if (BoundsTrueArm->isAny())
return BoundsFalseArm;
if (BoundsFalseArm->isAny())
return BoundsTrueArm;

// If the bounds for `e2` and `e3` are not equivalent, and neither is
// bounds(any), the bounds for `e` cannot be determined.
return CreateBoundsAlwaysUnknown();
}

// Methods to infer both:
Expand Down
167 changes: 167 additions & 0 deletions clang/test/CheckedC/inferred-bounds/conditionals.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Tests of inferred bounds for expressions involving conditional operators.
// The goal is to check that the bounds are being inferred correctly.
//
// The tests have the general form:
// 1. Some C code.
// 2. A description of the inferred bounds for that C code:
// a. The expression
// b. The inferred bounds.
// The description uses AST dumps.
//
// This line is for the clang test infrastructure:
// RUN: %clang_cc1 -fcheckedc-extension -verify -fdump-inferred-bounds %s | FileCheck %s

#include <stdchecked.h>

// Bounds in each arm are equivalent
void f1(array_ptr<int> q : count(2), array_ptr<int> r : count(2)) {
// Ensure q and r produce the same value so that bounds(q, q + 2) and bounds(r, r + 2) are equivalent
q = 0, r = 0;

// Declared LHS bounds: bounds(p, p + 1)
// Initializer RHS bounds: bounds(q, q + 2)
// Conditional true arm bounds: bounds(q, q + 2)
// Conditional false arm bounds: bounds(r, r + 2)
array_ptr<int> p : count(1) = 1 ? q : r;
// CHECK: VarDecl {{.*}} p
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ConditionalOperator
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'r'
// CHECK: Declared Bounds:
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: Initializer Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: BinaryOperator {{.*}} '+'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: IntegerLiteral {{.*}} 2
}

// Bounds in each arm are equivalent
void f2(array_ptr<int> b : count(3)) {
// Declared LHS bounds: bounds(a, a + 3)
// Initializer RHS bounds: bounds(b, b + 3)
// Conditional true arm bounds: bounds(b, b + 3)
// Conditional false arm bounds: bounds(b, b + 3)
// a and b are not known to be equivalent after this statement.
array_ptr<int> a : count(3) = 1 ? b : b + 2; // expected-warning {{cannot prove declared bounds for 'a' are valid after initialization}} \
// expected-note {{(expanded) declared bounds are 'bounds(a, a + 3)'}} \
// expected-note {{(expanded) inferred bounds are 'bounds(b, b + 3)'}}
// CHECK: VarDecl {{.*}} a
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 3
// CHECK: ConditionalOperator
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'b'
// CHECK: BinaryOperator {{.*}} '+'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'b'
// CHECK: IntegerLiteral {{.*}} 2
// CHECK: Declared Bounds:
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 3
// CHECK: Initializer Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'b'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'b'
// CHECK: IntegerLiteral {{.*}} 3
}

// Bounds in one arm are bounds(any)
void f3(array_ptr<int> p : count(1), array_ptr<int> q : count(1)) {
// Ensure that p and q produce the same value so that bounds(q, q + 1) imply bounds(p, p + 1).
p = 0, q = 0;

// Target LHS bounds: bounds(p, p + 1)
// Inferred RHS bounds: bounds(q, q + 1)
// Conditional true arm bounds: bounds(q, q + 1)
// Conditional false arm bounds: bounds(any)
p = (1 ? q : 0);
// CHECK: BinaryOperator {{.*}} '='
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: ParenExpr
// CHECK: ConditionalOperator
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: ImplicitCastExpr {{.*}} <NullToPointer>
// CHECK: IntegerLiteral {{.*}} 0
// CHECK: Target Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: RHS Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: IntegerLiteral {{.*}} 1

// Target LHS bounds: bounds(p, p + 1)
// Inferred RHS bounds: bounds(q, q + 1)
// Conditional true arm bounds: bounds(any)
// Conditional false arm bounds: bounds(q, q + 1)
p = (1 ? 0 : q);
// CHECK: BinaryOperator {{.*}} '='
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: ParenExpr
// CHECK: ConditionalOperator
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ImplicitCastExpr {{.*}} <NullToPointer>
// CHECK: IntegerLiteral {{.*}} 0
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: Target Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'p'
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: RHS Bounds:
// CHECK: RangeBoundsExpr
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'q'
// CHECK: IntegerLiteral {{.*}} 1
}

// Bounds in conditional arms are not equal (and neither is bounds(any))
void f4(array_ptr<int> b : count(5), array_ptr<int> c : count(6)) {
// Declared LHS bounds: bounds(a, a + 4)
// Initializer RHS bounds: bounds(unknown)
// Conditional true arm bounds: bounds(b, b + 5)
// Conditional false arm bounds: bounds(c, c + 6)
array_ptr<int> a : count(4) = 1 ? b : c; // expected-error {{inferred bounds for 'a' are unknown after initialization}} \
// expected-note {{(expanded) declared bounds are 'bounds(a, a + 4)'}}
// CHECK: VarDecl {{.*}} a
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 4
// CHECK: ConditionalOperator
// CHECK: IntegerLiteral {{.*}} 1
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'b'
// CHECK: ImplicitCastExpr {{.*}} <LValueToRValue>
// CHECK: DeclRefExpr {{.*}} 'c'
// CHECK: Declared Bounds:
// CHECK: CountBoundsExpr {{.*}} Element
// CHECK: IntegerLiteral {{.*}} 4
// CHECK: Initializer Bounds:
// CHECK: NullaryBoundsExpr {{.*}} Invalid
}
3 changes: 1 addition & 2 deletions clang/test/CheckedCRewriter/addrof_crash.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// RUN: cconv-standalone -alltypes %s -- | FileCheck %s
// RUN: cconv-standalone -alltypes %s -- | %clang_cc1 -fno-builtin -verify -fcheckedc-extension -x c -
// expected-no-diagnostics

// No conversions expected for these two, they just shouldn't crash

Expand Down Expand Up @@ -35,7 +34,7 @@ void test3(){
int *d[1] = {&b};
// CHECK: _Ptr<int> d _Checked[1] = {&b};

int **e = &((0?c:d)[0]);
int **e = &((0?c:d)[0]); // expected-error {{expression has unknown bounds, cast to ptr<T> expects source to have bounds}}
// CHECK: _Ptr<_Ptr<int>> e = &((0?c:d)[0]);
}

Expand Down