# 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' \
or fullname == 'sal_Int32 rtl::OStringBuffer::getLength() const' \
or fullname == 'sal_Int32 rtl::OUStringBuffer::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')