Skip to content

Commit ef33bff

Browse files
committed
[clang][Sema] Fix incomplete C mode incompatible ExtInfo/ExtProtoInfo conversion diagnostic
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" and function effect attributes, worked around the issue by introducing more complex checking logic that could open other holes. Here, we solve this longstanding issue by using IsFunctionConverions 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 for checking "cfi_unchecked_callee" and function effect attributes are removed. Fixes #160474
1 parent cae73be commit ef33bff

File tree

4 files changed

+87
-42
lines changed

4 files changed

+87
-42
lines changed

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 tranformed
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: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9187,12 +9187,43 @@ static AssignConvertType checkPointerTypesForAssignment(Sema &S,
91879187
return AssignConvertType::IncompatibleFunctionPointer;
91889188
return AssignConvertType::IncompatiblePointer;
91899189
}
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)
9190+
const auto *LFT = ltrans->getAs<FunctionType>();
9191+
const auto *RFT = rtrans->getAs<FunctionType>();
9192+
if (!S.getLangOpts().CPlusPlus && LFT && RFT) {
9193+
// The invocation of IsFunctionConversion below will try to transform rtrans
9194+
// to obtain an exact match for ltrans. This should not fail because of
9195+
// mismatches in result type and parameter types, they were already checked
9196+
// by typesAreCompatible above. So we will recreate rtrans using the result
9197+
// type and parameter types from ltrans, but keeping its
9198+
// ExtInfo/ExtProtoInfo.
9199+
const auto *LFPT = dyn_cast<FunctionProtoType>(LFT);
9200+
const auto *RFPT = dyn_cast<FunctionProtoType>(RFT);
9201+
if (LFPT && RFPT) {
9202+
rtrans = S.Context.getFunctionType(LFPT->getReturnType(),
9203+
LFPT->getParamTypes(),
9204+
RFPT->getExtProtoInfo());
9205+
} else if (LFPT) {
9206+
FunctionProtoType::ExtProtoInfo EPI;
9207+
EPI.ExtInfo = RFT->getExtInfo();
9208+
rtrans = S.Context.getFunctionType(LFPT->getReturnType(),
9209+
LFPT->getParamTypes(), EPI);
9210+
} else if (RFPT) {
9211+
// In this case, we want to retain rtrans as a FunctionProtoType, to keep
9212+
// all of its ExtProtoInfo. To enable an exact match, we recreate ltrans
9213+
// as a FunctionProtoType, using its own info but filling in the parameter
9214+
// types from rtrans.
9215+
FunctionProtoType::ExtProtoInfo EPI;
9216+
EPI.ExtInfo = LFT->getExtInfo();
9217+
ltrans = S.Context.getFunctionType(LFT->getReturnType(),
9218+
RFPT->getParamTypes(), EPI);
9219+
rtrans = S.Context.getFunctionType(
9220+
LFT->getReturnType(), RFPT->getParamTypes(), RFPT->getExtProtoInfo());
9221+
} else {
9222+
rtrans = S.Context.getFunctionNoProtoType(LFT->getReturnType(),
9223+
RFT->getExtInfo());
9224+
}
9225+
if (!S.Context.hasSameUnqualifiedType(rtrans, ltrans) &&
9226+
!S.IsFunctionConversion(rtrans, ltrans))
91969227
return AssignConvertType::IncompatibleFunctionPointer;
91979228
}
91989229
if (IsInvalidCmseNSCallConversion(S, ltrans, rtrans))

clang/lib/Sema/SemaOverload.cpp

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

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

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

19591952
if (FromFPT && ToFPT) {
1960-
if (FromFPT->hasCFIUncheckedCallee() && !ToFPT->hasCFIUncheckedCallee()) {
1961-
QualType NewTy = Context.getFunctionType(
1962-
FromFPT->getReturnType(), FromFPT->getParamTypes(),
1963-
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(false));
1964-
FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
1965-
FromFn = FromFPT;
1966-
Changed = true;
1967-
if (DiscardingCFIUncheckedCallee)
1968-
*DiscardingCFIUncheckedCallee = true;
1969-
} else if (!FromFPT->hasCFIUncheckedCallee() &&
1970-
ToFPT->hasCFIUncheckedCallee()) {
1953+
if (FromFPT->hasCFIUncheckedCallee() != ToFPT->hasCFIUncheckedCallee()) {
19711954
QualType NewTy = Context.getFunctionType(
19721955
FromFPT->getReturnType(), FromFPT->getParamTypes(),
1973-
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(true));
1956+
FromFPT->getExtProtoInfo().withCFIUncheckedCallee(
1957+
ToFPT->hasCFIUncheckedCallee()));
19741958
FromFPT = cast<FunctionProtoType>(NewTy.getTypePtr());
19751959
FromFn = FromFPT;
19761960
Changed = true;
1977-
if (AddingCFIUncheckedCallee)
1978-
*AddingCFIUncheckedCallee = true;
19791961
}
19801962
}
19811963

@@ -2006,11 +1988,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType,
20061988
Changed = true;
20071989
}
20081990

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

20161994
// Transparently add/drop effects; here we are concerned with
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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 #160474.
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 to function with no prototype
36+
37+
int f4 (int);
38+
int (*fp4a)(int) = &f4;
39+
int (*fp4b)() = &f4; // proto-error {{incompatible function pointer types}}

0 commit comments

Comments
 (0)