Skip to content

Commit d1e2a9a

Browse files
committed
[Clang] Diagnose down casting static_cast
Invalid down casts are a source of vulnerabilities. Its allowance in static_cast - which is otherwise relatively safe - are recognized as dangerous by various safety guidelines such as the core guidelines and misra. While there exist a clang-tidy check, it seems reasonable, in the interest of safety to warn about that construct in clang and to propose its deprecation in WG21. This is inspired by [p3081r0](https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3081r0.pdf) which propose to promote static_cast to dynamic_cast silently, which would have further issues (suddently static_cast could produce a null pointer). Part of the goal of this PR is therefore to demonstrate the viability of deprecating this construct.
1 parent bbbaeb5 commit d1e2a9a

File tree

9 files changed

+48
-23
lines changed

9 files changed

+48
-23
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,6 +593,10 @@ Improvements to Clang's diagnostics
593593
- Clang now supports using alias templates in deduction guides, aligning with the C++ standard,
594594
which treats alias templates as synonyms for their underlying types (#GH54909).
595595

596+
- Added ``-Wstatic-downcast`` to diagnose potentially dangerous uses of ``static_cast`` that perform a base-to-derived cast
597+
on pointers and references of polymorphic types.
598+
599+
596600
Improvements to Clang's time-trace
597601
----------------------------------
598602

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7982,6 +7982,9 @@ def err_bad_reinterpret_cast_reference : Error<
79827982
def warn_undefined_reinterpret_cast : Warning<
79837983
"reinterpret_cast from %0 to %1 has undefined behavior">,
79847984
InGroup<UndefinedReinterpretCast>, DefaultIgnore;
7985+
def warn_static_downcast : Warning<
7986+
"static downcast from %0 to %1 is potentially dangerous%select{|; did you mean 'dynamic_cast'?}2">,
7987+
InGroup<DiagGroup<"static-downcast">>;
79857988

79867989
// These messages don't adhere to the pattern.
79877990
// FIXME: Display the path somehow better.

clang/lib/Sema/SemaCast.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,16 @@ TryStaticDowncast(Sema &Self, CanQualType SrcType, CanQualType DestType,
17591759

17601760
Self.BuildBasePathArray(Paths, BasePath);
17611761
Kind = CK_BaseToDerived;
1762+
1763+
if (!CStyle && Self.LangOpts.CPlusPlus && SrcType->getAsCXXRecordDecl()->isPolymorphic()) {
1764+
auto D = Self.Diag(OpRange.getBegin(), diag::warn_static_downcast)
1765+
<< SrcType << DestType
1766+
<< OpRange
1767+
<< Self.LangOpts.RTTI;
1768+
if(Self.LangOpts.RTTI)
1769+
D << FixItHint::CreateReplacement(OpRange.getBegin(), "dynamic_cast");
1770+
}
1771+
17621772
return TC_Success;
17631773
}
17641774

clang/test/Analysis/ArrayDelete.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ void sink(Base *b) {
2121
}
2222

2323
void sink_cast(Base *b) {
24-
delete[] static_cast<Derived*>(b); // no-warning
24+
delete[] static_cast<Derived*>(b); // expected-warning {{static downcast from 'Base' to 'Derived'}}
2525
}
2626

2727
void sink_derived(Derived *d) {
@@ -62,7 +62,7 @@ void safe_function() {
6262
delete[] d; // no-warning
6363

6464
Base *b = new Derived[10];
65-
delete[] static_cast<Derived*>(b); // no-warning
65+
delete[] static_cast<Derived*>(b); // expected-warning {{static downcast from 'Base' to 'Derived'}}
6666

6767
Base *sb = new Derived[10];
6868
sink_cast(sb); // no-warning
@@ -77,7 +77,8 @@ void multiple_derived() {
7777
// expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}}
7878

7979
Base *b2 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
80-
Derived *d2 = static_cast<Derived*>(b2); // expected-note{{Casting from 'Base' to 'Derived' here}}
80+
Derived *d2 = static_cast<Derived*>(b2); // expected-note{{Casting from 'Base' to 'Derived' here}} \
81+
// expected-warning {{static downcast from 'Base' to 'Derived'}}
8182
delete[] d2; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}}
8283
// expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}}
8384

@@ -87,12 +88,13 @@ void multiple_derived() {
8788
// expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Base' is undefined}}
8889

8990
Base *b4 = new DoubleDerived[10];
90-
Derived *d4 = static_cast<Derived*>(b4);
91-
DoubleDerived *dd4 = static_cast<DoubleDerived*>(d4);
91+
Derived *d4 = static_cast<Derived*>(b4); // expected-warning {{static downcast from 'Base' to 'Derived'}}
92+
DoubleDerived *dd4 = static_cast<DoubleDerived*>(d4); // expected-warning {{static downcast from 'Derived' to 'DoubleDerived'}}
9293
delete[] dd4; // no-warning
9394

9495
Base *b5 = new DoubleDerived[10]; // expected-note{{Casting from 'DoubleDerived' to 'Base' here}}
95-
DoubleDerived *dd5 = static_cast<DoubleDerived*>(b5); // expected-note{{Casting from 'Base' to 'DoubleDerived' here}}
96+
DoubleDerived *dd5 = static_cast<DoubleDerived*>(b5); // expected-note{{Casting from 'Base' to 'DoubleDerived' here}} \
97+
// expected-warning {{static downcast from 'Base' to 'DoubleDerived'}}
9698
Derived *d5 = dd5; // expected-note{{Casting from 'DoubleDerived' to 'Derived' here}}
9799
delete[] d5; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}}
98100
// expected-note@-1{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}}
@@ -103,7 +105,8 @@ void unrelated_casts() {
103105
Base &b2 = *b; // no-note: See the FIXME.
104106

105107
// FIXME: Displaying casts of reference types is not supported.
106-
Derived &d2 = static_cast<Derived&>(b2); // no-note: See the FIXME.
108+
Derived &d2 = static_cast<Derived&>(b2); // expected-warning {{static downcast from 'Base' to 'Derived'}}
109+
// no-note: See the FIXME.
107110

108111
Derived *d = &d2; // no-note: See the FIXME.
109112
delete[] d; // expected-warning{{Deleting an array of 'DoubleDerived' objects as their base class 'Derived' is undefined}}

clang/test/Analysis/Checkers/WebKit/call-args-safe-functions.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.webkit.UncountedCallArgsChecker -verify %s
2-
// expected-no-diagnostics
32

43
class Base {
54
public:
@@ -30,27 +29,30 @@ template<typename Target, typename Source>
3029
inline Target* dynamicDowncast(Source* source)
3130
{
3231
return static_cast<Target*>(source);
32+
// expected-warning@-1 {{static downcast from 'Derived' to 'SubDerived'}}
3333
}
3434

3535
template<typename Target, typename Source>
3636
inline Target* checkedDowncast(Source* source)
3737
{
3838
return static_cast<Target*>(source);
39+
// expected-warning@-1 {{static downcast from 'Derived' to 'SubDerived'}}
3940
}
4041

4142
template<typename Target, typename Source>
4243
inline Target* uncheckedDowncast(Source* source)
4344
{
4445
return static_cast<Target*>(source);
46+
// expected-warning@-1 {{static downcast from 'Derived' to 'SubDerived'}}
4547
}
4648

4749
template<typename... Types>
4850
String toString(const Types&... values);
4951

5052
void foo(OtherObject* other)
5153
{
52-
dynamicDowncast<SubDerived>(other->obj());
53-
checkedDowncast<SubDerived>(other->obj());
54-
uncheckedDowncast<SubDerived>(other->obj());
54+
dynamicDowncast<SubDerived>(other->obj()); // expected-note {{in instantiation}}
55+
checkedDowncast<SubDerived>(other->obj()); // expected-note {{in instantiation}}
56+
uncheckedDowncast<SubDerived>(other->obj()); // expected-note {{in instantiation}}
5557
toString(other->obj());
5658
}

clang/test/Analysis/Checkers/WebKit/ref-cntbl-base-virtual-dtor-templates.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ void UseDerivedClass11(DerivedClass12& obj) { obj.deref(); }
327327
void deleteBase2(BaseClass2* obj) {
328328
if (obj->isDerived())
329329
delete static_cast<DerivedClass12*>(obj);
330+
// expected-warning@-1 {{static downcast from 'BaseClass2' to 'DerivedClass12'}}
330331
else
331332
delete obj;
332333
}
@@ -356,6 +357,7 @@ void UseDerivedClass11(DerivedClass13& obj) { obj.deref(); }
356357
void BaseClass3::destory() {
357358
if (isDerived())
358359
delete static_cast<DerivedClass13*>(this);
360+
// expected-warning@-1 {{static downcast from 'BaseClass3' to 'DerivedClass13'}}
359361
else
360362
delete this;
361363
}

clang/test/Analysis/cast-to-struct.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,21 @@ void structToStruct(struct AB *P) {
4141
Base &B1 = D1;
4242
D2 = (Derived *)&B1;
4343
D2 = dynamic_cast<Derived *>(&B1);
44-
D2 = static_cast<Derived *>(&B1);
44+
D2 = static_cast<Derived *>(&B1); // expected-warning {{static downcast from 'Base' to 'Derived'}}
4545

4646
// True positives when casting from Base to Derived.
4747
Base B2;
4848
D2 = (Derived *)&B2;// expected-warning {{Casting data to a larger structure type and accessing a field can lead to memory access errors or data corruption}}
4949
D2 = dynamic_cast<Derived *>(&B2);// expected-warning {{Casting data to a larger structure type and accessing a field can lead to memory access errors or data corruption}}
50-
D2 = static_cast<Derived *>(&B2);// expected-warning {{Casting data to a larger structure type and accessing a field can lead to memory access errors or data corruption}}
50+
D2 = static_cast<Derived *>(&B2);// expected-warning {{Casting data to a larger structure type and accessing a field can lead to memory access errors or data corruption}} \
51+
// expected-warning {{static downcast from 'Base' to 'Derived'}}
5152

5253
// False negatives, cast from Base to Derived. With path sensitive analysis
5354
// these false negatives could be fixed.
5455
Base *B3 = &B2;
5556
D2 = (Derived *)B3;
5657
D2 = dynamic_cast<Derived *>(B3);
57-
D2 = static_cast<Derived *>(B3);
58+
D2 = static_cast<Derived *>(B3); // expected-warning {{static downcast from 'Base' to 'Derived'}}
5859
}
5960

6061
void intToStruct(int *P) {

clang/test/Analysis/ctor.mm

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -849,11 +849,11 @@ void test() {
849849
// constructor could have been called on an initialized piece of memory),
850850
// so no uninitialized value warning here, and these should be symbols, not
851851
// undefined values, for later comparison.
852-
glob_y = static_cast<D *>(this)->y;
853-
glob_z = static_cast<D *>(this)->z;
854-
glob_w = static_cast<D *>(this)->w;
855-
static_cast<D *>(this)->y = 2;
856-
static_cast<D *>(this)->z = 3;
857-
static_cast<D *>(this)->w = 4;
852+
glob_y = static_cast<D *>(this)->y; // expected-warning {{static downcast}}
853+
glob_z = static_cast<D *>(this)->z; // expected-warning {{static downcast}}
854+
glob_w = static_cast<D *>(this)->w; // expected-warning {{static downcast}}
855+
static_cast<D *>(this)->y = 2; // expected-warning {{static downcast}}
856+
static_cast<D *>(this)->z = 3; // expected-warning {{static downcast}}
857+
static_cast<D *>(this)->w = 4; // expected-warning {{static downcast}}
858858
}
859859
}

clang/test/SemaCXX/address-space-conversion.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,9 @@ void test_static_cast(void_ptr vp, void_ptr_1 vp1, void_ptr_2 vp2,
5656
(void)static_cast<A_ptr_2>(bp2);
5757

5858
// Well-formed downcast
59-
(void)static_cast<B_ptr>(ap);
60-
(void)static_cast<B_ptr_1>(ap1);
61-
(void)static_cast<B_ptr_2>(ap2);
59+
(void)static_cast<B_ptr>(ap); // expected-warning {{static downcast from 'A' to 'B'}}
60+
(void)static_cast<B_ptr_1>(ap1); // expected-warning {{static downcast}}
61+
(void)static_cast<B_ptr_2>(ap2); // expected-warning {{static downcast}}
6262

6363
// Well-formed cast to/from void
6464
(void)static_cast<void_ptr>(ap);

0 commit comments

Comments
 (0)