Skip to content

Commit a03aef4

Browse files
authored
[Clang] [Sema] Fix incomplete C mode incompatible ExtInfo/ExtProtoInfo conversion diagnostic (#160477)
To raise -Wincompatible-function-pointer-type for extended function type information conversions in C mode, checkPointerTypesForAssignment contained an invocation of IsFunctionConversion that is quite dubious: it verifies the conversion from LHS to RHS, while we are converting from RHS to LHS. Presumably, this was done because result and argument types don't have to match exactly in C mode (compatible types is sufficient), while IsFunctionConversion checks that an exact match in function types can be obtained by transforming extended function type information. As a result of the dubious invocation, the diagnostic for incompatible extended function type changes was incomplete and would illogically disappear when other changes to the function type were involved (this was already raised in 2019 but left unsolved). Newer additions of extended function type information such as "cfi_unchecked_callee", ARM CMSE attributes, ARM SME attributes, and function effect attributes, worked around the issue by introducing either holes or additional checks which complicates the logic. Here, we solve this longstanding issue by using IsFunctionConversion to verify the extended function type information conversion in the correct direction (from RHS to LHS), after first removing other differences in the function type (argument and result type mismatch, prototype vs. no prototype) to allow IsFunctionConversion to obtain an exact match. The complications introduced for checking newer attributes are removed. Fixes #41465 Fixes #85415
1 parent 5ba3b52 commit a03aef4

File tree

5 files changed

+107
-64
lines changed

5 files changed

+107
-64
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,11 @@ Improvements to Clang's diagnostics
357357
properly being rejected when used at compile-time. It was not implemented
358358
and caused assertion failures before (#GH158471).
359359

360+
- Closed a loophole in the diagnosis of function pointer conversions changing
361+
extended function type information in C mode (#GH41465). Function conversions
362+
that were previously incorrectly accepted in case of other irrelevant
363+
conditions are now consistently diagnosed, identical to C++ mode.
364+
360365
Improvements to Clang's time-trace
361366
----------------------------------
362367

clang/include/clang/Sema/Sema.h

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10208,15 +10208,12 @@ class Sema final : public SemaBase {
1020810208
bool CStyle, bool &ObjCLifetimeConversion);
1020910209

1021010210
/// Determine whether the conversion from FromType to ToType is a valid
10211-
/// conversion that strips "noexcept" or "noreturn" or "cfi_unchecked_callee"
10212-
/// off the nested function type. This also checks if "cfi_unchecked_callee"
10213-
/// was added to the function type. If "cfi_unchecked_callee" is added and
10214-
/// `AddingCFIUncheckedCallee` is provided, it will be set to true. The same
10215-
/// thing applies for `DiscardingCFIUncheckedCallee` if the attribute is
10216-
/// discarded.
10217-
bool IsFunctionConversion(QualType FromType, QualType ToType,
10218-
bool *DiscardingCFIUncheckedCallee = nullptr,
10219-
bool *AddingCFIUncheckedCallee = nullptr) const;
10211+
/// conversion of ExtInfo/ExtProtoInfo on the nested function type.
10212+
/// More precisely, this method checks whether FromType can be transformed
10213+
/// into an exact match for ToType, by transforming its extended function
10214+
/// type information in legal manner (e.g. by strictly stripping "noreturn"
10215+
/// or "noexcept", or by stripping "noescape" for arguments).
10216+
bool IsFunctionConversion(QualType FromType, QualType ToType) const;
1022010217

1022110218
/// Same as `IsFunctionConversion`, but if this would return true, it sets
1022210219
/// `ResultTy` to `ToType`.

clang/lib/Sema/SemaExpr.cpp

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9014,24 +9014,6 @@ bool Sema::IsInvalidSMECallConversion(QualType FromType, QualType ToType) {
90149014
return FromAttributes != ToAttributes;
90159015
}
90169016

9017-
// Check if we have a conversion between incompatible cmse function pointer
9018-
// types, that is, a conversion between a function pointer with the
9019-
// cmse_nonsecure_call attribute and one without.
9020-
static bool IsInvalidCmseNSCallConversion(Sema &S, QualType FromType,
9021-
QualType ToType) {
9022-
if (const auto *ToFn =
9023-
dyn_cast<FunctionType>(S.Context.getCanonicalType(ToType))) {
9024-
if (const auto *FromFn =
9025-
dyn_cast<FunctionType>(S.Context.getCanonicalType(FromType))) {
9026-
FunctionType::ExtInfo ToEInfo = ToFn->getExtInfo();
9027-
FunctionType::ExtInfo FromEInfo = FromFn->getExtInfo();
9028-
9029-
return ToEInfo.getCmseNSCall() != FromEInfo.getCmseNSCall();
9030-
}
9031-
}
9032-
return false;
9033-
}
9034-
90359017
// checkPointerTypesForAssignment - This is a very tricky routine (despite
90369018
// being closely modeled after the C99 spec:-). The odd characteristic of this
90379019
// routine is it effectively iqnores the qualifiers on the top level pointee.
@@ -9187,18 +9169,43 @@ static AssignConvertType checkPointerTypesForAssignment(Sema &S,
91879169
return AssignConvertType::IncompatibleFunctionPointer;
91889170
return AssignConvertType::IncompatiblePointer;
91899171
}
9190-
bool DiscardingCFIUncheckedCallee, AddingCFIUncheckedCallee;
9191-
if (!S.getLangOpts().CPlusPlus &&
9192-
S.IsFunctionConversion(ltrans, rtrans, &DiscardingCFIUncheckedCallee,
9193-
&AddingCFIUncheckedCallee)) {
9194-
// Allow conversions between CFIUncheckedCallee-ness.
9195-
if (!DiscardingCFIUncheckedCallee && !AddingCFIUncheckedCallee)
9172+
// Note: in C++, typesAreCompatible(ltrans, rtrans) will have guaranteed
9173+
// hasSameType, so we can skip further checks.
9174+
const auto *LFT = ltrans->getAs<FunctionType>();
9175+
const auto *RFT = rtrans->getAs<FunctionType>();
9176+
if (!S.getLangOpts().CPlusPlus && LFT && RFT) {
9177+
// The invocation of IsFunctionConversion below will try to transform rtrans
9178+
// to obtain an exact match for ltrans. This should not fail because of
9179+
// mismatches in result type and parameter types, they were already checked
9180+
// by typesAreCompatible above. So we will recreate rtrans (or where
9181+
// appropriate ltrans) using the result type and parameter types from ltrans
9182+
// (respectively rtrans), but keeping its ExtInfo/ExtProtoInfo.
9183+
const auto *LFPT = dyn_cast<FunctionProtoType>(LFT);
9184+
const auto *RFPT = dyn_cast<FunctionProtoType>(RFT);
9185+
if (LFPT && RFPT) {
9186+
rtrans = S.Context.getFunctionType(LFPT->getReturnType(),
9187+
LFPT->getParamTypes(),
9188+
RFPT->getExtProtoInfo());
9189+
} else if (LFPT) {
9190+
FunctionProtoType::ExtProtoInfo EPI;
9191+
EPI.ExtInfo = RFT->getExtInfo();
9192+
rtrans = S.Context.getFunctionType(LFPT->getReturnType(),
9193+
LFPT->getParamTypes(), EPI);
9194+
} else if (RFPT) {
9195+
// In this case, we want to retain rtrans as a FunctionProtoType, to keep
9196+
// all of its ExtProtoInfo. Transform ltrans instead.
9197+
FunctionProtoType::ExtProtoInfo EPI;
9198+
EPI.ExtInfo = LFT->getExtInfo();
9199+
ltrans = S.Context.getFunctionType(RFPT->getReturnType(),
9200+
RFPT->getParamTypes(), EPI);
9201+
} else {
9202+
rtrans = S.Context.getFunctionNoProtoType(LFT->getReturnType(),
9203+
RFT->getExtInfo());
9204+
}
9205+
if (!S.Context.hasSameUnqualifiedType(rtrans, ltrans) &&
9206+
!S.IsFunctionConversion(rtrans, ltrans))
91969207
return AssignConvertType::IncompatibleFunctionPointer;
91979208
}
9198-
if (IsInvalidCmseNSCallConversion(S, ltrans, rtrans))
9199-
return AssignConvertType::IncompatibleFunctionPointer;
9200-
if (S.IsInvalidSMECallConversion(rtrans, ltrans))
9201-
return AssignConvertType::IncompatibleFunctionPointer;
92029209
return ConvTy;
92039210
}
92049211

clang/lib/Sema/SemaOverload.cpp

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,14 +1892,7 @@ bool Sema::TryFunctionConversion(QualType FromType, QualType ToType,
18921892
return Changed;
18931893
}
18941894

1895-
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
1896-
bool *DiscardingCFIUncheckedCallee,
1897-
bool *AddingCFIUncheckedCallee) const {
1898-
if (DiscardingCFIUncheckedCallee)
1899-
*DiscardingCFIUncheckedCallee = false;
1900-
if (AddingCFIUncheckedCallee)
1901-
*AddingCFIUncheckedCallee = false;
1902-
1895+
bool Sema::IsFunctionConversion(QualType FromType, QualType ToType) const {
19031896
if (Context.hasSameUnqualifiedType(FromType, ToType))
19041897
return false;
19051898

@@ -1958,25 +1951,14 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
19581951
const auto *ToFPT = dyn_cast<FunctionProtoType>(ToFn);
19591952

19601953
if (FromFPT && ToFPT) {
1961-
if (FromFPT->hasCFIUncheckedCallee() && !ToFPT->hasCFIUncheckedCallee()) {
1962-
QualType NewTy = Context.getFunctionType(
1963-
FromFPT->getReturnType(), FromFPT->getParamTypes(),
1964-
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(false));
1965-
FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
1966-
FromFn = FromFPT;
1967-
Changed = true;
1968-
if (DiscardingCFIUncheckedCallee)
1969-
*DiscardingCFIUncheckedCallee = true;
1970-
} else if (!FromFPT->hasCFIUncheckedCallee() &&
1971-
ToFPT->hasCFIUncheckedCallee()) {
1954+
if (FromFPT->hasCFIUncheckedCallee() != ToFPT->hasCFIUncheckedCallee()) {
19721955
QualType NewTy = Context.getFunctionType(
19731956
FromFPT->getReturnType(), FromFPT->getParamTypes(),
1974-
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(true));
1957+
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(
1958+
ToFPT->hasCFIUncheckedCallee()));
19751959
FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
19761960
FromFn = FromFPT;
19771961
Changed = true;
1978-
if (AddingCFIUncheckedCallee)
1979-
*AddingCFIUncheckedCallee = true;
19801962
}
19811963
}
19821964

@@ -2007,11 +1989,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
20071989
Changed = true;
20081990
}
20091991

2010-
// For C, when called from checkPointerTypesForAssignment,
2011-
// we need to not alter FromFn, or else even an innocuous cast
2012-
// like dropping effects will fail. In C++ however we do want to
2013-
// alter FromFn (because of the way PerformImplicitConversion works).
2014-
if (Context.hasAnyFunctionEffects() && getLangOpts().CPlusPlus) {
1992+
if (Context.hasAnyFunctionEffects()) {
20151993
FromFPT = cast<FunctionProtoType>(FromFn); // in case FromFn changed above
20161994

20171995
// Transparently add/drop effects; here we are concerned with
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %clang_cc1 -fsyntax-only %s -std=c99 -verify=expected
2+
// RUN: %clang_cc1 -fsyntax-only %s -std=c11 -verify=expected
3+
// RUN: %clang_cc1 -fsyntax-only %s -std=c23 -verify=expected,proto
4+
5+
// This verifies that -Wincompatible-function-pointer-type diagnostics for
6+
// extended function type information are consistent, also in case of other
7+
// allowed funcion type difference in C.
8+
//
9+
// Test case adapted from issue #41465, with suggestions from PR #160477.
10+
11+
enum E { A = -1, B };
12+
13+
// Case 1: assignment adding noreturn
14+
15+
int f1 (int);
16+
int (*fp1a)(int) __attribute__((noreturn)) = &f1; // expected-error {{incompatible function pointer types}}
17+
enum E (*fp1b)(int) __attribute__((noreturn)) = &f1; // expected-error {{incompatible function pointer types}}
18+
int (*fp1c)() __attribute__((noreturn)) = &f1; // expected-error {{incompatible function pointer types}}
19+
20+
// Case 2: assignment adding noescape on arg
21+
22+
int f2 (int* ) __attribute__((noreturn));
23+
int (*fp2a)(int* __attribute__((noescape))) __attribute__((noreturn)) = &f2; // expected-error {{incompatible function pointer types}}
24+
int (*fp2b)(int* __attribute__((noescape))) = &f2; // expected-error {{incompatible function pointer types}}
25+
26+
// Case 3: assignment adding cfi_unchecked_callee
27+
28+
int f3 (int* );
29+
int (*fp3a)(int* ) __attribute__((noreturn )) = &f3; // expected-error {{incompatible function pointer types}}
30+
int (*fp3b)(int* __attribute__((noescape))) = &f3; // expected-error {{incompatible function pointer types}}
31+
int (*fp3c)(int* ) __attribute__((noreturn,cfi_unchecked_callee)) = &f3; // expected-error {{incompatible function pointer types}}
32+
int (*fp3d)(int* __attribute__((noescape))) __attribute__(( cfi_unchecked_callee)) = &f3; // expected-error {{incompatible function pointer types}}
33+
int (*fp3e)(int* __attribute__((noescape))) __attribute__((noreturn,cfi_unchecked_callee)) = &f3; // expected-error {{incompatible function pointer types}}
34+
35+
// Case 4: assignment converting between prototype/no-prototype
36+
37+
void p1(void);
38+
void i1(int );
39+
void n1( );
40+
void p2(void) __attribute__((noreturn));
41+
void i2(int ) __attribute__((noreturn));
42+
void n2( ) __attribute__((noreturn));
43+
44+
void (*t1)() = p1;
45+
void (*t2)() = i1; // proto-error {{incompatible function pointer types}}
46+
void (*t3)() = n1;
47+
void (*t4)() __attribute__((noreturn)) = p1; // expected-error {{incompatible function pointer types}}
48+
void (*t5)() __attribute__((noreturn)) = i1; // expected-error {{incompatible function pointer types}}
49+
void (*t6)() __attribute__((noreturn)) = n1; // expected-error {{incompatible function pointer types}}
50+
51+
void (*t7)() = p2;
52+
void (*t8)() = i2; // proto-error {{incompatible function pointer types}}
53+
void (*t9)() = n2;
54+
void (*tA)() __attribute__((noreturn)) = p2;
55+
void (*tB)() __attribute__((noreturn)) = i2; // proto-error {{incompatible function pointer types}}
56+
void (*tC)() __attribute__((noreturn)) = n2;

0 commit comments

Comments
 (0)