From: John McCall Date: Sat, 1 Oct 2011 01:01:08 +0000 (+0000) Subject: Allow the results of cf_returns_not_retained function X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2cf031d33ce6801dc11183a3cb48168f53fe06c4;p=clang Allow the results of cf_returns_not_retained function calls, or calls to audited functions without an explicit return attribute, to be casted without a bridge cast. Tie this mechanism in with the existing exceptions to the cast restrictions. State those restrictions more correctly and generalize. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@140912 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index b272250d91..42d2734bde 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -5762,10 +5762,6 @@ public: Expr *CastExpr, CastKind &Kind, CXXCastPath &BasePath, bool FunctionalStyle); - /// \brief Checks for valid expressions which can be cast to an ObjC - /// pointer without needing a bridge cast. - bool ValidObjCARCNoBridgeCastExpr(Expr *&Exp, QualType castType); - /// \brief Checks for invalid conversions and casts between /// retainable pointers and other pointer kinds. void CheckObjCARCConversion(SourceRange castRange, QualType castType, diff --git a/lib/Sema/SemaExprObjC.cpp b/lib/Sema/SemaExprObjC.cpp index 51b5e4fb63..14c2d2808c 100644 --- a/lib/Sema/SemaExprObjC.cpp +++ b/lib/Sema/SemaExprObjC.cpp @@ -16,6 +16,7 @@ #include "clang/Sema/Scope.h" #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/Initialization.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/ExprObjC.h" @@ -1546,203 +1547,356 @@ ExprResult Sema::ActOnInstanceMessage(Scope *S, } enum ARCConversionTypeClass { + /// int, void, struct A ACTC_none, + + /// id, void (^)() ACTC_retainable, - ACTC_indirectRetainable + + /// id*, id***, void (^*)(), + ACTC_indirectRetainable, + + /// void* might be a normal C type, or it might a CF type. + ACTC_voidPtr, + + /// struct A* + ACTC_coreFoundation }; +static bool isAnyRetainable(ARCConversionTypeClass ACTC) { + return (ACTC == ACTC_retainable || + ACTC == ACTC_coreFoundation || + ACTC == ACTC_voidPtr); +} +static bool isAnyCLike(ARCConversionTypeClass ACTC) { + return ACTC == ACTC_none || + ACTC == ACTC_voidPtr || + ACTC == ACTC_coreFoundation; +} + static ARCConversionTypeClass classifyTypeForARCConversion(QualType type) { - ARCConversionTypeClass ACTC = ACTC_retainable; + bool isIndirect = false; // Ignore an outermost reference type. - if (const ReferenceType *ref = type->getAs()) + if (const ReferenceType *ref = type->getAs()) { type = ref->getPointeeType(); + isIndirect = true; + } // Drill through pointers and arrays recursively. while (true) { if (const PointerType *ptr = type->getAs()) { type = ptr->getPointeeType(); + + // The first level of pointer may be the innermost pointer on a CF type. + if (!isIndirect) { + if (type->isVoidType()) return ACTC_voidPtr; + if (type->isRecordType()) return ACTC_coreFoundation; + } } else if (const ArrayType *array = type->getAsArrayTypeUnsafe()) { type = QualType(array->getElementType()->getBaseElementTypeUnsafe(), 0); } else { break; } - ACTC = ACTC_indirectRetainable; + isIndirect = true; } - if (!type->isObjCRetainableType()) return ACTC_none; - return ACTC; + if (isIndirect) { + if (type->isObjCARCBridgableType()) + return ACTC_indirectRetainable; + return ACTC_none; + } + + if (type->isObjCARCBridgableType()) + return ACTC_retainable; + + return ACTC_none; } namespace { - /// Return true if the given expression can be reasonably converted - /// between a retainable pointer type and a C pointer type. - struct ARCCastChecker : StmtVisitor { + /// A result from the cast checker. + enum ACCResult { + /// Cannot be casted. + ACC_invalid, + + /// Can be safely retained or not retained. + ACC_bottom, + + /// Can be casted at +0. + ACC_plusZero, + + /// Can be casted at +1. + ACC_plusOne + }; + ACCResult merge(ACCResult left, ACCResult right) { + if (left == right) return left; + if (left == ACC_bottom) return right; + if (right == ACC_bottom) return left; + return ACC_invalid; + } + + /// A checker which white-lists certain expressions whose conversion + /// to or from retainable type would otherwise be forbidden in ARC. + class ARCCastChecker : public StmtVisitor { + typedef StmtVisitor super; + ASTContext &Context; - ARCCastChecker(ASTContext &Context) : Context(Context) {} - bool VisitStmt(Stmt *s) { - return false; + ARCConversionTypeClass SourceClass; + ARCConversionTypeClass TargetClass; + + static bool isCFType(QualType type) { + // Someday this can use ns_bridged. For now, it has to do this. + return type->isCARCBridgableType(); } - bool VisitExpr(Expr *e) { - return e->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull); + + public: + ARCCastChecker(ASTContext &Context, ARCConversionTypeClass source, + ARCConversionTypeClass target) + : Context(Context), SourceClass(source), TargetClass(target) {} + + using super::Visit; + ACCResult Visit(Expr *e) { + return super::Visit(e->IgnoreParens()); } - - bool VisitParenExpr(ParenExpr *e) { - return Visit(e->getSubExpr()); + + ACCResult VisitStmt(Stmt *s) { + return ACC_invalid; + } + + /// Null pointer constants can be casted however you please. + ACCResult VisitExpr(Expr *e) { + if (e->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNotNull)) + return ACC_bottom; + return ACC_invalid; } - bool VisitCastExpr(CastExpr *e) { + + /// Objective-C string literals can be safely casted. + ACCResult VisitObjCStringLiteral(ObjCStringLiteral *e) { + // If we're casting to any retainable type, go ahead. Global + // strings are immune to retains, so this is bottom. + if (isAnyRetainable(TargetClass)) return ACC_bottom; + + return ACC_invalid; + } + + /// Look through certain implicit and explicit casts. + ACCResult VisitCastExpr(CastExpr *e) { switch (e->getCastKind()) { case CK_NullToPointer: - return true; + return ACC_bottom; + case CK_NoOp: case CK_LValueToRValue: case CK_BitCast: + case CK_GetObjCProperty: case CK_CPointerToObjCPointerCast: case CK_BlockPointerToObjCPointerCast: case CK_AnyPointerToBlockPointerCast: return Visit(e->getSubExpr()); + default: - return false; + return ACC_invalid; } } - bool VisitUnaryExtension(UnaryOperator *e) { + + /// Look through unary extension. + ACCResult VisitUnaryExtension(UnaryOperator *e) { return Visit(e->getSubExpr()); } - bool VisitBinComma(BinaryOperator *e) { + + /// Ignore the LHS of a comma operator. + ACCResult VisitBinComma(BinaryOperator *e) { return Visit(e->getRHS()); } - bool VisitConditionalOperator(ConditionalOperator *e) { - // Conditional operators are okay if both sides are okay. - return Visit(e->getTrueExpr()) && Visit(e->getFalseExpr()); - } - bool VisitObjCStringLiteral(ObjCStringLiteral *e) { - // Always white-list Objective-C string literals. - return true; + + /// Conditional operators are okay if both sides are okay. + ACCResult VisitConditionalOperator(ConditionalOperator *e) { + ACCResult left = Visit(e->getTrueExpr()); + if (left == ACC_invalid) return ACC_invalid; + return merge(left, Visit(e->getFalseExpr())); } - bool VisitStmtExpr(StmtExpr *e) { + + /// Statement expressions are okay if their result expression is okay. + ACCResult VisitStmtExpr(StmtExpr *e) { return Visit(e->getSubStmt()->body_back()); } - bool VisitDeclRefExpr(DeclRefExpr *e) { - // White-list references to global extern strings from system - // headers. - if (VarDecl *var = dyn_cast(e->getDecl())) - if (var->getStorageClass() == SC_Extern && - var->getType().isConstQualified() && - Context.getSourceManager().isInSystemHeader(var->getLocation())) - return true; - return false; + + /// Some declaration references are okay. + ACCResult VisitDeclRefExpr(DeclRefExpr *e) { + // References to global constants from system headers are okay. + // These are things like 'kCFStringTransformToLatin'. They are + // can also be assumed to be immune to retains. + VarDecl *var = dyn_cast(e->getDecl()); + if (isAnyRetainable(TargetClass) && + isAnyRetainable(SourceClass) && + var && + var->getStorageClass() == SC_Extern && + var->getType().isConstQualified() && + Context.getSourceManager().isInSystemHeader(var->getLocation())) { + return ACC_bottom; + } + + // Nothing else. + return ACC_invalid; } - }; -} -bool -Sema::ValidObjCARCNoBridgeCastExpr(Expr *&Exp, QualType castType) { - Expr *NewExp = Exp->IgnoreParenCasts(); - - if (!isa(NewExp) && !isa(NewExp) - && !isa(NewExp)) - return false; - ObjCMethodDecl *method = 0; - bool MethodReturnsPlusOne = false; - - if (ObjCPropertyRefExpr *PRE = dyn_cast(NewExp)) { - method = PRE->getExplicitProperty()->getGetterMethodDecl(); - } - else if (ObjCMessageExpr *ME = dyn_cast(NewExp)) - method = ME->getMethodDecl(); - else { - CallExpr *CE = cast(NewExp); - Decl *CallDecl = CE->getCalleeDecl(); - if (!CallDecl) - return false; - if (CallDecl->hasAttr()) - return true; - MethodReturnsPlusOne = CallDecl->hasAttr(); - if (!MethodReturnsPlusOne) { - if (NamedDecl *ND = dyn_cast(CallDecl)) - if (const IdentifierInfo *Id = ND->getIdentifier()) - if (Id->isStr("__builtin___CFStringMakeConstantString")) - return true; + /// Some calls are okay. + ACCResult VisitCallExpr(CallExpr *e) { + if (FunctionDecl *fn = e->getDirectCallee()) + if (ACCResult result = checkCallToFunction(fn)) + return result; + + return super::VisitCallExpr(e); } - } - - if (!MethodReturnsPlusOne) { - if (!method) - return false; - if (method->hasAttr()) - return true; - MethodReturnsPlusOne = method->hasAttr(); - if (!MethodReturnsPlusOne) { - ObjCMethodFamily family = method->getSelector().getMethodFamily(); - switch (family) { - case OMF_alloc: - case OMF_copy: - case OMF_mutableCopy: - case OMF_new: - MethodReturnsPlusOne = true; - break; - default: - break; + + ACCResult checkCallToFunction(FunctionDecl *fn) { + // Require a CF*Ref return type. + if (!isCFType(fn->getResultType())) + return ACC_invalid; + + if (!isAnyRetainable(TargetClass)) + return ACC_invalid; + + // Honor an explicit 'not retained' attribute. + if (fn->hasAttr()) + return ACC_plusZero; + + // Honor an explicit 'retained' attribute, except that for + // now we're not going to permit implicit handling of +1 results, + // because it's a bit frightening. + if (fn->hasAttr()) + return ACC_invalid; // ACC_plusOne if we start accepting this + + // Recognize this specific builtin function, which is used by CFSTR. + unsigned builtinID = fn->getBuiltinID(); + if (builtinID == Builtin::BI__builtin___CFStringMakeConstantString) + return ACC_bottom; + + // Otherwise, don't do anything implicit with an unaudited function. + if (!fn->hasAttr()) + return ACC_invalid; + + // Otherwise, it's +0 unless it follows the create convention. + if (ento::coreFoundation::followsCreateRule(fn)) + return ACC_invalid; // ACC_plusOne if we start accepting this + + return ACC_plusZero; + } + + ACCResult VisitObjCMessageExpr(ObjCMessageExpr *e) { + return checkCallToMethod(e->getMethodDecl()); + } + + ACCResult VisitObjCPropertyRefExpr(ObjCPropertyRefExpr *e) { + ObjCMethodDecl *method; + if (e->isExplicitProperty()) + method = e->getExplicitProperty()->getGetterMethodDecl(); + else + method = e->getImplicitPropertyGetter(); + return checkCallToMethod(method); + } + + ACCResult checkCallToMethod(ObjCMethodDecl *method) { + if (!method) return ACC_invalid; + + // Check for message sends to functions returning CF types. We + // just obey the Cocoa conventions with these, even though the + // return type is CF. + if (!isAnyRetainable(TargetClass) || !isCFType(method->getResultType())) + return ACC_invalid; + + // If the method is explicitly marked not-retained, it's +0. + if (method->hasAttr()) + return ACC_plusZero; + + // If the method is explicitly marked as returning retained, or its + // selector follows a +1 Cocoa convention, treat it as +1. + if (method->hasAttr()) + return ACC_plusOne; + + switch (method->getSelector().getMethodFamily()) { + case OMF_alloc: + case OMF_copy: + case OMF_mutableCopy: + case OMF_new: + return ACC_plusOne; + + default: + // Otherwise, treat it as +0. + return ACC_plusZero; } } - } - - if (MethodReturnsPlusOne) { - TypeSourceInfo *TSInfo = - Context.getTrivialTypeSourceInfo(castType, SourceLocation()); - ExprResult ExpRes = BuildObjCBridgedCast(SourceLocation(), OBC_BridgeTransfer, - SourceLocation(), TSInfo, Exp); - Exp = ExpRes.take(); - } - return true; + }; } void Sema::CheckObjCARCConversion(SourceRange castRange, QualType castType, Expr *&castExpr, CheckedConversionKind CCK) { QualType castExprType = castExpr->getType(); + + // For the purposes of the classification, we assume reference types + // will bind to temporaries. + QualType effCastType = castType; + if (const ReferenceType *ref = castType->getAs()) + effCastType = ref->getPointeeType(); ARCConversionTypeClass exprACTC = classifyTypeForARCConversion(castExprType); - ARCConversionTypeClass castACTC = classifyTypeForARCConversion(castType); + ARCConversionTypeClass castACTC = classifyTypeForARCConversion(effCastType); if (exprACTC == castACTC) return; - if (exprACTC && castType->isIntegralType(Context)) return; + if (isAnyCLike(exprACTC) && isAnyCLike(castACTC)) return; + + // Allow all of these types to be cast to integer types (but not + // vice-versa). + if (castACTC == ACTC_none && castType->isIntegralType(Context)) + return; // Allow casts between pointers to lifetime types (e.g., __strong id*) // and pointers to void (e.g., cv void *). Casting from void* to lifetime* // must be explicit. - if (const PointerType *CastPtr = castType->getAs()) { - if (const PointerType *CastExprPtr = castExprType->getAs()) { - QualType CastPointee = CastPtr->getPointeeType(); - QualType CastExprPointee = CastExprPtr->getPointeeType(); - if ((CCK != CCK_ImplicitConversion && - CastPointee->isObjCIndirectLifetimeType() && - CastExprPointee->isVoidType()) || - (CastPointee->isVoidType() && - CastExprPointee->isObjCIndirectLifetimeType())) - return; - } - } - - if (ARCCastChecker(Context).Visit(castExpr)) + if (exprACTC == ACTC_indirectRetainable && castACTC == ACTC_voidPtr) + return; + if (castACTC == ACTC_indirectRetainable && exprACTC == ACTC_voidPtr && + CCK != CCK_ImplicitConversion) + return; + + switch (ARCCastChecker(Context, exprACTC, castACTC).Visit(castExpr)) { + // For invalid casts, fall through. + case ACC_invalid: + break; + + // Do nothing for both bottom and +0. + case ACC_bottom: + case ACC_plusZero: + return; + + // If the result is +1, consume it here. + case ACC_plusOne: + castExpr = ImplicitCastExpr::Create(Context, castExpr->getType(), + CK_ARCConsumeObject, castExpr, + 0, VK_RValue); + ExprNeedsCleanups = true; return; + } SourceLocation loc = - (castRange.isValid() ? castRange.getBegin() : castExpr->getExprLoc()); + (castRange.isValid() ? castRange.getBegin() : castExpr->getExprLoc()); if (makeUnavailableInSystemHeader(loc, - "converts between Objective-C and C pointers in -fobjc-arc")) + "converts between Objective-C and C pointers in -fobjc-arc")) return; unsigned srcKind = 0; switch (exprACTC) { - case ACTC_none: - srcKind = (castExprType->isPointerType() ? 1 : 0); - break; - case ACTC_retainable: - srcKind = (castExprType->isBlockPointerType() ? 2 : 3); - break; - case ACTC_indirectRetainable: - srcKind = 4; - break; + case ACTC_none: + case ACTC_coreFoundation: + case ACTC_voidPtr: + srcKind = (castExprType->isPointerType() ? 1 : 0); + break; + case ACTC_retainable: + srcKind = (castExprType->isBlockPointerType() ? 2 : 3); + break; + case ACTC_indirectRetainable: + srcKind = 4; + break; } if (CCK == CCK_CStyleCast) { @@ -1750,12 +1904,7 @@ Sema::CheckObjCARCConversion(SourceRange castRange, QualType castType, SourceLocation AfterLParen = PP.getLocForEndOfToken(castRange.getBegin()); SourceLocation NoteLoc = AfterLParen.isValid()? AfterLParen : loc; - if (castType->isObjCARCBridgableType() && - castExprType->isCARCBridgableType()) { - // explicit unbridged casts are allowed if the source of the cast is a - // message sent to an objc method (or property access) - if (ValidObjCARCNoBridgeCastExpr(castExpr, castType)) - return; + if (castACTC == ACTC_retainable && isAnyRetainable(exprACTC)) { Diag(loc, diag::err_arc_cast_requires_bridge) << 2 << castExprType @@ -1772,8 +1921,7 @@ Sema::CheckObjCARCConversion(SourceRange castRange, QualType castType, return; } - if (castType->isCARCBridgableType() && - castExprType->isObjCARCBridgableType()){ + if (exprACTC == ACTC_retainable && isAnyRetainable(castACTC)) { Diag(loc, diag::err_arc_cast_requires_bridge) << (castExprType->isBlockPointerType()? 1 : 0) << castExprType diff --git a/test/CodeGenObjC/arc-unbridged-cast.m b/test/CodeGenObjC/arc-unbridged-cast.m index 0f3db09f1f..1da47e5582 100644 --- a/test/CodeGenObjC/arc-unbridged-cast.m +++ b/test/CodeGenObjC/arc-unbridged-cast.m @@ -29,8 +29,6 @@ CFStringRef SomeOtherFunc() __attribute__((cf_returns_retained)); id MMM() { id obj = (id)((CFStringRef) __builtin___CFStringMakeConstantString ("" "Some CF String" "")); - if (obj) - return (id) SomeOtherFunc(); return 0; } diff --git a/test/SemaObjC/arc-cf.m b/test/SemaObjC/arc-cf.m new file mode 100644 index 0000000000..9a1750a445 --- /dev/null +++ b/test/SemaObjC/arc-cf.m @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -verify %s + +typedef const void *CFTypeRef; +typedef const struct __CFString *CFStringRef; + +extern CFStringRef CFMakeString0(void); +extern CFStringRef CFCreateString0(void); +void test0() { + id x; + x = (id) CFMakeString0(); // expected-error {{requires a bridged cast}} expected-note {{__bridge to convert directly}} expected-note {{__bridge_transfer to transfer}} + x = (id) CFCreateString0(); // expected-error {{requires a bridged cast}} expected-note {{__bridge to convert directly}} expected-note {{__bridge_transfer to transfer}} +} + +extern CFStringRef CFMakeString1(void) __attribute__((cf_returns_not_retained)); +extern CFStringRef CFCreateString1(void) __attribute__((cf_returns_retained)); +void test1() { + id x; + x = (id) CFMakeString1(); + x = (id) CFCreateString1(); // expected-error {{requires a bridged cast}} expected-note {{__bridge to convert directly}} expected-note {{__bridge_transfer to transfer}} +} diff --git a/test/SemaObjC/arc-unbridged-cast.m b/test/SemaObjC/arc-unbridged-cast.m index 03c84cfce3..243edd613f 100644 --- a/test/SemaObjC/arc-unbridged-cast.m +++ b/test/SemaObjC/arc-unbridged-cast.m @@ -1,18 +1,72 @@ // // RUN: %clang_cc1 -triple x86_64-apple-darwin11 -fobjc-nonfragile-abi -fsyntax-only -fobjc-arc -verify %s -// rdar://9744349 typedef const struct __CFString * CFStringRef; -@interface I -@property CFStringRef P; +@interface Object +@property CFStringRef property; +- (CFStringRef) implicitProperty; +- (CFStringRef) newString; +- (CFStringRef) makeString; @end -@implementation I -@synthesize P; -- (id) Meth { - I* p1 = (id)[p1 P]; - id p2 = (__bridge_transfer id)[p1 P]; - id p3 = (__bridge I*)[p1 P]; - return (id) p1.P; +extern Object *object; + +// rdar://9744349 +id test0(void) { + id p1 = (id)[object property]; + id p2 = (__bridge_transfer id)[object property]; + id p3 = (__bridge id)[object property]; + return (id) object.property; +} + +// rdar://10140692 +CFStringRef unauditedString(void); +CFStringRef plusOneString(void) __attribute__((cf_returns_retained)); + +#pragma clang arc_cf_code_audited begin +CFStringRef auditedString(void); +CFStringRef auditedCreateString(void); +#pragma clang arc_cf_code_audited end + +void test1(int cond) { + id x; + x = (id) auditedString(); + x = (id) (cond ? auditedString() : (void*) 0); + x = (id) (cond ? (void*) 0 : auditedString()); + x = (id) (cond ? (CFStringRef) @"help" : auditedString()); + + x = (id) unauditedString(); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? unauditedString() : (void*) 0); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? (void*) 0 : unauditedString()); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? (CFStringRef) @"help" : unauditedString()); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + + x = (id) auditedCreateString(); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? auditedCreateString() : (void*) 0); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? (void*) 0 : auditedCreateString()); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + x = (id) (cond ? (CFStringRef) @"help" : auditedCreateString()); // expected-error {{requires a bridged cast}} expected-note {{use __bridge to}} expected-note {{use __bridge_transfer to}} + + x = (id) [object property]; + x = (id) (cond ? [object property] : (void*) 0); + x = (id) (cond ? (void*) 0 : [object property]); + x = (id) (cond ? (CFStringRef) @"help" : [object property]); + + x = (id) object.property; + x = (id) (cond ? object.property : (void*) 0); + x = (id) (cond ? (void*) 0 : object.property); + x = (id) (cond ? (CFStringRef) @"help" : object.property); + + x = (id) object.implicitProperty; + x = (id) (cond ? object.implicitProperty : (void*) 0); + x = (id) (cond ? (void*) 0 : object.implicitProperty); + x = (id) (cond ? (CFStringRef) @"help" : object.implicitProperty); + + x = (id) [object makeString]; + x = (id) (cond ? [object makeString] : (void*) 0); + x = (id) (cond ? (void*) 0 : [object makeString]); + x = (id) (cond ? (CFStringRef) @"help" : [object makeString]); + + x = (id) [object newString]; + x = (id) (cond ? [object newString] : (void*) 0); + x = (id) (cond ? (void*) 0 : [object newString]); + x = (id) (cond ? (CFStringRef) @"help" : [object newString]); // a bit questionable } -@end