# Find calls of rtl::O[U]String::getLength() that check for an empty string (and # that would better use calls to rtl::O[U]String::isEmpty) in the LibreOffice # code base. # # Emits GCC notes prefixed with "[EmptyLength plugin]". The re-constructed # original comparison and the suggested improvement should be taken with a grain # of salt. # # * See for details about the GCC # Python plugin. # # * Requires a recent gcc-python-plugin.git version (post v0.7) including # adding "fullname" support. # # * Can be enabled in a LibreOffice build via # # ./autogen.sh ... CXX='g++ -fplugin=/gcc-python-plugin/python.so # -fplugin-arg-python-script=/emptylength.py' # # * Produces false positives like in (sal/rtl/source/bootstrap.cxx:237): # # inline void EnsureNoFinalSlash (rtl::OUString & url) # { # sal_Int32 i = url.getLength(); # if (i > 0 && url[i - 1] == '/') { # url = url.copy(0, i - 1); # } # } # # sal/rtl/source/bootstrap.cxx:240:5: note: [EmptyLength plugin] replace # "getLength() > 0" with "!isEmpty()" # # * Disable ccache (beware g++ pointing to /usr/lib64/ccache/g++, etc.) if you # want to modify this script and re-run GCC. Otherwise, your script might not # be re-run. import gcc verbose = False class EmptyLength(gcc.GimplePass): def execute(self, fun): for bb in fun.cfg.basic_blocks: if bb.gimple: processBlock(bb.gimple) def processBlock(statements): calls = set () for stmt in statements: verbose and stmt.loc and gcc.inform(stmt.loc, 'XXX stmt: "%s"' % stmt) if isinstance(stmt, gcc.GimpleCall): if isSimpleVar(stmt.lhs) and stmt.fndecl \ and isLength(stmt.fndecl.fullname): verbose and gcc.inform(stmt.loc, 'XXX match call'); calls.add(stmt.lhs.addr) elif isinstance(stmt, gcc.GimpleAssign): if len(stmt.rhs) == 1: if isSimpleVar(stmt.lhs) and stmt.exprcode == gcc.VarDecl \ and isCall(calls, stmt.rhs[0]): verbose and gcc.inform(stmt.loc, 'XXX match assign'); calls.add(stmt.lhs.addr) elif len(stmt.rhs) == 2: verbose and gcc.inform(stmt.loc, 'XXX check assign'); checkLength( stmt.loc, calls, stmt.exprcode, stmt.rhs[0], stmt.rhs[1]) elif isinstance(stmt, gcc.GimpleCond): verbose and gcc.inform(stmt.loc, 'XXX check cond'); checkLength(stmt.loc, calls, stmt.exprcode, stmt.lhs, stmt.rhs) def checkLength(location, calls, exprcode, lhs, rhs): if isCall(calls, lhs): if exprcode == gcc.EqExpr and isConst(rhs, 0): replace(location, 'getLength() == 0', 'isEmpty()') elif exprcode == gcc.GeExpr and isConst(rhs, 1): replace(location, 'getLength() >= 1', '!isEmpty()') elif exprcode == gcc.GtExpr and isConst(rhs, 0): replace(location, 'getLength() > 0', '!isEmpty()') elif exprcode == gcc.LeExpr and isConst(rhs, 0): replace(location, 'getLength() <= 0', 'isEmpty()') elif exprcode == gcc.LtExpr and isConst(rhs, 1): replace(location, 'getLength() < 1', 'isEmpty()') elif exprcode == gcc.NeExpr and isConst(rhs, 0): replace(location, 'getLength() != 0', '!isEmpty()') elif isCall(calls, rhs): if exprcode == gcc.EqExpr and isConst(lhs, 0): replace(location, '0 == getLength()', 'isEmpty()') elif exprcode == gcc.GeExpr and isConst(lhs, 0): replace(location, '0 >= getLength()', 'isEmpty()') elif exprcode == gcc.GtExpr and isConst(lhs, 1): replace(location, '1 > getLength()', 'isEmpty()') elif exprcode == gcc.LeExpr and isConst(lhs, 1): replace(location, '1 <= getLength()', '!isEmpty()') elif exprcode == gcc.LtExpr and isConst(lhs, 0): replace(location, '0 < getLength()', '!isEmpty()') elif exprcode == gcc.NeExpr and isConst(lhs, 0): replace(location, '0 != getLength()', '!isEmpty()') def isLength(fullname): return fullname == 'sal_Int32 rtl::OString::getLength() const' \ or fullname == 'sal_Int32 rtl::OUString::getLength() const'; def isCall(calls, tree): return isSimpleVar(tree) and tree.addr in calls def isSimpleVar(tree): return isinstance(tree, gcc.VarDecl) and tree.addr def isConst(tree, value): return isinstance(tree, gcc.Constant) and tree.constant == value def replace(location, found, better): gcc.inform( location, '[EmptyLength plugin] replace "%s" with "%s"' % (found, better)) ps = EmptyLength(name='EmptyLength') ps.register_after('cfg')