[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:
Akira Hatanaka 2019-03-08 04:45:37 +00:00
parent 988332a54a
commit 1488ee4bd5
12 changed files with 113 additions and 10 deletions

View file

@ -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(); }

View file

@ -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">;

View file

@ -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 "

View file

@ -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);

View file

@ -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

View file

@ -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();

View file

@ -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);

View file

@ -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");
}

View file

@ -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;

View file

@ -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}}
}

View file

@ -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
}

View file

@ -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");
}