[ObjC] Emit a boxed expression as a compile-time constant if the
expression inside the parentheses is a valid UTF-8 string literal. Previously clang emitted an expression like @("abc") as a message send to stringWithUTF8String. This commit makes clang emit the boxed expression as a compile-time constant instead. This commit also has the effect of silencing the nullable-to-nonnull conversion warning clang started emitting after r317727, which originally motivated this commit (see https://oleb.net/2018/@keypath). rdar://problem/42684601 Differential Revision: https://reviews.llvm.org/D58729 llvm-svn: 355662
This commit is contained in:
parent
988332a54a
commit
1488ee4bd5
|
@ -138,6 +138,12 @@ public:
|
|||
return BoxingMethod;
|
||||
}
|
||||
|
||||
// Indicates whether this boxed expression can be emitted as a compile-time
|
||||
// constant.
|
||||
bool isExpressibleAsConstantInitializer() const {
|
||||
return !BoxingMethod && SubExpr;
|
||||
}
|
||||
|
||||
SourceLocation getAtLoc() const { return Range.getBegin(); }
|
||||
|
||||
SourceLocation getBeginLoc() const LLVM_READONLY { return Range.getBegin(); }
|
||||
|
|
|
@ -404,6 +404,7 @@ def ObjCPointerIntrospectPerformSelector : DiagGroup<"deprecated-objc-pointer-in
|
|||
def ObjCPointerIntrospect : DiagGroup<"deprecated-objc-pointer-introspection", [ObjCPointerIntrospectPerformSelector]>;
|
||||
def ObjCMultipleMethodNames : DiagGroup<"objc-multiple-method-names">;
|
||||
def ObjCFlexibleArray : DiagGroup<"objc-flexible-array">;
|
||||
def ObjCBoxing : DiagGroup<"objc-boxing">;
|
||||
def OpenCLUnsupportedRGBA: DiagGroup<"opencl-unsupported-rgba">;
|
||||
def DeprecatedObjCIsaUsage : DiagGroup<"deprecated-objc-isa-usage">;
|
||||
def ExplicitInitializeCall : DiagGroup<"explicit-initialize-call">;
|
||||
|
|
|
@ -918,6 +918,9 @@ def err_inconsistent_ivar_count : Error<
|
|||
"inconsistent number of instance variables specified">;
|
||||
def warn_undef_method_impl : Warning<"method definition for %0 not found">,
|
||||
InGroup<DiagGroup<"incomplete-implementation">>;
|
||||
def warn_objc_boxing_invalid_utf8_string : Warning<
|
||||
"string is ill-formed as UTF-8 and will become a null %0 when boxed">,
|
||||
InGroup<ObjCBoxing>;
|
||||
|
||||
def warn_conflicting_overriding_ret_types : Warning<
|
||||
"conflicting return type in "
|
||||
|
|
|
@ -1743,6 +1743,8 @@ static bool IsGlobalLValue(APValue::LValueBase B) {
|
|||
case Expr::CXXTypeidExprClass:
|
||||
case Expr::CXXUuidofExprClass:
|
||||
return true;
|
||||
case Expr::ObjCBoxedExprClass:
|
||||
return cast<ObjCBoxedExpr>(E)->isExpressibleAsConstantInitializer();
|
||||
case Expr::CallExprClass:
|
||||
return IsStringLiteralCall(cast<CallExpr>(E));
|
||||
// For GCC compatibility, &&label has static storage duration.
|
||||
|
@ -5794,6 +5796,8 @@ public:
|
|||
bool VisitObjCStringLiteral(const ObjCStringLiteral *E)
|
||||
{ return Success(E); }
|
||||
bool VisitObjCBoxedExpr(const ObjCBoxedExpr *E) {
|
||||
if (E->isExpressibleAsConstantInitializer())
|
||||
return Success(E);
|
||||
if (Info.noteFailure())
|
||||
EvaluateIgnoredValue(Info, E->getSubExpr());
|
||||
return Error(E);
|
||||
|
|
|
@ -1613,6 +1613,7 @@ private:
|
|||
ConstantLValue VisitConstantExpr(const ConstantExpr *E);
|
||||
ConstantLValue VisitCompoundLiteralExpr(const CompoundLiteralExpr *E);
|
||||
ConstantLValue VisitStringLiteral(const StringLiteral *E);
|
||||
ConstantLValue VisitObjCBoxedExpr(const ObjCBoxedExpr *E);
|
||||
ConstantLValue VisitObjCEncodeExpr(const ObjCEncodeExpr *E);
|
||||
ConstantLValue VisitObjCStringLiteral(const ObjCStringLiteral *E);
|
||||
ConstantLValue VisitPredefinedExpr(const PredefinedExpr *E);
|
||||
|
@ -1775,10 +1776,24 @@ ConstantLValueEmitter::VisitObjCEncodeExpr(const ObjCEncodeExpr *E) {
|
|||
return CGM.GetAddrOfConstantStringFromObjCEncode(E);
|
||||
}
|
||||
|
||||
static ConstantLValue emitConstantObjCStringLiteral(const StringLiteral *S,
|
||||
QualType T,
|
||||
CodeGenModule &CGM) {
|
||||
auto C = CGM.getObjCRuntime().GenerateConstantString(S);
|
||||
return C.getElementBitCast(CGM.getTypes().ConvertTypeForMem(T));
|
||||
}
|
||||
|
||||
ConstantLValue
|
||||
ConstantLValueEmitter::VisitObjCStringLiteral(const ObjCStringLiteral *E) {
|
||||
auto C = CGM.getObjCRuntime().GenerateConstantString(E->getString());
|
||||
return C.getElementBitCast(CGM.getTypes().ConvertTypeForMem(E->getType()));
|
||||
return emitConstantObjCStringLiteral(E->getString(), E->getType(), CGM);
|
||||
}
|
||||
|
||||
ConstantLValue
|
||||
ConstantLValueEmitter::VisitObjCBoxedExpr(const ObjCBoxedExpr *E) {
|
||||
assert(E->isExpressibleAsConstantInitializer() &&
|
||||
"this boxed expression can't be emitted as a compile-time constant");
|
||||
auto *SL = cast<StringLiteral>(E->getSubExpr()->IgnoreParenCasts());
|
||||
return emitConstantObjCStringLiteral(SL, E->getType(), CGM);
|
||||
}
|
||||
|
||||
ConstantLValue
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "CGObjCRuntime.h"
|
||||
#include "CodeGenFunction.h"
|
||||
#include "CodeGenModule.h"
|
||||
#include "ConstantEmitter.h"
|
||||
#include "TargetInfo.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/DeclObjC.h"
|
||||
|
@ -60,7 +61,12 @@ CodeGenFunction::EmitObjCBoxedExpr(const ObjCBoxedExpr *E) {
|
|||
// Get the method.
|
||||
const ObjCMethodDecl *BoxingMethod = E->getBoxingMethod();
|
||||
const Expr *SubExpr = E->getSubExpr();
|
||||
assert(BoxingMethod && "BoxingMethod is null");
|
||||
|
||||
if (E->isExpressibleAsConstantInitializer()) {
|
||||
ConstantEmitter ConstEmitter(CGM);
|
||||
return ConstEmitter.tryEmitAbstract(E, E->getType());
|
||||
}
|
||||
|
||||
assert(BoxingMethod->isClassMethod() && "BoxingMethod must be a class method");
|
||||
Selector Sel = BoxingMethod->getSelector();
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
#include "clang/Sema/Scope.h"
|
||||
#include "clang/Sema/ScopeInfo.h"
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/Support/ConvertUTF.h"
|
||||
|
||||
using namespace clang;
|
||||
using namespace sema;
|
||||
|
@ -524,6 +525,30 @@ ExprResult Sema::BuildObjCBoxedExpr(SourceRange SR, Expr *ValueExpr) {
|
|||
NSStringPointer = Context.getObjCObjectPointerType(NSStringObject);
|
||||
}
|
||||
|
||||
// The boxed expression can be emitted as a compile time constant if it is
|
||||
// a string literal whose character encoding is compatible with UTF-8.
|
||||
if (auto *CE = dyn_cast<ImplicitCastExpr>(ValueExpr))
|
||||
if (CE->getCastKind() == CK_ArrayToPointerDecay)
|
||||
if (auto *SL =
|
||||
dyn_cast<StringLiteral>(CE->getSubExpr()->IgnoreParens())) {
|
||||
assert((SL->isAscii() || SL->isUTF8()) &&
|
||||
"unexpected character encoding");
|
||||
StringRef Str = SL->getString();
|
||||
const llvm::UTF8 *StrBegin = Str.bytes_begin();
|
||||
const llvm::UTF8 *StrEnd = Str.bytes_end();
|
||||
// Check that this is a valid UTF-8 string.
|
||||
if (llvm::isLegalUTF8String(&StrBegin, StrEnd)) {
|
||||
BoxedType = Context.getAttributedType(
|
||||
AttributedType::getNullabilityAttrKind(
|
||||
NullabilityKind::NonNull),
|
||||
NSStringPointer, NSStringPointer);
|
||||
return new (Context) ObjCBoxedExpr(CE, BoxedType, nullptr, SR);
|
||||
}
|
||||
|
||||
Diag(SL->getBeginLoc(), diag::warn_objc_boxing_invalid_utf8_string)
|
||||
<< NSStringPointer << SL->getSourceRange();
|
||||
}
|
||||
|
||||
if (!StringWithUTF8StringMethod) {
|
||||
IdentifierInfo *II = &Context.Idents.get("stringWithUTF8String");
|
||||
Selector stringWithUTF8String = Context.Selectors.getUnarySelector(II);
|
||||
|
|
|
@ -53,6 +53,9 @@ typedef signed char BOOL;
|
|||
+ (id)stringWithUTF8String:(const char *)nullTerminatedCString;
|
||||
@end
|
||||
|
||||
// CHECK: [[V0:%.*]] = type opaque
|
||||
// CHECK: [[STRUCT_NSCONSTANT_STRING_TAG:%.*]] = type { i32*, i32, i8*, i64 }
|
||||
|
||||
// CHECK: [[WithIntMeth:@.*]] = private unnamed_addr constant [15 x i8] c"numberWithInt:\00"
|
||||
// CHECK: [[WithIntSEL:@.*]] = private externally_initialized global i8* getelementptr inbounds ([15 x i8], [15 x i8]* [[WithIntMeth]]
|
||||
// CHECK: [[WithCharMeth:@.*]] = private unnamed_addr constant [16 x i8] c"numberWithChar:\00"
|
||||
|
@ -65,8 +68,12 @@ typedef signed char BOOL;
|
|||
// CHECK: [[WithUnsignedIntegerSEL:@.*]] = private externally_initialized global i8* getelementptr inbounds ([27 x i8], [27 x i8]* [[WithUnsignedIntegerMeth]]
|
||||
// CHECK: [[stringWithUTF8StringMeth:@.*]] = private unnamed_addr constant [22 x i8] c"stringWithUTF8String:\00"
|
||||
// CHECK: [[stringWithUTF8StringSEL:@.*]] = private externally_initialized global i8* getelementptr inbounds ([22 x i8], [22 x i8]* [[stringWithUTF8StringMeth]]
|
||||
// CHECK: [[STR0:.*]] = private unnamed_addr constant [4 x i8] c"abc\00", section "__TEXT,__cstring,cstring_literals", align 1
|
||||
// CHECK: [[UNNAMED_CFSTRING:.*]] = private global [[STRUCT_NSCONSTANT_STRING_TAG]] { i32* getelementptr inbounds ([0 x i32], [0 x i32]* @__CFConstantStringClassReference, i32 0, i32 0), i32 1992, i8* getelementptr inbounds ([4 x i8], [4 x i8]* [[STR0]], i32 0, i32 0), i64 3 }, section "__DATA,__cfstring", align 8
|
||||
|
||||
int main() {
|
||||
// CHECK: [[T:%.*]] = alloca [[V0]]*, align 8
|
||||
|
||||
// CHECK: load i8*, i8** [[WithIntSEL]]
|
||||
int i; @(i);
|
||||
// CHECK: load i8*, i8** [[WithCharSEL]]
|
||||
|
@ -92,4 +99,7 @@ int main() {
|
|||
Color col = Red;
|
||||
// CHECK: load i8*, i8** [[WithIntegerSEL]]
|
||||
@(col);
|
||||
|
||||
// CHECK: store [[V0]]* bitcast ([[STRUCT_NSCONSTANT_STRING_TAG]]* [[UNNAMED_CFSTRING]] to [[V0]]*), [[V0]]** [[T]], align 8
|
||||
NSString *t = @("abc");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// RUN: %clang_cc1 -fsyntax-only -verify -Wattributes %s
|
||||
// RUN: %clang_cc1 -fsyntax-only -verify -Wattributes -fpascal-strings %s
|
||||
|
||||
typedef long NSInteger;
|
||||
typedef unsigned long NSUInteger;
|
||||
|
@ -57,6 +57,19 @@ void testEnum(void *p) {
|
|||
box = @(*(enum ForwE*)p); // expected-error {{incomplete type 'enum ForwE' used in a boxed expression}}
|
||||
}
|
||||
|
||||
@interface NSString
|
||||
@end
|
||||
|
||||
void testStringLiteral() {
|
||||
NSString *s;
|
||||
s = @("abc");
|
||||
s = @(u8"abc");
|
||||
s = @(u"abc"); // expected-error {{illegal type 'unsigned short *' used in a boxed expression}}
|
||||
s = @(U"abc"); // expected-error {{illegal type 'unsigned int *' used in a boxed expression}}
|
||||
s = @(L"abc"); // expected-error {{illegal type 'int *' used in a boxed expression}}
|
||||
s = @("\pabc"); // expected-error {{illegal type 'unsigned char *' used in a boxed expression}}
|
||||
}
|
||||
|
||||
// rdar://13333205
|
||||
@class NSMutableDictionary;
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ typedef _Bool BOOL;
|
|||
|
||||
// All tests are doubled to make sure that a bad method is not saved
|
||||
// and then used un-checked.
|
||||
const char *getStr(void);
|
||||
|
||||
void test_sig() {
|
||||
(void)@__objc_yes; // expected-error{{literal construction method 'numberWithBool:' has incompatible signature}}
|
||||
(void)@__objc_yes; // expected-error{{literal construction method 'numberWithBool:' has incompatible signature}}
|
||||
|
@ -46,6 +48,6 @@ void test_sig() {
|
|||
id array2 = @[ @17 ]; // expected-error{{literal construction method 'arrayWithObjects:count:' has incompatible signature}}
|
||||
id dict = @{ @"hello" : @17 }; // expected-error{{literal construction method 'dictionaryWithObjects:forKeys:count:' has incompatible signature}}
|
||||
id dict2 = @{ @"hello" : @17 }; // expected-error{{literal construction method 'dictionaryWithObjects:forKeys:count:' has incompatible signature}}
|
||||
id str = @("hello"); // expected-error{{literal construction method 'stringWithUTF8String:' has incompatible signature}}
|
||||
id str2 = @("hello"); // expected-error{{literal construction method 'stringWithUTF8String:' has incompatible signature}}
|
||||
id str = @(getStr()); // expected-error{{literal construction method 'stringWithUTF8String:' has incompatible signature}}
|
||||
id str2 = @(getStr()); // expected-error{{literal construction method 'stringWithUTF8String:' has incompatible signature}}
|
||||
}
|
||||
|
|
|
@ -16,13 +16,23 @@
|
|||
void takesNonNull(NSString * _Nonnull ptr);
|
||||
|
||||
void testBoxedString() {
|
||||
// No diagnostic emitted as this doesn't need a stringWithUTF8String message
|
||||
// send.
|
||||
takesNonNull(@("hey"));
|
||||
takesNonNull(@(u8"hey"));
|
||||
|
||||
// If the string isn't a valid UTF-8 string, a diagnostic is emitted since the
|
||||
// boxed expression turns into a message send.
|
||||
takesNonNull(@(u8"\xFF")); // expected-warning {{string is ill-formed as UTF-8}}
|
||||
takesNonNull(@(u8"\xC0\x80")); // expected-warning {{string is ill-formed as UTF-8}}
|
||||
|
||||
const char *str = "hey";
|
||||
takesNonNull([NSString stringWithUTF8String:str]);
|
||||
takesNonNull(@(str));
|
||||
#ifndef NOWARN
|
||||
// expected-warning@-3 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
// expected-warning@-3 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
#else
|
||||
// expected-no-diagnostics
|
||||
// expected-warning@-7 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
// expected-warning@-7 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
// expected-warning@-5 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
// expected-warning@-5 {{implicit conversion from nullable pointer 'NSString * _Nullable' to non-nullable pointer type 'NSString * _Nonnull'}}
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@ void x() {
|
|||
+ (id)dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(unsigned long)cnt;
|
||||
@end
|
||||
|
||||
@interface NSString
|
||||
@end
|
||||
|
||||
template<typename T>
|
||||
struct ConvertibleTo {
|
||||
operator T();
|
||||
|
@ -185,3 +188,8 @@ id value;
|
|||
void test_dictionary_colon() {
|
||||
id dict = @{ key : value };
|
||||
}
|
||||
|
||||
void testConstExpr() {
|
||||
constexpr NSString *t0 = @"abc";
|
||||
constexpr NSString *t1 = @("abc");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue