DynamicTypePropagation.cpp revision 54918ba02ba900c0e0bb4fd3d749b6b1ac4e50a9
146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//== DynamicTypePropagation.cpp ----------------------------------- -*- C++ -*--=//
246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//
346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//                     The LLVM Compiler Infrastructure
446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//
546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// This file is distributed under the University of Illinois Open Source
646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// License. See LICENSE.TXT for details.
746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//
846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//===----------------------------------------------------------------------===//
946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//
10f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)// This checker defines the rules for dynamic type gathering and propagation.
1146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//
1246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)//===----------------------------------------------------------------------===//
1346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
1446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "ClangSACheckers.h"
1546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/Checker.h"
1646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/CheckerManager.h"
1746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
1846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
1946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h"
2046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
2146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)#include "clang/Basic/Builtins.h"
2246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
2346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)using namespace clang;
2446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)using namespace ento;
2546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
2646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)namespace {
2746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)class DynamicTypePropagation:
2846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    public Checker< check::PostCall,
2946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                    check::PostStmt<ImplicitCastExpr> > {
3046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE,
3146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                    CheckerContext &C) const;
3246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
3346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  /// \brief Return a better dynamic type if one can be derived from the cast.
3446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE,
3546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                 CheckerContext &C) const;
3646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)public:
3746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
3846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  void checkPostStmt(const ImplicitCastExpr *CastE, CheckerContext &C) const;
3946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)};
4046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
4146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
4246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void DynamicTypePropagation::checkPostCall(const CallEvent &Call,
4346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                           CheckerContext &C) const {
4446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // We can obtain perfect type info for return values from some calls.
4546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(&Call)) {
4646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
4746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // Get the returned value if it's a region.
4846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    SVal Result = C.getSVal(Call.getOriginExpr());
4946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    const MemRegion *RetReg = Result.getAsRegion();
5046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (!RetReg)
5146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return;
5246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
5346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    ProgramStateRef State = C.getState();
5446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
5546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    switch (Msg->getMethodFamily()) {
5646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    default:
5746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      break;
5846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
5946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // We assume that the type of the object returned by alloc and new are the
6046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // pointer to the object of the class specified in the receiver of the
6146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // message.
6246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    case OMF_alloc:
6346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    case OMF_new: {
6446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      // Get the type of object that will get created.
6546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const ObjCMessageExpr *MsgE = Msg->getOriginExpr();
6646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const ObjCObjectType *ObjTy = getObjectTypeForAllocAndNew(MsgE, C);
6746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (!ObjTy)
6846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return;
6946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      QualType DynResTy =
7046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                 C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0));
7146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      C.addTransition(State->setDynamicTypeInfo(RetReg, DynResTy, false));
7246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      break;
7346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
7446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    case OMF_init: {
7546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      // Assume, the result of the init method has the same dynamic type as
7646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      // the receiver and propagate the dynamic type info.
7746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      const MemRegion *RecReg = Msg->getReceiverSVal().getAsRegion();
7846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (!RecReg)
7946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        return;
8046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      DynamicTypeInfo RecDynType = State->getDynamicTypeInfo(RecReg);
8146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      C.addTransition(State->setDynamicTypeInfo(RetReg, RecDynType));
8246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      break;
8346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
8446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
8546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
8646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
8746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
881320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tuccivoid DynamicTypePropagation::checkPostStmt(const ImplicitCastExpr *CastE,
8946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                           CheckerContext &C) const {
9046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // We only track dynamic type info for regions.
9146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const MemRegion *ToR = C.getSVal(CastE).getAsRegion();
9246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!ToR)
931320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    return;
941320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci
951320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  switch (CastE->getCastKind()) {
961320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  default:
971320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    break;
981320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  case CK_BitCast:
991320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    // Only handle ObjCObjects for now.
1001320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    if (const Type *NewTy = getBetterObjCType(CastE, C))
1011320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci      C.addTransition(C.getState()->setDynamicTypeInfo(ToR, QualType(NewTy,0)));
1021320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci    break;
1031320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
1041320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  return;
1051320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci}
10646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
10746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)const ObjCObjectType *
10846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)DynamicTypePropagation::getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE,
10946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                                    CheckerContext &C) const {
1101320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (MsgE->getReceiverKind() == ObjCMessageExpr::Class) {
11146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (const ObjCObjectType *ObjTy
11246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          = MsgE->getClassReceiver()->getAs<ObjCObjectType>())
11346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return ObjTy;
11446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
11546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
11646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (MsgE->getReceiverKind() == ObjCMessageExpr::SuperClass) {
11746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (const ObjCObjectType *ObjTy
11846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          = MsgE->getSuperType()->getAs<ObjCObjectType>())
11946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      return ObjTy;
12046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
12146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
12246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const Expr *RecE = MsgE->getInstanceReceiver();
12346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!RecE)
12446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return 0;
12546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
12646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  RecE= RecE->IgnoreParenImpCasts();
12746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(RecE)) {
12846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    const StackFrameContext *SFCtx = C.getCurrentStackFrame();
129f8ee788a64d60abd8f2d742a5fdedde054ecd910Torne (Richard Coles)    // Are we calling [self alloc]? If this is self, get the type of the
13046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    // enclosing ObjC class.
13146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    if (DRE->getDecl() == SFCtx->getSelfDecl()) {
13246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(SFCtx->getDecl()))
13346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)        if (const ObjCObjectType *ObjTy =
13446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)            dyn_cast<ObjCObjectType>(MD->getClassInterface()->getTypeForDecl()))
13546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)          return ObjTy;
13646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    }
13746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  }
13846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return 0;
13946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
14046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
14146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Return a better dynamic type if one can be derived from the cast.
14246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// Compare the current dynamic type of the region and the new type to which we
14346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)// are casting. If the new type is lower in the inheritance hierarchy, pick it.
1441320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucciconst ObjCObjectPointerType *
14546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)DynamicTypePropagation::getBetterObjCType(const Expr *CastE,
14646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)                                          CheckerContext &C) const {
14746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const MemRegion *ToR = C.getSVal(CastE).getAsRegion();
14846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  assert(ToR);
14946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
15046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // Get the old and new types.
15146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCObjectPointerType *NewTy =
15246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)      CastE->getType()->getAs<ObjCObjectPointerType>();
15346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!NewTy)
15446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return 0;
15546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  QualType OldDTy = C.getState()->getDynamicTypeInfo(ToR).getType();
1561320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  if (OldDTy.isNull()) {
15746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return NewTy;
1581320f92c476a1ad9d19dba2a48c72b75566198e9Primiano Tucci  }
15946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCObjectPointerType *OldTy =
16046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    OldDTy->getAs<ObjCObjectPointerType>();
16146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (!OldTy)
16246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return 0;
16346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
16446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // Id the old type is 'id', the new one is more precise.
16546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (OldTy->isObjCIdType() && !NewTy->isObjCIdType())
16646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return NewTy;
16746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
16846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  // Return new if it's a subclass of old.
16946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCInterfaceDecl *ToI = NewTy->getInterfaceDecl();
17046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  const ObjCInterfaceDecl *FromI = OldTy->getInterfaceDecl();
17146d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  if (ToI && FromI && FromI->isSuperClassOf(ToI))
17246d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)    return NewTy;
17346d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
17446d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  return 0;
17546d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
17646d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)
17746d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)void ento::registerDynamicTypePropagation(CheckerManager &mgr) {
17846d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)  mgr.registerChecker<DynamicTypePropagation>();
17946d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)}
18046d4c2bc3267f3f028f39e7e311b0f89aba2e4fdTorne (Richard Coles)