// TODO: header template #include "clang/Analysis/Analyses/OSLog.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprObjC.h" #include "clang/Analysis/Analyses/FormatString.h" #include "clang/Basic/Builtins.h" #include "llvm/ADT/SmallBitVector.h" using namespace clang; using clang::analyze_os_log::OSLogBufferItem; using clang::analyze_os_log::OSLogBufferLayout; namespace { class OSLogFormatStringHandler : public analyze_format_string::FormatStringHandler { private: struct ArgData { const Expr *E = nullptr; Optional Kind; Optional Size; Optional Count; Optional Precision; Optional FieldWidth; unsigned char Flags = 0; }; SmallVector ArgsData; ArrayRef Args; OSLogBufferItem::Kind getKind(analyze_format_string::ConversionSpecifier::Kind K) { switch (K) { case clang::analyze_format_string::ConversionSpecifier::sArg: // "%s" return OSLogBufferItem::StringKind; case clang::analyze_format_string::ConversionSpecifier::SArg: // "%S" return OSLogBufferItem::WideStringKind; case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P" return OSLogBufferItem::PointerKind; case clang::analyze_format_string::ConversionSpecifier::ObjCObjArg: // "%@" return OSLogBufferItem::ObjCObjKind; case clang::analyze_format_string::ConversionSpecifier::PrintErrno: // "%m" return OSLogBufferItem::ErrnoKind; default: return OSLogBufferItem::ScalarKind; } } } public: OSLogFormatStringHandler(ArrayRef Args) : Args(Args) { ArgsData.reserve(Args.size()); } virtual bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS, const char *StartSpecifier, unsigned SpecifierLen) { if (!FS.consumesDataArgument() && FS.getConversionSpecifier().getKind() != clang::analyze_format_string::ConversionSpecifier::PrintErrno) return true; ArgsData.emplace_back(); unsigned ArgIndex = FS.getArgIndex(); if (ArgIndex < Args.size()) ArgsData.back().E = Args[ArgIndex]; // First get the Kind ArgsData.back().Kind = getKind(FS.getConversionSpecifier().getKind()); if (ArgsData.back().Kind != OSLogBufferItem::ErrnoKind && !ArgsData.back().E) { // missing argument ArgsData.pop_back(); return false; } switch (FS.getConversionSpecifier().getKind()) { case clang::analyze_format_string::ConversionSpecifier::sArg: // "%s" case clang::analyze_format_string::ConversionSpecifier::SArg: { // "%S" auto &precision = FS.getPrecision(); switch (precision.getHowSpecified()) { case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%s" break; case clang::analyze_format_string::OptionalAmount::Constant: // "%.16s" ArgsData.back().Size = precision.getConstantAmount(); break; case clang::analyze_format_string::OptionalAmount::Arg: // "%.*s" ArgsData.back().Count = Args[precision.getArgIndex()]; break; case clang::analyze_format_string::OptionalAmount::Invalid: return false; } break; } case clang::analyze_format_string::ConversionSpecifier::PArg: { // "%P" auto &precision = FS.getPrecision(); switch (precision.getHowSpecified()) { case clang::analyze_format_string::OptionalAmount::NotSpecified: // "%P" return false; // length must be supplied with pointer format specifier case clang::analyze_format_string::OptionalAmount::Constant: // "%.16P" ArgsData.back().Size = precision.getConstantAmount(); break; case clang::analyze_format_string::OptionalAmount::Arg: // "%.*P" ArgsData.back().Count = Args[precision.getArgIndex()]; break; case clang::analyze_format_string::OptionalAmount::Invalid: return false; } break; } default: if (FS.getPrecision().hasDataArgument()) { ArgsData.back().Precision = Args[FS.getPrecision().getArgIndex()]; } break; } if (FS.getFieldWidth().hasDataArgument()) { ArgsData.back().FieldWidth = Args[FS.getFieldWidth().getArgIndex()]; } if (FS.isPrivate()) { ArgsData.back().Flags |= OSLogBufferItem::IsPrivate; } if (FS.isPublic()) { ArgsData.back().Flags |= OSLogBufferItem::IsPublic; } return true; } void computeLayout(ASTContext &Ctx, OSLogBufferLayout &Layout) const { Layout.Items.clear(); for (auto &Data : ArgsData) { if (Data.FieldWidth) { CharUnits Size = Ctx.getTypeSizeInChars((*Data.FieldWidth)->getType()); Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, *Data.FieldWidth, Size, 0); } if (Data.Precision) { CharUnits Size = Ctx.getTypeSizeInChars((*Data.Precision)->getType()); Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, *Data.Precision, Size, 0); } if (Data.Count) { // "%.*P" has an extra "count" that we insert before the argument. CharUnits Size = Ctx.getTypeSizeInChars((*Data.Count)->getType()); Layout.Items.emplace_back(OSLogBufferItem::CountKind, *Data.Count, Size, 0); } if (Data.Size) Layout.Items.emplace_back(Ctx, CharUnits::fromQuantity(*Data.Size), Data.Flags); if (Data.Kind) { CharUnits Size; if (*Data.Kind == OSLogBufferItem::ErrnoKind) Size = CharUnits::Zero(); else Size = Ctx.getTypeSizeInChars(Data.E->getType()); Layout.Items.emplace_back(*Data.Kind, Data.E, Size, Data.Flags); } else { auto Size = Ctx.getTypeSizeInChars(Data.E->getType()); Layout.Items.emplace_back(OSLogBufferItem::ScalarKind, Data.E, Size, Data.Flags); } } } }; } // end anonymous namespace bool clang::analyze_os_log::computeOSLogBufferLayout( ASTContext &Ctx, const CallExpr *E, OSLogBufferLayout &Layout) { ArrayRef Args(E->getArgs(), E->getArgs() + E->getNumArgs()); const Expr *StringArg; ArrayRef VarArgs; switch (E->getBuiltinCallee()) { case Builtin::BI__builtin_os_log_format_buffer_size: assert(E->getNumArgs() >= 1 && "__builtin_os_log_format_buffer_size takes at least 1 argument"); StringArg = E->getArg(0); VarArgs = Args.slice(1); break; case Builtin::BI__builtin_os_log_format: assert(E->getNumArgs() >= 2 && "__builtin_os_log_format takes at least 2 arguments"); StringArg = E->getArg(1); VarArgs = Args.slice(2); break; default: llvm_unreachable("non-os_log builtin passed to computeOSLogBufferLayout"); } const StringLiteral *Lit = cast(StringArg->IgnoreParenCasts()); assert(Lit && (Lit->isAscii() || Lit->isUTF8())); StringRef Data = Lit->getString(); OSLogFormatStringHandler H(VarArgs); ParsePrintfString(H, Data.begin(), Data.end(), Ctx.getLangOpts(), Ctx.getTargetInfo(), /*isFreeBSDKPrintf*/ false); H.computeLayout(Ctx, Layout); return true; }