The following page summarizes the results for the manual inspection of 20 randomly chosen mutations for the Jaxen XPath engine. Further details on the study can be found in the corresponding paper The Impact of Equivalent Mutants
Every mutation is described as follows:

  • First, a table gives the location, the type of the mutation, and whether it was considered non-equivalent
  • Next, the source code context of this mutation, with the mutated line in bold, is depicted.
  • After that a source code statement that corresponds to the mutation is shown. Although the mutations are applied to bytecode directly, the mutated statements are given in Java source code, if possible. (Not all mutations that are legal bytecode can be expressed in Java source code)
  • Then there is a text that explains why the mutation was considered to be equivalent or not.
  • Finally, if possible, we provide a unit test that detects the mutation.


20 randomly chosen mutations

Mutation 1

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.saxpath.base.Verifier278replace numerical constant (X-1)true

Source Code Context

if (c < 0x1F00) return false;  if (c <= 0x1F15) return true;
if (c < 0x1F18) return false;  if (c <= 0x1F1D) return true;
if (c < 0x1F20) return false;  if (c <= 0x1F45) return true;
if (c < 0x1F48) return false;  if (c <= 0x1F4D) return true;
if (c < 0x1F50) return false;  if (c <= 0x1F57) return true;

Mutated Statement

if (c < 0x1F20-1) return false; if (c <= 0x1F45) return true;

Description

Mutation causes a character to be qualified as non-valid XML character, although it is a valid character.

Unit Test for Mutation

public void test1() throws JaxenException {
  char c = 0x1F20 - 1;
  String s = "" + c;
  try {
    XPath xpath = new DOMXPath(s);
    fail("Expected an exception");
  } catch (XPathSyntaxException e) {
    // Expect this
  }
};

Mutation 2

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.dom.DocumentNavigator360omit method callfalse

Source Code Context

if (myNamespace != null && ! "".equals(myNamespace)) {
  String myPrefix = n.getPrefix();
  if (!nsMap.containsKey(myPrefix) ) {
    NamespaceNode ns = new NamespaceNode((Node) contextNode, myPrefix, myNamespace);
    nsMap.put(myPrefix, ns);
  }
}          

Mutated Statement

