From: Argyrios Kyrtzidis Date: Mon, 7 Nov 2011 18:46:46 +0000 (+0000) Subject: [arcmt] In GC, handle (assign) @properties. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b0e1e121b32a9a04b39f1b77b3068bce2f3be05a;p=clang [arcmt] In GC, handle (assign) @properties. -Move __strong/__weak added to a property type to the property attribute, e.g. "@property (assign) __weak Foo *prop;" --> "@property (weak) Foo *prop;" -Remove (assign) in a property so that it becomes strong-by-default in ARC. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@143979 91177308-0d34-0410-b5e6-96231b3b80d8 --- diff --git a/lib/ARCMigrate/TransGCAttrs.cpp b/lib/ARCMigrate/TransGCAttrs.cpp index 70157aba3d..8f3926fbbc 100644 --- a/lib/ARCMigrate/TransGCAttrs.cpp +++ b/lib/ARCMigrate/TransGCAttrs.cpp @@ -13,6 +13,7 @@ #include "clang/Basic/SourceManager.h" #include "clang/Analysis/Support/SaveAndRestore.h" #include "clang/Sema/SemaDiagnostic.h" +#include "llvm/ADT/TinyPtrVector.h" using namespace clang; using namespace arcmt; @@ -24,11 +25,14 @@ namespace { class GCAttrsCollector : public RecursiveASTVisitor { MigrationContext &MigrateCtx; bool FullyMigratable; + std::vector &AllProps; typedef RecursiveASTVisitor base; public: - explicit GCAttrsCollector(MigrationContext &ctx) - : MigrateCtx(ctx), FullyMigratable(false) { } + GCAttrsCollector(MigrationContext &ctx, + std::vector &AllProps) + : MigrateCtx(ctx), FullyMigratable(false), + AllProps(AllProps) { } bool shouldWalkTypesOfTypeLocs() const { return false; } @@ -41,13 +45,14 @@ public: if (!D || D->isImplicit()) return true; - bool migratable = isMigratable(D); - SaveAndRestore Save(FullyMigratable, migratable); + SaveAndRestore Save(FullyMigratable, isMigratable(D)); - if (DeclaratorDecl *DD = dyn_cast(D)) - lookForAttribute(DD, DD->getTypeSourceInfo()); - else if (ObjCPropertyDecl *PropD = dyn_cast(D)) + if (ObjCPropertyDecl *PropD = dyn_cast(D)) { lookForAttribute(PropD, PropD->getTypeSourceInfo()); + AllProps.push_back(PropD); + } else if (DeclaratorDecl *DD = dyn_cast(D)) { + lookForAttribute(DD, DD->getTypeSourceInfo()); + } return base::TraverseDecl(D); } @@ -56,11 +61,14 @@ public: return; TypeLoc TL = TInfo->getTypeLoc(); while (TL) { - if (const AttributedTypeLoc *Attr = dyn_cast(&TL)) { + if (const QualifiedTypeLoc *QL = dyn_cast(&TL)) { + TL = QL->getUnqualifiedLoc(); + } else if (const AttributedTypeLoc * + Attr = dyn_cast(&TL)) { if (handleAttr(*Attr, D)) break; TL = Attr->getModifiedLoc(); - } if (const ArrayTypeLoc *Arr = dyn_cast(&TL)) { + } else if (const ArrayTypeLoc *Arr = dyn_cast(&TL)) { TL = Arr->getElementLoc(); } else if (const PointerTypeLoc *PT = dyn_cast(&TL)) { TL = PT->getPointeeLoc(); @@ -108,10 +116,6 @@ public: Attr.ModifiedType = TL.getModifiedLoc().getType(); Attr.Dcl = D; Attr.FullyMigratable = FullyMigratable; - - if (ObjCPropertyDecl *PD = dyn_cast_or_null(D)) - MigrateCtx.PropGCAttrs[PD] = MigrateCtx.GCAttrs.size() - 1; - return true; } @@ -125,15 +129,8 @@ public: if (FunctionDecl *FD = dyn_cast(D)) return FD->hasBody(); - if (ObjCContainerDecl *ContD = dyn_cast(D)) { - if (ObjCInterfaceDecl *ID = dyn_cast(ContD)) - return ID->getImplementation() != 0; - if (ObjCCategoryDecl *CD = dyn_cast(ContD)) - return CD->getImplementation() != 0; - if (isa(ContD)) - return true; - return false; - } + if (ObjCContainerDecl *ContD = dyn_cast(D)) + return hasObjCImpl(ContD); if (CXXRecordDecl *RD = dyn_cast(D)) { for (CXXRecordDecl::method_iterator @@ -147,6 +144,21 @@ public: return isMigratable(cast(D->getDeclContext())); } + static bool hasObjCImpl(Decl *D) { + if (!D) + return false; + if (ObjCContainerDecl *ContD = dyn_cast(D)) { + if (ObjCInterfaceDecl *ID = dyn_cast(ContD)) + return ID->getImplementation() != 0; + if (ObjCCategoryDecl *CD = dyn_cast(ContD)) + return CD->getImplementation() != 0; + if (isa(ContD)) + return true; + return false; + } + return false; + } + bool isInMainFile(Decl *D) { if (!D) return false; @@ -214,8 +226,7 @@ static void checkWeakGCAttrs(MigrationContext &MigrateCtx) { for (unsigned i = 0, e = MigrateCtx.GCAttrs.size(); i != e; ++i) { MigrationContext::GCAttrOccurrence &Attr = MigrateCtx.GCAttrs[i]; - if (Attr.Kind == MigrationContext::GCAttrOccurrence::Weak && - Attr.FullyMigratable) { + if (Attr.Kind == MigrationContext::GCAttrOccurrence::Weak) { if (Attr.ModifiedType.isNull() || !Attr.ModifiedType->isObjCRetainableType()) continue; @@ -231,13 +242,113 @@ static void checkWeakGCAttrs(MigrationContext &MigrateCtx) { } } +typedef llvm::TinyPtrVector IndivPropsTy; + +static void checkAllAtProps(MigrationContext &MigrateCtx, + SourceLocation AtLoc, + IndivPropsTy &IndProps) { + if (IndProps.empty()) + return; + + for (IndivPropsTy::iterator + PI = IndProps.begin(), PE = IndProps.end(); PI != PE; ++PI) { + QualType T = (*PI)->getType(); + if (T.isNull() || !T->isObjCRetainableType()) + return; + } + + SmallVector, 4> ATLs; + bool hasWeak = false, hasStrong = false; + for (IndivPropsTy::iterator + PI = IndProps.begin(), PE = IndProps.end(); PI != PE; ++PI) { + ObjCPropertyDecl *PD = *PI; + TypeSourceInfo *TInfo = PD->getTypeSourceInfo(); + if (!TInfo) + return; + TypeLoc TL = TInfo->getTypeLoc(); + if (AttributedTypeLoc *ATL = dyn_cast(&TL)) { + ATLs.push_back(std::make_pair(*ATL, PD)); + if (TInfo->getType().getObjCLifetime() == Qualifiers::OCL_Weak) { + hasWeak = true; + } else if (TInfo->getType().getObjCLifetime() == Qualifiers::OCL_Strong) + hasStrong = true; + else + return; + } + } + if (ATLs.empty()) + return; + if (hasWeak && hasStrong) + return; + + TransformActions &TA = MigrateCtx.Pass.TA; + Transaction Trans(TA); + + if (GCAttrsCollector::hasObjCImpl( + cast(IndProps.front()->getDeclContext()))) { + if (hasWeak) + MigrateCtx.AtPropsWeak.insert(AtLoc.getRawEncoding()); + + } else { + StringRef toAttr = "strong"; + if (hasWeak) { + if (canApplyWeak(MigrateCtx.Pass.Ctx, IndProps.front()->getType(), + /*AllowOnUnkwownClass=*/true)) + toAttr = "weak"; + else + toAttr = "unsafe_unretained"; + } + if (!MigrateCtx.rewritePropertyAttribute("assign", toAttr, AtLoc)) { + return; + } + } + + for (unsigned i = 0, e = ATLs.size(); i != e; ++i) { + SourceLocation Loc = ATLs[i].first.getAttrNameLoc(); + if (Loc.isMacroID()) + Loc = MigrateCtx.Pass.Ctx.getSourceManager() + .getImmediateExpansionRange(Loc).first; + TA.remove(Loc); + TA.clearDiagnostic(diag::err_objc_property_attr_mutually_exclusive, AtLoc); + TA.clearDiagnostic(diag::err_arc_inconsistent_property_ownership, + ATLs[i].second->getLocation()); + } +} + +static void checkAllProps(MigrationContext &MigrateCtx, + std::vector &AllProps) { + typedef llvm::TinyPtrVector IndivPropsTy; + llvm::DenseMap AtProps; + + for (unsigned i = 0, e = AllProps.size(); i != e; ++i) { + ObjCPropertyDecl *PD = AllProps[i]; + if (PD->getPropertyAttributesAsWritten() & + ObjCPropertyDecl::OBJC_PR_assign) { + SourceLocation AtLoc = PD->getAtLoc(); + if (AtLoc.isInvalid()) + continue; + unsigned RawAt = AtLoc.getRawEncoding(); + AtProps[RawAt].push_back(PD); + } + } + + for (llvm::DenseMap::iterator + I = AtProps.begin(), E = AtProps.end(); I != E; ++I) { + SourceLocation AtLoc = SourceLocation::getFromRawEncoding(I->first); + IndivPropsTy &IndProps = I->second; + checkAllAtProps(MigrateCtx, AtLoc, IndProps); + } +} + void GCAttrsTraverser::traverseTU(MigrationContext &MigrateCtx) { - GCAttrsCollector(MigrateCtx).TraverseDecl( + std::vector AllProps; + GCAttrsCollector(MigrateCtx, AllProps).TraverseDecl( MigrateCtx.Pass.Ctx.getTranslationUnitDecl()); clearRedundantStrongs(MigrateCtx); errorForGCAttrsOnNonObjC(MigrateCtx); checkWeakGCAttrs(MigrateCtx); + checkAllProps(MigrateCtx, AllProps); } void MigrationContext::dumpGCAttrs() { diff --git a/lib/ARCMigrate/TransProperties.cpp b/lib/ARCMigrate/TransProperties.cpp index f9f642ffc8..050512fbbe 100644 --- a/lib/ARCMigrate/TransProperties.cpp +++ b/lib/ARCMigrate/TransProperties.cpp @@ -44,6 +44,7 @@ using namespace trans; namespace { class PropertiesRewriter { + MigrationContext &MigrateCtx; MigrationPass &Pass; ObjCImplementationDecl *CurImplD; @@ -51,7 +52,7 @@ class PropertiesRewriter { PropAction_None, PropAction_RetainToStrong, PropAction_RetainRemoved, - PropAction_AssignToStrong, + PropAction_AssignRemoved, PropAction_AssignRewritten, PropAction_MaybeAddStrong, PropAction_MaybeAddWeakOrUnsafe @@ -71,7 +72,8 @@ class PropertiesRewriter { llvm::DenseMap ActionOnProp; public: - PropertiesRewriter(MigrationPass &pass) : Pass(pass) { } + explicit PropertiesRewriter(MigrationContext &MigrateCtx) + : MigrateCtx(MigrateCtx), Pass(MigrateCtx.Pass) { } static void collectProperties(ObjCContainerDecl *D, AtPropDeclsTy &AtProps) { for (ObjCInterfaceDecl::prop_iterator @@ -167,9 +169,8 @@ private: case PropAction_RetainRemoved: removeAttribute("retain", atLoc); return; - case PropAction_AssignToStrong: - rewriteAttribute("assign", "strong", atLoc); - return; + case PropAction_AssignRemoved: + return removeAssignForDefaultStrong(props, atLoc); case PropAction_AssignRewritten: return rewriteAssign(props, atLoc); case PropAction_MaybeAddStrong: @@ -205,21 +206,39 @@ private: return doPropAction(PropAction_RetainRemoved, props, atLoc); } + bool HasIvarAssignedAPlusOneObject = hasIvarAssignedAPlusOneObject(props); + if (propAttrs & ObjCPropertyDecl::OBJC_PR_assign) { - if (hasIvarAssignedAPlusOneObject(props)) { - return doPropAction(PropAction_AssignToStrong, props, atLoc); + if (HasIvarAssignedAPlusOneObject || + (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) { + return doPropAction(PropAction_AssignRemoved, props, atLoc); } return doPropAction(PropAction_AssignRewritten, props, atLoc); } - if (hasIvarAssignedAPlusOneObject(props)) + if (HasIvarAssignedAPlusOneObject || + (Pass.isGCMigration() && !hasGCWeak(props, atLoc))) return doPropAction(PropAction_MaybeAddStrong, props, atLoc); return doPropAction(PropAction_MaybeAddWeakOrUnsafe, props, atLoc); } + void removeAssignForDefaultStrong(PropsTy &props, + SourceLocation atLoc) const { + removeAttribute("retain", atLoc); + if (!removeAttribute("assign", atLoc)) + return; + + for (PropsTy::iterator I = props.begin(), E = props.end(); I != E; ++I) { + if (I->ImplD) + Pass.TA.clearDiagnostic(diag::err_arc_assign_property_ownership, + I->ImplD->getLocation()); + } + } + void rewriteAssign(PropsTy &props, SourceLocation atLoc) const { - bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props)); + bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), + /*AllowOnUnknownClass=*/Pass.isGCMigration()); bool rewroteAttr = rewriteAttribute("assign", canUseWeak ? "weak" : "unsafe_unretained", @@ -241,7 +260,8 @@ private: SourceLocation atLoc) const { ObjCPropertyDecl::PropertyAttributeKind propAttrs = getPropertyAttrs(props); - bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props)); + bool canUseWeak = canApplyWeak(Pass.Ctx, getPropertyType(props), + /*AllowOnUnknownClass=*/Pass.isGCMigration()); if (!(propAttrs & ObjCPropertyDecl::OBJC_PR_readonly) || !hasAllIvarsBacked(props)) { bool addedAttr = addAttribute(canUseWeak ? "weak" : "unsafe_unretained", @@ -289,85 +309,7 @@ private: bool rewriteAttribute(StringRef fromAttr, StringRef toAttr, SourceLocation atLoc) const { - if (atLoc.isMacroID()) - return false; - - SourceManager &SM = Pass.Ctx.getSourceManager(); - - // Break down the source location. - std::pair locInfo = SM.getDecomposedLoc(atLoc); - - // Try to load the file buffer. - bool invalidTemp = false; - StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); - if (invalidTemp) - return false; - - const char *tokenBegin = file.data() + locInfo.second; - - // Lex from the start of the given location. - Lexer lexer(SM.getLocForStartOfFile(locInfo.first), - Pass.Ctx.getLangOptions(), - file.begin(), tokenBegin, file.end()); - Token tok; - lexer.LexFromRawLexer(tok); - if (tok.isNot(tok::at)) return false; - lexer.LexFromRawLexer(tok); - if (tok.isNot(tok::raw_identifier)) return false; - if (StringRef(tok.getRawIdentifierData(), tok.getLength()) - != "property") - return false; - lexer.LexFromRawLexer(tok); - if (tok.isNot(tok::l_paren)) return false; - - Token BeforeTok = tok; - Token AfterTok; - AfterTok.startToken(); - SourceLocation AttrLoc; - - lexer.LexFromRawLexer(tok); - if (tok.is(tok::r_paren)) - return false; - - while (1) { - if (tok.isNot(tok::raw_identifier)) return false; - StringRef ident(tok.getRawIdentifierData(), tok.getLength()); - if (ident == fromAttr) { - if (!toAttr.empty()) { - Pass.TA.replaceText(tok.getLocation(), fromAttr, toAttr); - return true; - } - // We want to remove the attribute. - AttrLoc = tok.getLocation(); - } - - do { - lexer.LexFromRawLexer(tok); - if (AttrLoc.isValid() && AfterTok.is(tok::unknown)) - AfterTok = tok; - } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren)); - if (tok.is(tok::r_paren)) - break; - if (AttrLoc.isInvalid()) - BeforeTok = tok; - lexer.LexFromRawLexer(tok); - } - - if (toAttr.empty() && AttrLoc.isValid() && AfterTok.isNot(tok::unknown)) { - // We want to remove the attribute. - if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::r_paren)) { - Pass.TA.remove(SourceRange(BeforeTok.getLocation(), - AfterTok.getLocation())); - } else if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::comma)) { - Pass.TA.remove(SourceRange(AttrLoc, AfterTok.getLocation())); - } else { - Pass.TA.remove(SourceRange(BeforeTok.getLocation(), AttrLoc)); - } - - return true; - } - - return false; + return MigrateCtx.rewritePropertyAttribute(fromAttr, toAttr, atLoc); } bool addAttribute(StringRef attr, SourceLocation atLoc) const { @@ -482,6 +424,15 @@ private: return true; } + // \brief Returns true if all declarations in the @property have GC __weak. + bool hasGCWeak(PropsTy &props, SourceLocation atLoc) const { + if (!Pass.isGCMigration()) + return false; + if (props.empty()) + return false; + return MigrateCtx.AtPropsWeak.count(atLoc.getRawEncoding()); + } + bool isUserDeclared(ObjCIvarDecl *ivarD) const { return ivarD && !ivarD->getSynthesize(); } @@ -513,23 +464,10 @@ private: } }; -class ImplementationChecker : - public RecursiveASTVisitor { - MigrationPass &Pass; - -public: - ImplementationChecker(MigrationPass &pass) : Pass(pass) { } - - bool TraverseObjCImplementationDecl(ObjCImplementationDecl *D) { - PropertiesRewriter(Pass).doTransform(D); - return true; - } -}; - } // anonymous namespace void PropertyRewriteTraverser::traverseObjCImplementation( ObjCImplementationContext &ImplCtx) { - PropertiesRewriter(ImplCtx.getMigrationContext().Pass) + PropertiesRewriter(ImplCtx.getMigrationContext()) .doTransform(ImplCtx.getImplementationDecl()); } diff --git a/lib/ARCMigrate/Transforms.cpp b/lib/ARCMigrate/Transforms.cpp index a736c2419e..86812a5f87 100644 --- a/lib/ARCMigrate/Transforms.cpp +++ b/lib/ARCMigrate/Transforms.cpp @@ -70,6 +70,9 @@ bool trans::canApplyWeak(ASTContext &Ctx, QualType type, return false; QualType T = type; + if (T.isNull()) + return false; + while (const PointerType *ptr = T->getAs()) T = ptr->getPointeeType(); if (const ObjCObjectPointerType *ObjT = T->getAs()) { @@ -357,6 +360,90 @@ bool MigrationContext::isGCOwnedNonObjC(QualType T) { return false; } +bool MigrationContext::rewritePropertyAttribute(StringRef fromAttr, + StringRef toAttr, + SourceLocation atLoc) { + if (atLoc.isMacroID()) + return false; + + SourceManager &SM = Pass.Ctx.getSourceManager(); + + // Break down the source location. + std::pair locInfo = SM.getDecomposedLoc(atLoc); + + // Try to load the file buffer. + bool invalidTemp = false; + StringRef file = SM.getBufferData(locInfo.first, &invalidTemp); + if (invalidTemp) + return false; + + const char *tokenBegin = file.data() + locInfo.second; + + // Lex from the start of the given location. + Lexer lexer(SM.getLocForStartOfFile(locInfo.first), + Pass.Ctx.getLangOptions(), + file.begin(), tokenBegin, file.end()); + Token tok; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::at)) return false; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::raw_identifier)) return false; + if (StringRef(tok.getRawIdentifierData(), tok.getLength()) + != "property") + return false; + lexer.LexFromRawLexer(tok); + if (tok.isNot(tok::l_paren)) return false; + + Token BeforeTok = tok; + Token AfterTok; + AfterTok.startToken(); + SourceLocation AttrLoc; + + lexer.LexFromRawLexer(tok); + if (tok.is(tok::r_paren)) + return false; + + while (1) { + if (tok.isNot(tok::raw_identifier)) return false; + StringRef ident(tok.getRawIdentifierData(), tok.getLength()); + if (ident == fromAttr) { + if (!toAttr.empty()) { + Pass.TA.replaceText(tok.getLocation(), fromAttr, toAttr); + return true; + } + // We want to remove the attribute. + AttrLoc = tok.getLocation(); + } + + do { + lexer.LexFromRawLexer(tok); + if (AttrLoc.isValid() && AfterTok.is(tok::unknown)) + AfterTok = tok; + } while (tok.isNot(tok::comma) && tok.isNot(tok::r_paren)); + if (tok.is(tok::r_paren)) + break; + if (AttrLoc.isInvalid()) + BeforeTok = tok; + lexer.LexFromRawLexer(tok); + } + + if (toAttr.empty() && AttrLoc.isValid() && AfterTok.isNot(tok::unknown)) { + // We want to remove the attribute. + if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::r_paren)) { + Pass.TA.remove(SourceRange(BeforeTok.getLocation(), + AfterTok.getLocation())); + } else if (BeforeTok.is(tok::l_paren) && AfterTok.is(tok::comma)) { + Pass.TA.remove(SourceRange(AttrLoc, AfterTok.getLocation())); + } else { + Pass.TA.remove(SourceRange(BeforeTok.getLocation(), AttrLoc)); + } + + return true; + } + + return false; +} + void MigrationContext::traverse(TranslationUnitDecl *TU) { for (traverser_iterator I = traversers_begin(), E = traversers_end(); I != E; ++I) diff --git a/lib/ARCMigrate/Transforms.h b/lib/ARCMigrate/Transforms.h index 89716ad61d..49fd0f726d 100644 --- a/lib/ARCMigrate/Transforms.h +++ b/lib/ARCMigrate/Transforms.h @@ -96,8 +96,9 @@ public: std::vector GCAttrs; llvm::DenseSet AttrSet; - /// \brief Map of property decl to the index in the GCAttrs vector. - llvm::DenseMap PropGCAttrs; + /// \brief Set of raw '@' locations for 'assign' properties group that contain + /// GC __weak. + llvm::DenseSet AtPropsWeak; explicit MigrationContext(MigrationPass &pass) : Pass(pass) {} ~MigrationContext(); @@ -111,6 +112,8 @@ public: } bool isGCOwnedNonObjC(QualType T); + bool rewritePropertyAttribute(StringRef fromAttr, StringRef toAttr, + SourceLocation atLoc); void traverse(TranslationUnitDecl *TU); diff --git a/test/ARCMT/GC.m b/test/ARCMT/GC.m index b3ba2a4211..e49bca8fa3 100644 --- a/test/ARCMT/GC.m +++ b/test/ARCMT/GC.m @@ -48,3 +48,22 @@ __attribute__((objc_arc_weak_reference_unavailable)) __weak QQ *q; } @end + +@interface I3 +@property (assign) I3 *__weak pw1, *__weak pw2; +@property (assign) I3 *__strong ps; +@property (assign) I3 * pds; +@end + +@interface I4Impl { + I4Impl *pds2; +} +@property (assign) I4Impl *__weak pw1, *__weak pw2; +@property (assign) I4Impl *__strong ps; +@property (assign) I4Impl * pds; +@property (assign) I4Impl * pds2; +@end + +@implementation I4Impl +@synthesize pw1, pw2, ps, pds, pds2; +@end diff --git a/test/ARCMT/GC.m.result b/test/ARCMT/GC.m.result index 405219ff3a..67a70e3dd2 100644 --- a/test/ARCMT/GC.m.result +++ b/test/ARCMT/GC.m.result @@ -43,3 +43,22 @@ __attribute__((objc_arc_weak_reference_unavailable)) __unsafe_unretained QQ *q; } @end + +@interface I3 +@property (weak) I3 * pw1, * pw2; +@property (strong) I3 * ps; +@property (assign) I3 * pds; +@end + +@interface I4Impl { + I4Impl *pds2; +} +@property (weak) I4Impl * pw1, * pw2; +@property I4Impl * ps; +@property I4Impl * pds; +@property I4Impl * pds2; +@end + +@implementation I4Impl +@synthesize pw1, pw2, ps, pds, pds2; +@end