Index: include/clang/AST/ExprObjC.h =================================================================== --- include/clang/AST/ExprObjC.h +++ include/clang/AST/ExprObjC.h @@ -138,6 +138,12 @@ 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(); } Index: include/clang/Basic/DiagnosticGroups.td =================================================================== --- include/clang/Basic/DiagnosticGroups.td +++ include/clang/Basic/DiagnosticGroups.td @@ -404,6 +404,7 @@ 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">; Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -918,6 +918,9 @@ "inconsistent number of instance variables specified">; def warn_undef_method_impl : Warning<"method definition for %0 not found">, InGroup>; +def warn_objc_boxing_invalid_utf8_string : Warning< + "string is ill-formed as UTF-8 and will become a null NSString* when boxed">, + InGroup; def warn_conflicting_overriding_ret_types : Warning< "conflicting return type in " Index: lib/AST/ExprConstant.cpp =================================================================== --- lib/AST/ExprConstant.cpp +++ lib/AST/ExprConstant.cpp @@ -1743,6 +1743,8 @@ case Expr::CXXTypeidExprClass: case Expr::CXXUuidofExprClass: return true; + case Expr::ObjCBoxedExprClass: + return cast(E)->isExpressibleAsConstantInitializer(); case Expr::CallExprClass: return IsStringLiteralCall(cast(E)); // For GCC compatibility, &&label has static storage duration. @@ -5794,6 +5796,8 @@ 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); Index: lib/CodeGen/CGExprConstant.cpp =================================================================== --- lib/CodeGen/CGExprConstant.cpp +++ lib/CodeGen/CGExprConstant.cpp @@ -1613,6 +1613,7 @@ 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 @@ 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(E->getSubExpr()->IgnoreParenCasts()); + return emitConstantObjCStringLiteral(SL, E->getType(), CGM); } ConstantLValue Index: lib/CodeGen/CGObjC.cpp =================================================================== --- lib/CodeGen/CGObjC.cpp +++ lib/CodeGen/CGObjC.cpp @@ -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 @@ // 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(); Index: lib/Sema/SemaExprObjC.cpp =================================================================== --- lib/Sema/SemaExprObjC.cpp +++ lib/Sema/SemaExprObjC.cpp @@ -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 @@ 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(ValueExpr)) + if (CE->getCastKind() == CK_ArrayToPointerDecay) + if (auto *SL = + dyn_cast(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) + << SL->getSourceRange(); + } + if (!StringWithUTF8StringMethod) { IdentifierInfo *II = &Context.Idents.get("stringWithUTF8String"); Selector stringWithUTF8String = Context.Selectors.getUnarySelector(II); Index: test/CodeGenObjC/boxing.m =================================================================== --- test/CodeGenObjC/boxing.m +++ test/CodeGenObjC/boxing.m @@ -53,6 +53,9 @@ + (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 @@ // 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 @@ 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"); } Index: test/SemaObjC/boxing-illegal.m =================================================================== --- test/SemaObjC/boxing-illegal.m +++ test/SemaObjC/boxing-illegal.m @@ -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 @@ 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; Index: test/SemaObjC/objc-literal-sig.m =================================================================== --- test/SemaObjC/objc-literal-sig.m +++ test/SemaObjC/objc-literal-sig.m @@ -39,6 +39,8 @@ // 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 @@ 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}} } Index: test/SemaObjC/transfer-boxed-string-nullability.m =================================================================== --- test/SemaObjC/transfer-boxed-string-nullability.m +++ test/SemaObjC/transfer-boxed-string-nullability.m @@ -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 } Index: test/SemaObjCXX/literals.mm =================================================================== --- test/SemaObjCXX/literals.mm +++ test/SemaObjCXX/literals.mm @@ -50,6 +50,9 @@ + (id)dictionaryWithObjects:(const id [])objects forKeys:(const id [])keys count:(unsigned long)cnt; @end +@interface NSString +@end + template struct ConvertibleTo { operator T(); @@ -185,3 +188,8 @@ void test_dictionary_colon() { id dict = @{ key : value }; } + +void testConstExpr() { + constexpr NSString *t0 = @"abc"; + constexpr NSString *t1 = @("abc"); +}