if (!false) {

Description

Mutation suppresses a check if a value for a key is already contained in the map, which lets the if-condition to always evaluate to true. This however does not alter the program behavior, since the object that is constructed, as a value for the map is always the same, and thus an equal element gets added again.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 3

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.pattern.UnionPattern62replace numerical constant (X+1)false

Source Code Context

public UnionPattern(Pattern lhs, Pattern rhs)
  {
  this.nodeType = ANY_NODE;
  this.lhs = lhs;
  this.rhs = rhs;
  init();
  }

Note: The first statement in the constructor cannot be found in this form in the original source code. It corresponds to a field declaration and initialization that is inserted into the constructor by the compiler. Since the mutations are carried out on bytecode this statement gets mutated.

Mutated Statement

this.nodeType = ANY_NODE +1;

Description

Mutation sets a different value for the field nodeType at the beginning of the constructor. This value however is reset in the init() method that is called at the end of the constructor.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 4

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.jdom.DocumentNavigator256omit method callundecided

Source Code Context

if ( ns != Namespace.NO_NAMESPACE ) {
  if ( !nsMap.containsKey(ns.getPrefix()) )
    nsMap.put( ns.getPrefix(), new XPathNamespace(elem, ns) );
}

Mutated Statement

if ( !false)

Description

Mutation could result in a namespace bound to its last occurrence rather than its first. However none of the authors could write a test triggering this behavior. When specifying a namespace twice for an element another exception is thrown such that this erroneous behavior cannot be triggered.
Since the two authors of the study disagreed on the equivalence of this mutant and none could write a test this mutation was left undecided.

Unit Test for Mutation

No test since the mutation was undecided

Mutation 5

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.expr.DefaultStep159omit method calltrue

Source Code Context

if ( matches( eachAxisNode, support ) )
{
  unique.add( eachAxisNode );
  interimSet.add( eachAxisNode );
}

Mutated Statement

// unique.add( eachAxisNode );//Call is omitted

Description

The mutation can result in a returned set with more entries than necessary.

Unit Test for Mutation

public void test5() throws JaxenException {
  Navigator nav = new DocumentNavigator();
  String url = "xml/testNamespaces.xml";
  Object document = nav.getDocument(url);
  XPath contextpath = new BaseXPath("/Template/Application1", nav);
  List list = contextpath.selectNodes(document);
  Object context = list.get(0);
  String xpathStr = "/descendant-or-self::node()";
  DOMXPath xpath = new DOMXPath(xpathStr);
  List results = xpath.selectNodes(getContext(context));
  assertEquals(25, results.size());
}

Note: This test uses some methods from org.jaxen.test.BaseXPathTest

Mutation 6

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.dom.NamespaceNode147omit method callfalse

Source Code Context

String attributeName = attribute.getNodeName();
if (attributeName.equals("xmlns")) {
  this.name = "";
}

Mutated Statement

if (false) {

Description

The mutation would cause an error if the name of the namespace node, however inserting such a node in an xml element produces another exception ( [Fatal Error] testNamespaces14.xml:4:54: The prefix ""xmlns"" cannot be bound to any namespace explicitly; neither can the namespace for ""xmlns"" be bound to any prefix explicitly).

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 7

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.pattern.LocationPathPattern103omit method calltrue

Source Code Context

if ( ancestorPattern != null )
{
  ancestorPattern = ancestorPattern.simplify() ;
}

Mutated Statement

ancestorPattern = null;

Description

Mutation causes a wrong path for some Xpath expressions.

Unit Test for Mutation

public void test7() throws JaxenException, SAXPathException {
  String path = "foo//bar";
  Pattern pattern = PatternParser.parse(path);
  Navigator nav = getNavigator();
  String url = "xml/text.xml";
  Object document = nav.getDocument(url);
  XPath xpath = new BaseXPath(path, getNavigator());
  List result = xpath.selectNodes(document);
  boolean matches = pattern.matches(result.get(0), getContext(document));
  assertFalse(matches);
}    

Note: This test uses some methods from org.jaxen.test.BaseXPathTest

Mutation 8

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.saxpath.base.XPathLexer491omit method callundecided

Source Code Context

token = new Token( TokenTypes.OR,
    getXPath(),
    currentPosition(),
    currentPosition()+2 );

Mutated Statement

token = new Token( TokenTypes.OR,
    null,
    currentPosition(),
    currentPosition()+2 );

Description

The mutation results in a text that is not set for an XPath node. This text may be used for node checks or exception messages.
Since the two authors disagreed on the equivalence of this mutation and none of them could write a test it was left undecided.

Unit Test for Mutation

No test since the mutation was undecided

Mutation 9

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.expr.NodeComparator147replace numerical constant (X+1)false

Source Code Context

private int getDepth(Object o) throws UnsupportedAxisException {
  int depth = 0;
  Object parent = o;
  while ((parent = navigator.getParentNode(parent)) != null) {
    depth++;
  }
  return depth;
}

Mutated Statement

int depth = 1;

Description

Due to the mutation, the method returns an increased depth. However this is a private method and is only called in the compareTo() method. Since a higher depth value is returned for both nodes that are compared, the compareTo() method remains correct.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 10

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.function.ext.LocaleFunctionSupport98negate jumptrue

Source Code Context

...
  else {
    String text = StringFunction.evaluate( value, navigator );
    if (text != null && text.length() > 0)
    {
      return findLocale( text );
    }
  }
  return null;
}

Mutated Statement

if (text == null && text.length() > 0)

Description

Mutation returns null in some cases although a locale may be returned. When a Turkish locale is used to build an uppercase string this erroneous behavior is triggered.

Unit Test for Mutation

public void testStringExtensionFunctions() throws JaxenException
{
  Navigator nav = getNavigator();
  String url = "xml/webLocaleUpperCase.xml";
  log("Document [" + url + "]");
  Object document = nav.getDocument(url);
  XPath contextpath = new BaseXPath("/web-app/servlet[1]", nav);
  log("Initial Context :: " + contextpath);
  List list = contextpath.selectNodes(document);
  Iterator iter = list.iterator();
  while (iter.hasNext())
  {
    Object context = iter.next();
    String xpathStr= "upper-case( servlet-class, 'tr' )";
    DOMXPath xpath = new DOMXPath(xpathStr);
    Object node = xpath.evaluate(getContext(context));
    String result = StringFunction.evaluate(node,
        getNavigator());
    assertEquals(0x130, (int) result.charAt(0));
  }
}

Mutation 11

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.pattern.PatternParser104omit method callfalse

Source Code Context

...
PathReader reader = XPathReaderFactory.createReader();
JaxenHandler handler = new JaxenHandler();
handler.setXPathFactory( new DefaultXPathFactory() );
reader.setXPathHandler( handler );
reader.parse( text );
...

Mutated Statement

// handler.setXPathFactory( new DefaultXPathFactory() ); // Call is omitted

Description

Mutation suppresses the call to setXPathFactory(). However this call just sets the default value again, such that its omission has no effect.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 12

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.expr.DefaultLocationPath147replace numerical constant (0)false

Source Code Context

if (getSteps().size() > 1) {
  Collections.sort(contextNodeSet, new NodeComparator(support.getNavigator()));
}

Mutated Statement

if (getSteps().size() > 0) {

Description

Mutation causes the collection to be sorted when it has a size of at least one instead of two. This mutation has does not change the program behavior since a sorting a list of size one has no effect on the program's semantics.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 13

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.saxpath.base.XPathReader131omit method calltrue

Source Code Context

...
filterExpr();
if ( LA(1) == TokenTypes.SLASH || LA(1) == TokenTypes.DOUBLE_SLASH )
{
  XPathSyntaxException ex = createSyntaxException("Node-set expected");
  throw ex;
}
...

Mutated Statement

if ( LA(1) == TokenTypes.SLASH || LA(1) == 0 )

Description

Mutation causes an exception not to be thrown when a literal is followed by a double slash

Unit Test for Mutation

public void test13() throws JaxenException
{
  Navigator nav = getNavigator();
  String url = "xml/nitf.xml";
  Object document = nav.getDocument(url);
  XPath contextpath = new BaseXPath("/", nav);
  List list = contextpath.selectNodes(document);
  Iterator iter = list.iterator();
  while (iter.hasNext())
  {
    Object context = iter.next();
    try{
      assertCountXPath(1, context, "/nitf//meta[@name='ap-cycle'//]");
      fail("Expected exception");
    }
    catch(XPathSyntaxException exc){
      //expect this;
      assertTrue(exc.getMessage().contains("Node-set expected"));
    }
  }
}

Mutation 14

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.xom.DocumentNavigator377replace numerical constant (0)false

Source Code Context

private boolean addNamespaceForElement(Element elt, String uri, String prefix, Map map)
{
  if (uri != null && uri.length() > 0 && (! map.containsKey(prefix))) {
    map.put(prefix, new XPathNamespace(elt, uri, prefix));
    return true;
  }
  return false;
}

Mutated Statement

return false;

Description

Mutation returns false instead of true. The return value, however, is not checked in the program and the method is private. Thus the mutation has no effect on the program.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 15

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.xom.DocumentNavigator$IndexIterator217replace numerical constant (0)false

Source Code Context

private abstract static class IndexIterator implements Iterator {
  private Object o = null;
  private int pos = 0, end = -1;
  public IndexIterator(Object o, int pos, int end) {
    this.o = o;
    this.pos = pos;
    this.end = end;
  }

Mutated Statement

private int pos = 0, end = 0;

Description

Mutation sets a different default value for a field. In the constructor the field is set to another value. Thus, the default value is never used and changing the default value has no effect.

Unit Test for Mutation

No test since the mutation is considered to be equivalent

Mutation 16

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.function.NumberFunction210replace numerical constant (X-1)true

Source Code Context

... else if ( obj instanceof Boolean )
{
  if ( obj == Boolean.TRUE )
  {
    return new Double( 1 );
  }
  else
  {
    return new Double(0);
  }
}
...

Mutated Statement

return new Double(-1);

Description

Mutation sets a different double representation for the false value. When comparing zero directly to false this error can be triggered.

Unit Test for Mutation

public void test16() throws JaxenException {
  XPath xpath = new DOMXPath("( 0 <= false())");
  Boolean result = (Boolean) xpath.evaluate(doc);
  assertTrue(result.booleanValue());
}

Mutation 17

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.JaxenRuntimeException66replace numerical constant (X-1)true

Source Code Context

public JaxenRuntimeException(String message) {
  super(message);
  causeSet = false;
}

Mutated Statement

causeSet = true;

Description

Mutation replaces the initial value of causeSet from false to true. This prevents the possibility to set a cause.

Unit Test for Mutation

public void test17() {
  JaxenRuntimeException e = new JaxenRuntimeException("A");
  try {
    e.initCause(e);
    fail();
  } catch (IllegalArgumentException iae) {
    // Expect this
  }
  e.initCause(new RuntimeException("B"));
  try {
    e.initCause(new RuntimeException("C"));
    fail();
  } catch (IllegalStateException ise) {
    // Expect this
  }
}

Mutation 18

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.XPathSyntaxException135replace numerical constant (X-1)true

Source Code Context

public String getPositionMarker()
{
  StringBuffer buf = new StringBuffer();
  int pos = getPosition();
  for ( int i = -1; i < pos ; ++i )
  {
    buf.append(" ");
  }
...

Mutated Statement

for ( int i = 0; i < pos ; ++i )

Description

Mutation sets the beginning of a loop from -1 to 0. This causes a different length for a String that points to an error in the XPath query.

Unit Test for Mutation

public void test18() throws JaxenException {
  try {
    new DOMXPath("///triple slash");
    fail("Bad parsing");
  }
  catch (XPathSyntaxException ex) {
    assertEquals("  ^", ex.getPositionMarker());
  }

}

Mutation 19

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.Context101replace numerical constant (X-1)true

Source Code Context

public Context(ContextSupport contextSupport)
  {
    this.contextSupport = contextSupport;
    this.nodeSet        = Collections.EMPTY_LIST;
    this.size           = 0;
    this.position       = 0;
  }

Mutated Statement

this.size = -1;

Description

Mutation causes the size field to be initialized with a wrong value, which can be checked by a test.

Unit Test for Mutation

public void test19(){
  Context c = new Context(null);
  assertEquals(0, c.getSize());
}

Mutation 20

ClassLine NumberMutation TypeNon-Equivalent
org.jaxen.expr.DefaultMultiplyExpr75omit method calltrue

Source Code Context

public Object evaluate( Context context ) throws JaxenException
{
  Number lhsValue = NumberFunction.evaluate( getLHS().evaluate( context ),
      context.getNavigator() );

  Number rhsValue = NumberFunction.evaluate( getRHS().evaluate( context ),
                         context.getNavigator() );
  double result = lhsValue.doubleValue() * rhsValue.doubleValue();
  return new Double( result );
}

Mutated Statement

Number lhsValue = NumberFunction.evaluate( getLHS().evaluate( context ), null );

Description

Mutation sets the second argument of the evaluate call to null, such that it is invoked without a navigator. This cause different behavior when the left hand side is of a multiplicative expression is using a query to compute its value.

Unit Test for Mutation

public void test20() throws JaxenException {
  Navigator nav = getNavigator();
  String url = "xml/numbers.xml";
  Object document = nav.getDocument(url);
  XPath contextpath = new BaseXPath("/", nav);
  List list = contextpath.selectNodes(document);
  Iterator iter = list.iterator();
  while (iter.hasNext()) {
    Object context = iter.next();
    assertValueOfXPath("true", context,
        "(/numbers/set/nr = '-3') *(8 * 2 + 1) = 17");
  }
}

Appendix

Mutation Types

  • Replace a numerical constant X by X+1, X-1, or 0.
  • Negate jump condition. Replace a conditional jump by its counterpart. This is equivalent to negating a conditional statement in the source code.
  • Replace arithmetic operator. Replace an arithmetic operator by another arithmetic operator, e.g. + by -, or <<, >>
  • Omit a method call. If the method has a return value, a default value is used instead, e.g. x = Math.random() is replaced by x = 0.0 .