diff options
Diffstat (limited to 'compilerplugins/clang/toolslong.cxx')
-rw-r--r-- | compilerplugins/clang/toolslong.cxx | 653 |
1 files changed, 653 insertions, 0 deletions
diff --git a/compilerplugins/clang/toolslong.cxx b/compilerplugins/clang/toolslong.cxx new file mode 100644 index 000000000..35a7223bd --- /dev/null +++ b/compilerplugins/clang/toolslong.cxx @@ -0,0 +1,653 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <algorithm> +#include <cassert> +#include <limits> +#include <map> +#include <string> +#include <iostream> + +#include "clang/AST/Attr.h" +#include "clang/Basic/Builtins.h" + +#include "config_clang.h" + +#include "check.hxx" +#include "plugin.hxx" + +namespace +{ +bool isLong(QualType type) +{ + type = type.getNonReferenceType(); + // ignore sal_Int64 + if (type->getAs<TypedefType>()) + return false; + // some parts of the STL have ::difference_type => long + if (type->getAs<AutoType>() || type->getAs<DecltypeType>()) + return false; + if (type->isSpecificBuiltinType(BuiltinType::Kind::Long)) + return true; + auto arrayType = type->getAsArrayTypeUnsafe(); + if (arrayType) + return isLong(arrayType->getElementType()); + if (type->isPointerType()) + return isLong(type->getPointeeType()); + return false; +} + +enum class OverrideKind +{ + NO, + YES, + MAYBE +}; + +OverrideKind getOverrideKind(FunctionDecl const* decl) +{ + CXXMethodDecl const* m = dyn_cast<CXXMethodDecl>(decl); + if (m == nullptr) + return OverrideKind::NO; + if (m->size_overridden_methods() != 0 || m->hasAttr<OverrideAttr>()) + return OverrideKind::YES; + if (!dyn_cast<CXXRecordDecl>(m->getDeclContext())->hasAnyDependentBases()) + return OverrideKind::NO; + return OverrideKind::MAYBE; +} + +class ToolsLong : public loplugin::FilteringRewritePlugin<ToolsLong> +{ +public: + explicit ToolsLong(loplugin::InstantiationData const& data) + : loplugin::FilteringRewritePlugin<ToolsLong>(data) + { + } + + virtual void run() override; + + bool VisitCStyleCastExpr(CStyleCastExpr* expr); + + bool VisitCXXStaticCastExpr(CXXStaticCastExpr* expr); + + bool VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr); + + bool WalkUpFromParmVarDecl(ParmVarDecl const* decl); + bool VisitParmVarDecl(ParmVarDecl const* decl); + + bool WalkUpFromVarDecl(VarDecl const* decl); + bool VisitVarDecl(VarDecl const* decl); + + bool WalkUpFromFieldDecl(FieldDecl const* decl); + bool VisitFieldDecl(FieldDecl const* decl); + + bool WalkUpFromFunctionDecl(FunctionDecl const* decl); + bool VisitFunctionDecl(FunctionDecl const* decl); + + bool VisitCallExpr(CallExpr const* expr); + +private: + bool rewrite(SourceLocation location); + bool isExcludedFile(SourceLocation spellingLocation) const; + /** sort by the reverse of source order, so we can do replacing from the end of the file backwards, + which means we reduce the chances of having overlapping changes. */ + template <class T> + std::vector<std::pair<T, bool>> reverseSourceOrder(std::map<T, bool> const& map) const + { + std::vector<std::pair<T, bool>> vec(map.begin(), map.end()); + std::sort(vec.begin(), vec.end(), + [&](std::pair<T, bool> const& lhs, std::pair<T, bool> const& rhs) { + return compiler.getSourceManager().getCharacterData(lhs.first->getBeginLoc()) + > compiler.getSourceManager().getCharacterData( + rhs.first->getBeginLoc()); + }); + return vec; + } + + std::map<VarDecl const*, bool> varDecls_; + std::map<FieldDecl const*, bool> fieldDecls_; + std::map<ParmVarDecl const*, bool> parmVarDecls_; + std::map<FunctionDecl const*, bool> functionDecls_; + std::map<CXXStaticCastExpr const*, bool> staticCasts_; + std::map<CXXFunctionalCastExpr const*, bool> functionalCasts_; +}; + +void ToolsLong::run() +{ + if (!compiler.getLangOpts().CPlusPlus) + return; + + StringRef fn(handler.getMainFileName()); + // sberg says this is fine + if (loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/framegrabber.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/manager.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/player.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/avmedia/source/win/window.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/drivers/ado/AStatement.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/drivers/ado/Awrapado.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/desktop/win32/source/loader.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/extensions/source/activex/SOActiveX.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/pyuno/source/module/pyuno.cxx") + || loplugin::isSamePathname(fn, SRCDIR + "/setup_native/source/win32/customactions/sellang/sellang.cxx") + || loplugin::isSamePathname(fn, SRCDIR + "/shell/source/win32/shlxthandler/ooofilt/stream_helper.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/shell/source/win32/zipfile/zipfile.cxx") + || loplugin::isSamePathname(fn, SRCDIR "/ucb/source/ucp/webdav-curl/CurlSession.cxx")) + return; + // these are places where the external API is actually "long" + if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/jpeg/JpegReader.cxx")) + return; + if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/DirectoryStream.cxx")) + return; + if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/source/common/WPXSvInputStream.cxx")) + return; + if (loplugin::isSamePathname(fn, + SRCDIR "/writerperfect/source/calc/MSWorksCalcImportFilter.cxx")) + return; + if (loplugin::isSamePathname(fn, SRCDIR "/writerperfect/qa/unit/WPXSvStreamTest.cxx")) + return; + if (loplugin::isSamePathname(fn, SRCDIR "/desktop/source/lib/init.cxx")) + return; + + TraverseDecl(compiler.getASTContext().getTranslationUnitDecl()); + + for (auto const& dcl : reverseSourceOrder(varDecls_)) + { + auto const decl = dcl.first; + SourceLocation loc{ decl->getBeginLoc() }; + TypeSourceInfo* tsi = decl->getTypeSourceInfo(); + if (tsi != nullptr) + { + SourceLocation l{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getBeginLoc()) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getEndLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) + { + for (;;) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + if (l == end) + { + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + } + if (!rewrite(loc)) + { + report(DiagnosticsEngine::Warning, "VarDecl, use \"tools::Long\" instead of %0", loc) + << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange(); + } + } + for (auto const& dcl : reverseSourceOrder(fieldDecls_)) + { + auto const decl = dcl.first; + SourceLocation loc{ decl->getBeginLoc() }; + TypeSourceInfo* tsi = decl->getTypeSourceInfo(); + if (tsi != nullptr) + { + SourceLocation l{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getBeginLoc()) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getEndLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) + { + for (;;) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + if (l == end) + { + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + } + if (!rewrite(loc)) + { + report(DiagnosticsEngine::Warning, "FieldDecl, use \"tools::Long\" instead of %0", loc) + << decl->getType().getLocalUnqualifiedType() << decl->getSourceRange(); + } + } + for (auto const& dcl : reverseSourceOrder(parmVarDecls_)) + { + auto const decl = dcl.first; + SourceLocation loc{ decl->getBeginLoc() }; + TypeSourceInfo* tsi = decl->getTypeSourceInfo(); + if (tsi != nullptr) + { + SourceLocation l{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getBeginLoc()) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getEndLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (l == end || (compiler.getSourceManager().isBeforeInTranslationUnit(l, end))) + { + for (;;) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + if (l == end) + { + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + } + FunctionDecl const* f = dyn_cast_or_null<FunctionDecl>(decl->getDeclContext()); + if (f) + f = f->getCanonicalDecl(); + OverrideKind k = f ? getOverrideKind(f) : OverrideKind::NO; + if (k == OverrideKind::MAYBE || !rewrite(loc)) + { + report(DiagnosticsEngine::Warning, + ("ParmVarDecl, use \"tools::Long\" instead of" + " %0%1"), + loc) + << decl->getType().getNonReferenceType().getLocalUnqualifiedType() + << (k == OverrideKind::MAYBE ? (" (unless this member function overrides a" + " dependent base member function, even" + " though it is not marked 'override')") + : "") + << decl->getSourceRange(); + } + } + for (auto const& dcl : functionDecls_) + { + auto const decl = dcl.first; + SourceLocation loc{ decl->getBeginLoc() }; + SourceLocation l{ compiler.getSourceManager().getExpansionLoc(loc) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + decl->getNameInfo().getLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) + { + while (l != end) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + if (rewrite(loc)) + continue; + report(DiagnosticsEngine::Warning, "use \"tools::Long\" instead of %0 as return type%1", + loc) + << decl->getReturnType().getNonReferenceType().getLocalUnqualifiedType() + << (getOverrideKind(decl) == OverrideKind::MAYBE + ? (" (unless this member function overrides a dependent" + " base member function, even though it is not marked" + " 'override')") + : "") + << decl->getSourceRange(); + } + + for (auto const& dcl : staticCasts_) + { + auto const expr = dcl.first; + SourceLocation loc{ expr->getBeginLoc() }; + TypeSourceInfo* tsi = expr->getTypeInfoAsWritten(); + if (tsi != nullptr) + { + SourceLocation l{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getBeginLoc()) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getEndLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) + { + for (;;) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + if (l == end) + { + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + } + if (!rewrite(loc)) + { + report(DiagnosticsEngine::Warning, "CXXStaticCastExpr, suspicious cast from %0 to %1", + expr->getBeginLoc()) + << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType() + << expr->getSourceRange(); + } + } + + for (auto const& dcl : functionalCasts_) + { + auto const expr = dcl.first; + SourceLocation loc{ expr->getBeginLoc() }; + TypeSourceInfo* tsi = expr->getTypeInfoAsWritten(); + if (tsi != nullptr) + { + SourceLocation l{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getBeginLoc()) }; + SourceLocation end{ compiler.getSourceManager().getExpansionLoc( + tsi->getTypeLoc().getEndLoc()) }; + assert(l.isFileID() && end.isFileID()); + if (l == end || compiler.getSourceManager().isBeforeInTranslationUnit(l, end)) + { + for (;;) + { + unsigned n = Lexer::MeasureTokenLength(l, compiler.getSourceManager(), + compiler.getLangOpts()); + std::string s{ compiler.getSourceManager().getCharacterData(l), n }; + if (s == "long") + { + loc = l; + break; + } + if (l == end) + { + break; + } + l = l.getLocWithOffset(std::max<unsigned>(n, 1)); + } + } + } + if (!rewrite(loc)) + { + report(DiagnosticsEngine::Warning, + "CXXFunctionalCastExpr, suspicious cast from %0 to %1", expr->getBeginLoc()) + << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType() + << expr->getSourceRange(); + } + } +} + +bool ToolsLong::VisitCStyleCastExpr(CStyleCastExpr* expr) +{ + if (ignoreLocation(expr)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()))) + return true; + auto const k = isLong(expr->getType()); + if (!k) + return true; + SourceLocation loc{ expr->getBeginLoc() }; + while (compiler.getSourceManager().isMacroArgExpansion(loc)) + loc = compiler.getSourceManager().getImmediateMacroCallerLoc(loc); + if (compiler.getSourceManager().isMacroBodyExpansion(loc) + && compiler.getSourceManager().isInSystemHeader( + compiler.getSourceManager().getSpellingLoc(loc))) + { + return true; + } + report(DiagnosticsEngine::Warning, "CStyleCastExpr, suspicious cast from %0 to %1", + expr->getBeginLoc()) + << expr->getSubExpr()->IgnoreParenImpCasts()->getType() << expr->getType() + << expr->getSourceRange(); + return true; +} + +bool ToolsLong::VisitCXXStaticCastExpr(CXXStaticCastExpr* expr) +{ + if (ignoreLocation(expr)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()))) + return true; + auto const k = isLong(expr->getType()); + if (!k) + return true; + staticCasts_.insert({ expr, k }); + return true; +} + +bool ToolsLong::VisitCXXFunctionalCastExpr(CXXFunctionalCastExpr* expr) +{ + if (ignoreLocation(expr)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(expr->getBeginLoc()))) + return true; + auto const k = isLong(expr->getType()); + if (!k) + return true; + functionalCasts_.insert({ expr, k }); + return true; +} + +bool ToolsLong::WalkUpFromParmVarDecl(ParmVarDecl const* decl) { return VisitParmVarDecl(decl); } + +bool ToolsLong::VisitParmVarDecl(ParmVarDecl const* decl) +{ + if (ignoreLocation(decl)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation()))) + return true; + auto const fbk = isLong(decl->getType()); + if (!fbk) + return true; + FunctionDecl const* f = dyn_cast<FunctionDecl>(decl->getDeclContext()); + if (f) // e.g.: typedef sal_Bool (* FuncPtr )( sal_Bool ); + { + // ignore the function in include/test/cppunitasserthelper.hxx + if (f->getIdentifier() && f->getName() == "assertEquals") + return true; + auto canonicalF = f->getCanonicalDecl(); + if (canonicalF->isDeletedAsWritten() && isa<CXXConversionDecl>(canonicalF)) + return true; + if (auto const d = dyn_cast<CXXMethodDecl>(canonicalF)) + { + if (d->isVirtual()) + { + return true; + } + } + // Only rewrite declarations in include files if a definition is + // also seen, to avoid compilation of a definition (in a main file + // only processed later) to fail with a "mismatch" error before the + // rewriter had a chance to act upon the definition: + bool ok = canonicalF->isDefined() + || compiler.getSourceManager().isInMainFile( + compiler.getSourceManager().getSpellingLoc(f->getNameInfo().getLoc())); + if (!ok) + return true; + } + parmVarDecls_.insert({ decl, fbk }); + return true; +} + +bool ToolsLong::WalkUpFromVarDecl(VarDecl const* decl) { return VisitVarDecl(decl); } + +bool ToolsLong::VisitVarDecl(VarDecl const* decl) +{ + if (ignoreLocation(decl)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation()))) + return true; + auto k = isLong(decl->getType()); + if (!k) + return true; + varDecls_.insert({ decl, k }); + return true; +} + +bool ToolsLong::WalkUpFromFieldDecl(FieldDecl const* decl) { return VisitFieldDecl(decl); } + +bool ToolsLong::VisitFieldDecl(FieldDecl const* decl) +{ + if (ignoreLocation(decl)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation()))) + return true; + auto k = isLong(decl->getType()); + if (!k) + return true; + TagDecl const* td = dyn_cast<TagDecl>(decl->getDeclContext()); + if (td == nullptr) + { + //TODO: ObjCInterface + return true; + } + fieldDecls_.insert({ decl, k }); + return true; +} + +bool ToolsLong::WalkUpFromFunctionDecl(FunctionDecl const* decl) { return VisitFunctionDecl(decl); } + +bool ToolsLong::VisitFunctionDecl(FunctionDecl const* decl) +{ + if (ignoreLocation(decl)) + return true; + if (isExcludedFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation()))) + return true; + auto const fbk = isLong(decl->getReturnType()); + if (!fbk) + return true; + if (decl->isDeletedAsWritten() && isa<CXXConversionDecl>(decl)) + return true; + if (auto const d = dyn_cast<CXXMethodDecl>(decl)) + { + if (d->isVirtual()) + { + return true; + } + } + if (decl->isDefined() + || compiler.getSourceManager().isInMainFile( + compiler.getSourceManager().getSpellingLoc(decl->getNameInfo().getLoc()))) + { + functionDecls_.insert({ decl, fbk }); + } + return true; +} + +bool ToolsLong::VisitCallExpr(CallExpr const* expr) +{ + if (ignoreLocation(expr)) + { + return true; + } + auto const d1 = expr->getDirectCallee(); + if (d1 == nullptr || !loplugin::DeclCheck(d1).Function("curl_easy_getinfo").GlobalNamespace()) + { + return true; + } + if (expr->getNumArgs() != 3) + { + return true; + } + //TODO: Check expr->getArg(1) is CURLINFO_RESPONSE_CODE + auto const e1 = dyn_cast<UnaryOperator>(expr->getArg(2)->IgnoreParenImpCasts()); + if (e1 == nullptr || e1->getOpcode() != UO_AddrOf) + { + return true; + } + auto const e2 = dyn_cast<DeclRefExpr>(e1->getSubExpr()->IgnoreParenImpCasts()); + if (e2 == nullptr) + { + return true; + } + auto const d2 = e2->getDecl(); + if (auto const d3 = dyn_cast<ParmVarDecl>(d2)) + { + parmVarDecls_.erase(d3); + } + else if (auto const d4 = dyn_cast<VarDecl>(d2)) + { + varDecls_.erase(d4); + } + else if (auto const d5 = dyn_cast<FieldDecl>(d2)) + { + fieldDecls_.erase(d5); + } + return true; +} + +bool ToolsLong::rewrite(SourceLocation location) +{ + if (rewriter != nullptr) + { + SourceLocation loc{ compiler.getSourceManager().getExpansionLoc(location) }; + unsigned n + = Lexer::MeasureTokenLength(loc, compiler.getSourceManager(), compiler.getLangOpts()); + if (std::string(compiler.getSourceManager().getCharacterData(loc), n) == "long") + { + return replaceText(loc, n, "tools::Long"); + } + } + return false; +} + +bool ToolsLong::isExcludedFile(SourceLocation spellingLocation) const +{ + if (isInUnoIncludeFile(spellingLocation)) + return true; + auto f = getFilenameOfLocation(spellingLocation); + return loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppu/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/cppuhelper/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/registry/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/osl/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/rtl/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/sal/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/salhelper/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/typelib/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/include/LibreOfficeKit/") // TODO + || loplugin::hasPathnamePrefix(f, SRCDIR "/bridges/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/codemaker/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/configmgr/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/cppu/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/cppuhelper/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/external/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/libreofficekit/") // TODO + || loplugin::hasPathnamePrefix(f, SRCDIR "/registry/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/rtl/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/sal/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/salhelper/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/soltools/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/unoidl/") + || loplugin::hasPathnamePrefix(f, SRCDIR "/workdir/"); +} + +loplugin::Plugin::Registration<ToolsLong> X("toolslong", true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |