[cfe-commits] r58509 - in /cfe/trunk: include/clang/Basic/DiagnosticKinds.def lib/AST/Expr.cpp lib/Sema/Sema.h lib/Sema/SemaExprCXX.cpp lib/Sema/SemaInherit.cpp lib/Sema/SemaInherit.h lib/Sema/SemaOverload.cpp test/SemaCXX/condition.cpp test/SemaCXX/dynamic-cast.cpp test/SemaCXX/static-cast.cpp
Sebastian Redl
sebastian.redl at getdesigned.at
Mon Nov 3 07:28:24 PST 2008
Chris Lattner wrote:
> On Oct 31, 2008, at 7:43 AM, Sebastian Redl wrote:
>> URL: http://llvm.org/viewvc/llvm-project?rev=58509&view=rev
>> Log:
>> Implement semantic checking of static_cast and dynamic_cast.
>
> Very nice Sebastian, thank you for your contribution!
>
> Some thoughts below. I'm leaving the "deep" issues to Doug, but I'll
> nit pick some details :)
Thanks for the review. I'll address your comments below and update the
code as soon as I get my next piece of work out of the way.
>
>
>> +++ cfe/trunk/include/clang/Basic/DiagnosticKinds.def Fri Oct 31
>> 09:43:28 2008
>> @@ -1096,6 +1096,8 @@
>> "an extension")
>> DIAG(err_bad_reinterpret_cast_small_int, ERROR,
>> "cast from pointer to smaller type '%0' loses information")
>> +DIAG(err_bad_dynamic_cast_operand, ERROR,
>> + "'%0' is %1")
>
> This is really dangerous. I note that you use this like this:
> <snip>
> etc. The problem with this is that we'd like to keep diagnostics
> localizable. In practice, this means that the only thing that can be
> substituted into a "%" variable safely are 1) standard language
> keywords 2) things that are reprinted from the user's source code
> (e.g. types).
>
> Substituting in random text won't work, because we lose the ability to
> just localize the diagnostic catalog. Please split these into
> separate diagnostics for each case.
Will do. I expected this objection to be raised, but I wasn't entirely
sure about best practice, so I went with the way that was less work ;-)
>
>
>> ==============================================================================
>>
>> --- cfe/trunk/lib/AST/Expr.cpp (original)
>> +++ cfe/trunk/lib/AST/Expr.cpp Fri Oct 31 09:43:28 2008
>> @@ -1072,15 +1072,17 @@
>> /// integer constant expression with the value zero, or if this is
>> one that is
>> /// cast to void*.
>> bool Expr::isNullPointerConstant(ASTContext &Ctx) const {
>> - // Strip off a cast to void*, if it exists.
>> + // Strip off a cast to void*, if it exists. Except in C++.
>> if (const ExplicitCastExpr *CE = dyn_cast<ExplicitCastExpr>(this)) {
>> + if(!Ctx.getLangOptions().CPlusPlus) {
>
> It's a minor detail, but please add a space after the if. The
> prevailing heuristic is that we put a space between a keyword and open
> paren, but not a space between an identifier and open paren (as in
> function calls).
I know. It's just that my habit is to have no space there, and I have to
go over the code afterwards to find all these places. Sometimes I
overlook one.
>
>> +++ cfe/trunk/lib/Sema/Sema.h Fri Oct 31 09:43:28 2008
>> @@ -707,13 +707,22 @@
>> SourceLocation LParenLoc,
>> ExprTy *E,
>> SourceLocation RParenLoc);
>>
>> +private:
>> // Helpers for ActOnCXXCasts
>> - bool CastsAwayConstness(QualType SrcType, QualType DestType);
>> void CheckConstCast(SourceLocation OpLoc, Expr *&SrcExpr, QualType
>> DestType);
>> void CheckReinterpretCast(SourceLocation OpLoc, Expr *&SrcExpr,
>> QualType DestType);
>> void CheckStaticCast(SourceLocation OpLoc, Expr *&SrcExpr, QualType
>> DestType);
>> + void CheckDynamicCast(SourceLocation OpLoc, Expr *&SrcExpr,
>> + QualType DestType, const SourceRange
>> &DestRange);
>> + bool CastsAwayConstness(QualType SrcType, QualType DestType);
>> + bool IsStaticReferenceDowncast(Expr *SrcExpr, QualType DestType);
>> + bool IsStaticPointerDowncast(QualType SrcType, QualType DestType);
>> + bool IsStaticDowncast(QualType SrcType, QualType DestType);
>> + ImplicitConversionSequence TryDirectInitialization(Expr *SrcExpr,
>> + QualType
>> DestType);
>
> Instead of these being methods on Sema, would it be reasonable to make
> them static functions that take Sema as an argument? The benefit of
> doing this is to reduce the number of methods on Sema. If it is
> outrageously ugly to do this, please don't, but as a general principle
> it is good to prefer static functions where possible.
I believe that should be no problem at all. I'll have to check the
functions individually.
>
>> DestType = Context.getCanonicalType(DestType);
>
> Does this code really need to strip down to canonical types? Your
> code seems very clean and uses predicates like ->getAsFoo and
> ->isFoo() consistently. If it is safe, it would be nice to just use
> these predicates consistently and avoid canonicalizing them. The
> major benefit of this is that it means you don't need an "orig" type
> as well as the canonical one.
>
> If you decide that you need to canonicalize, I'd prefer the code to
> keep the original type named "DestType" (for example) and name the
> canonical type "CanDestType" or "DestTypeC" or something like that, to
> make it obvious to the reader that the type has been canonicalized.
OK, I'll check that as well.
>
>
>> + // C++ 5.2.9p5, reference downcast.
>> + // See the function for details.
>> + if (IsStaticReferenceDowncast(SrcExpr, DestType)) {
>> + return;
>> + }
>> +
>> + // C++ 5.2.9p2: An expression e can be explicitly converted to a
>> type T
>> + // [...] if the declaration "T t(e);" is well-formed, [...].
>> + ImplicitConversionSequence ICS = TryDirectInitialization(SrcExpr,
>> DestType);
>> + if (ICS.ConversionKind !=
>> ImplicitConversionSequence::BadConversion) {
>> + if (ICS.ConversionKind ==
>> ImplicitConversionSequence::StandardConversion &&
>> + ICS.Standard.First != ICK_Identity)
>> + {
>
> Sometimes you're putting open braces on their own line, sometimes on
> the same line as the if (likewise for method bodies). I personally
> don't care which, but consistency is good. Most of the code puts the
> braces on the same line of the if/method, so it's probably best to go
> with that.
>
>> + if (SrcExpr->isLvalue(Context) != Expr::LV_Valid) {
>> + return false;
>> + }
>
> Another minor issue: generally we avoid braces for really simple if
> conditions like this. If you really prefer them, they are ok and I'm
> fine with keeping them, but if they don't add value, I have a slight
> preference to elide them.
Actually, my system is complex, but consistent. I put the brace on the
same line unless I had to break the condition across lines. I find that
having the brace on the same line when the condition is multiple lines
long blurs the distinction between the condition and the statement body.
As for eliding braces, I never do that. I simply like being able to put
another statement in there (usually debug output) without having to
check if there is a brace first.
I've got no problem with changing that stuff, though.
>
>> + DestType = Context.getCanonicalType(DestType);
>> + const ReferenceType *DestReference = DestType->getAsReferenceType();
>> + if (!DestReference) {
>> + return false;
>> + }
>> + QualType DestPointee = DestReference->getPointeeType();
>> +
>> + QualType SrcType = Context.getCanonicalType(SrcExpr->getType());
>> +
>> + return IsStaticDowncast(SrcType, DestPointee);
>
> Instead of requiring all the callers to pass in canonical types, it
> would be cleaner (from an interface perspective) to make
> IsStaticDowncast canonicalize the type if it really needs to. This
> makes the caller simpler and makes the function more general. It's
> even better to just not require canonical types of course ;-)
I'll have to check if I require canonical types there. However, the
function in question is really intended to only be called from two
callers, which already use canonical types, and it seems a waste to
canonicalize those types again.
>
>> +/// IsStaticDowncast - Common functionality of
>> IsStaticReferenceDowncast and
>> +/// IsStaticPointerDowncast. Tests whether a static downcast from
>> SrcType to
>> +/// DestType, both of which must be canonical, is possible and allowed.
>> +bool
>> +Sema::IsStaticDowncast(QualType SrcType, QualType DestType)
>> +{
>> + assert(SrcType->isCanonical());
>> + assert(DestType->isCanonical());
>> +
>> + if (!DestType->isRecordType()) {
>> + return false;
>> + }
>> +
>> + if (!SrcType->isRecordType()) {
>> + return false;
>> + }
>
> This is another very personal issue, but I'd generally prefer to merge
> these into the same if, and use "||". There should also be a small
> comment explaining why you reject non record types.
I'll merge them. As for the comment, I think it's unnecessary. The
function checks whether this is a cast in a class hierarchy - it should
be completely obvious why these need to be records.
>
>> + // Assumptions to this point.
>> + assert(DestPointer || DestReference);
>> + assert(DestRecord || DestPointee->isVoidType());
>> + assert(SrcRecord);
>
> This is a self-evident/redundant comment :). It would be better to
> enhance the assertions with a message explaining why these must hold,
> e.g.:
>
> assert(SrcRecord && "Non-records were rejected above")
>
> or something like that. Short messages like this are very useful when
> someone else is debugging a problem in your code, because it makes it
> more obvious *why* you thought the condition should hold.
OK.
>
>> bool Sema::IsIntegralPromotion(Expr *From, QualType FromType,
>> QualType ToType)
>> {
>> const BuiltinType *To = ToType->getAsBuiltinType();
>> + if (!To) {
>> + return false;
>> + }
>
> This deserves a comment. Maybe something like "all integers are
> builtin types"?
OK.
As I said, it will be a few days before I commit fixes to these. I have
some partial work in that area, and separating that stuff is so annoying
in SVN.
Sebastian
More information about the cfe-commits
mailing list