From ed8e8ac2892af3a0a70b95330e01ec976d3fea3c Mon Sep 17 00:00:00 2001
From: Joe Wang
Date: Fri, 27 May 2022 21:47:05 +0000
Subject: [PATCH] 8284400: Improve XPath exception handling
Reviewed-by: lancea, naoto
---
.../org/apache/xpath/internal/NodeSet.java | 68 +--
.../org/apache/xpath/internal/NodeSetDTM.java | 72 +--
.../sun/org/apache/xpath/internal/XPath.java | 16 +-
.../internal/axes/PredicatedNodeTest.java | 10 +-
.../xpath/internal/compiler/XPathParser.java | 87 ++-
.../internal/functions/FuncExtFunction.java | 14 +-
.../internal/jaxp/JAXPVariableStack.java | 11 +-
.../internal/jaxp/XPathExpressionImpl.java | 35 +-
.../apache/xpath/internal/jaxp/XPathImpl.java | 46 +-
.../internal/res/XPATHErrorResources.java | 23 +-
.../unittest/xpath/XPathExceptionTest.java | 548 +++++++++++++++++-
11 files changed, 684 insertions(+), 246 deletions(-)
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java
index aa8eeb170d3..d8dc27c0651 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -55,7 +55,7 @@ import org.w3c.dom.traversal.NodeIterator;
* to the same calls; the disadvantage is that some of them may return
* less-than-enlightening results when you do so.
* @xsl.usage advanced
- * @LastModified: Nov 2017
+ * @LastModified: May 2022
*/
public class NodeSet
implements NodeList, NodeIterator, Cloneable, ContextNodeList
@@ -379,11 +379,7 @@ public class NodeSet
*/
public void addNode(Node n)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
- this.addElement(n);
+ addElement(n);
}
/**
@@ -397,10 +393,6 @@ public class NodeSet
*/
public void insertNode(Node n, int pos)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
insertElementAt(n, pos);
}
@@ -413,11 +405,7 @@ public class NodeSet
*/
public void removeNode(Node n)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
- this.removeElement(n);
+ removeElement(n);
}
/**
@@ -431,10 +419,6 @@ public class NodeSet
*/
public void addNodes(NodeList nodelist)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if (null != nodelist) // defensive to fix a bug that Sanjiva reported.
{
int nChildren = nodelist.getLength();
@@ -471,10 +455,6 @@ public class NodeSet
*/
public void addNodes(NodeSet ns)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
addNodes((NodeIterator) ns);
}
@@ -488,10 +468,6 @@ public class NodeSet
*/
public void addNodes(NodeIterator iterator)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if (null != iterator) // defensive to fix a bug that Sanjiva reported.
{
Node obj;
@@ -516,10 +492,6 @@ public class NodeSet
*/
public void addNodesInDocOrder(NodeList nodelist, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
int nChildren = nodelist.getLength();
for (int i = 0; i < nChildren; i++)
@@ -544,10 +516,6 @@ public class NodeSet
*/
public void addNodesInDocOrder(NodeIterator iterator, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
Node node;
while (null != (node = iterator.nextNode()))
@@ -572,10 +540,6 @@ public class NodeSet
private boolean addNodesInDocOrder(int start, int end, int testIndex,
NodeList nodelist, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
boolean foundit = false;
int i;
Node node = nodelist.item(testIndex);
@@ -632,10 +596,6 @@ public class NodeSet
*/
public int addNodeInDocOrder(Node node, boolean test, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
int insertIndex = -1;
if (test)
@@ -706,10 +666,6 @@ public class NodeSet
*/
public int addNodeInDocOrder(Node node, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
return addNodeInDocOrder(node, true, support);
} // end addNodeInDocOrder(Vector v, Object obj)
@@ -767,9 +723,6 @@ public class NodeSet
return n;
}
- /** True if this list can be mutated. */
- transient protected boolean m_mutable = true;
-
/** True if this list is cached.
* @serial */
transient protected boolean m_cacheNodes = true;
@@ -804,7 +757,6 @@ public class NodeSet
XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_CALL_SETSHOULDCACHENODE, null)); //"Can not call setShouldCacheNodes after nextNode has been called!");
m_cacheNodes = b;
- m_mutable = true;
}
@@ -875,9 +827,6 @@ public class NodeSet
*/
public void addElement(Node value)
{
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if ((m_firstFree + 1) >= m_mapSize)
{
if (null == m_map)
@@ -1102,9 +1051,6 @@ public class NodeSet
*/
public void insertElementAt(Node value, int at)
{
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if (null == m_map)
{
m_map = new Node[m_blocksize];
@@ -1195,9 +1141,6 @@ public class NodeSet
*/
public boolean removeElement(Node s)
{
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if (null == m_map)
return false;
@@ -1258,9 +1201,6 @@ public class NodeSet
*/
public void setElementAt(Node node, int index)
{
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!");
-
if (null == m_map)
{
m_map = new Node[m_blocksize];
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java
index 6b0c1bde1b5..ae0e196f0bb 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java
@@ -1,6 +1,5 @@
/*
- * reserved comment block
- * DO NOT REMOVE OR ALTER!
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -57,6 +56,7 @@ import org.w3c.dom.traversal.NodeIterator;
* to the same calls; the disadvantage is that some of them may return
* less-than-enlightening results when you do so.
* @xsl.usage advanced
+ * @LastModified: May 2022
*/
public class NodeSetDTM extends NodeVector
implements /* NodeList, NodeIterator, */ DTMIterator,
@@ -536,11 +536,7 @@ public class NodeSetDTM extends NodeVector
*/
public void addNode(int n)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
- this.addElement(n);
+ addElement(n);
}
/**
@@ -554,10 +550,6 @@ public class NodeSetDTM extends NodeVector
*/
public void insertNode(int n, int pos)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
insertElementAt(n, pos);
}
@@ -570,10 +562,6 @@ public class NodeSetDTM extends NodeVector
*/
public void removeNode(int n)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
this.removeElement(n);
}
@@ -647,10 +635,6 @@ public class NodeSetDTM extends NodeVector
*/
public void addNodes(DTMIterator iterator)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
if (null != iterator) // defensive to fix a bug that Sanjiva reported.
{
int obj;
@@ -704,10 +688,6 @@ public class NodeSetDTM extends NodeVector
*/
public void addNodesInDocOrder(DTMIterator iterator, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
int node;
while (DTM.NULL != (node = iterator.nextNode()))
@@ -793,10 +773,6 @@ public class NodeSetDTM extends NodeVector
*/
public int addNodeInDocOrder(int node, boolean test, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
int insertIndex = -1;
if (test)
@@ -868,10 +844,6 @@ public class NodeSetDTM extends NodeVector
*/
public int addNodeInDocOrder(int node, XPathContext support)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
return addNodeInDocOrder(node, true, support);
} // end addNodeInDocOrder(Vector v, Object obj)
@@ -894,10 +866,6 @@ public class NodeSetDTM extends NodeVector
*/
public void addElement(int value)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.addElement(value);
}
@@ -914,10 +882,6 @@ public class NodeSetDTM extends NodeVector
*/
public void insertElementAt(int value, int at)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.insertElementAt(value, at);
}
@@ -930,10 +894,6 @@ public class NodeSetDTM extends NodeVector
*/
public void appendNodes(NodeVector nodes)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.appendNodes(nodes);
}
@@ -947,10 +907,6 @@ public class NodeSetDTM extends NodeVector
*/
public void removeAllElements()
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.removeAllElements();
}
@@ -969,10 +925,6 @@ public class NodeSetDTM extends NodeVector
*/
public boolean removeElement(int s)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
return super.removeElement(s);
}
@@ -988,10 +940,6 @@ public class NodeSetDTM extends NodeVector
*/
public void removeElementAt(int i)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.removeElementAt(i);
}
@@ -1009,10 +957,6 @@ public class NodeSetDTM extends NodeVector
*/
public void setElementAt(int node, int index)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.setElementAt(node, index);
}
@@ -1026,10 +970,6 @@ public class NodeSetDTM extends NodeVector
*/
public void setItem(int node, int index)
{
-
- if (!m_mutable)
- throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!");
-
super.setElementAt(node, index);
}
@@ -1157,9 +1097,6 @@ public class NodeSetDTM extends NodeVector
return n;
}
- /** True if this list can be mutated. */
- transient protected boolean m_mutable = true;
-
/** True if this list is cached.
* @serial */
transient protected boolean m_cacheNodes = true;
@@ -1197,7 +1134,6 @@ public class NodeSetDTM extends NodeVector
XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_CALL_SETSHOULDCACHENODE, null)); //"Can not call setShouldCacheNodes after nextNode has been called!");
m_cacheNodes = b;
- m_mutable = true;
}
/**
@@ -1208,7 +1144,7 @@ public class NodeSetDTM extends NodeVector
*/
public boolean isMutable()
{
- return m_mutable;
+ return true;
}
transient private int m_last = 0;
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java
index 18632eab6ab..bd2d5a093a0 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java
@@ -42,7 +42,7 @@ import jdk.xml.internal.XMLSecurityManager;
* The XPath class wraps an expression object and provides general services
* for execution of that expression.
* @xsl.usage advanced
- * @LastModified: Jan 2022
+ * @LastModified: May 2022
*/
public class XPath implements Serializable, ExpressionOwner
{
@@ -208,22 +208,16 @@ public class XPath implements Serializable, ExpressionOwner
else if (MATCH == type)
parser.initMatchPattern(compiler, exprString, prefixResolver);
else
- throw new RuntimeException(XSLMessages.createXPATHMessage(
+ throw new TransformerException(XSLMessages.createXPATHMessage(
XPATHErrorResources.ER_CANNOT_DEAL_XPATH_TYPE,
new Object[]{Integer.toString(type)}));
- //"Can not deal with XPath type: " + type);
- // System.out.println("----------------");
- Expression expr = compiler.compileExpression(0);
-
- // System.out.println("expr: "+expr);
- this.setExpression(expr);
+ m_mainExp = compiler.compileExpression(0);
if((null != locator) && locator instanceof ExpressionNode)
{
- expr.exprSetParent((ExpressionNode)locator);
+ m_mainExp.exprSetParent((ExpressionNode)locator);
}
-
}
/**
@@ -274,7 +268,7 @@ public class XPath implements Serializable, ExpressionOwner
*/
public XPath(Expression expr)
{
- this.setExpression(expr);
+ m_mainExp = expr;
initFunctionTable();
}
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java
index 6e52dc80393..b4ecbee15a0 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -24,6 +24,7 @@ import com.sun.org.apache.xml.internal.dtm.DTM;
import com.sun.org.apache.xml.internal.dtm.DTMIterator;
import com.sun.org.apache.xml.internal.utils.PrefixResolver;
import com.sun.org.apache.xml.internal.utils.QName;
+import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException;
import com.sun.org.apache.xpath.internal.Expression;
import com.sun.org.apache.xpath.internal.ExpressionOwner;
import com.sun.org.apache.xpath.internal.XPathContext;
@@ -34,7 +35,7 @@ import com.sun.org.apache.xpath.internal.patterns.NodeTest;
import java.util.List;
/**
- * @LastModified: May 2020
+ * @LastModified: May 2022
*/
public abstract class PredicatedNodeTest extends NodeTest implements SubContextList
{
@@ -491,9 +492,8 @@ public abstract class PredicatedNodeTest extends NodeTest implements SubContextL
}
catch (javax.xml.transform.TransformerException se)
{
-
- // TODO: Fix this.
- throw new RuntimeException(se.getMessage());
+ // the Xalan/XPath impl use WrappedRuntimeException to carry errors over
+ throw new WrappedRuntimeException(se);
}
finally
{
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java
index 1efc5a32c37..e7f2f567c84 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java
@@ -35,7 +35,7 @@ import jdk.xml.internal.XMLSecurityManager;
* Tokenizes and parses XPath expressions. This should really be named
* XPathParserImpl, and may be renamed in the future.
* @xsl.usage general
- * @LastModified: Apr 2022
+ * @LastModified: May 2022
*/
public class XPathParser
{
@@ -79,6 +79,16 @@ public class XPathParser
// XML security manager
XMLSecurityManager m_xmlSecMgr;
+ // Union operands must be node-sets, e.g. //a | //b
+ // A flag indicating whether the operand is a location path
+ boolean isLocationPath = false;
+
+ // A flag indicating whether the next operand is required to be a location path
+ boolean lPathRequired = false;
+
+ // Keep track of the status of reading the next token after lPathRequired is flagged
+ boolean nextTokenRead = false;
+
/**
* The parser constructor.
*/
@@ -434,9 +444,19 @@ public class XPathParser
* Retrieve the next token from the command and
* store it in m_token string.
*/
- private final void nextToken()
+ private final void nextToken() throws TransformerException
{
-
+ // before reading another token, check the last one
+ if (lPathRequired) {
+ if (nextTokenRead) {
+ // check whether the operand behind the union was a Location path
+ checkNodeSet();
+ lPathRequired = false;
+ nextTokenRead = false;
+ } else {
+ nextTokenRead = true;
+ }
+ }
if (m_queueMark < m_ops.getTokenQueueSize())
{
m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++);
@@ -497,31 +517,28 @@ public class XPathParser
}
/**
- * Consume an expected token, throwing an exception if it
- * isn't there.
- *
- * @param expected The string to be expected.
- *
- * @throws TransformerException
+ * Checks whether the function token represents a function that returns a
+ * nodeset.
+ * @param funcTok the function token
+ * @return true if the function token represents a function that returns a
+ * nodeset, false otherwise
*/
- private final void consumeExpected(String expected)
- throws TransformerException
- {
+ private boolean isNodesetFunction(int funcTok) {
+ return (funcTok == FunctionTable.FUNC_CURRENT || funcTok == FunctionTable.FUNC_HERE
+ || funcTok == FunctionTable.FUNC_ID);
+ }
- if (tokenIs(expected))
+ /**
+ * Checks whether the operand is a location path, reports error if not.
+ *
+ * @throws TransformerException if an error is found
+ */
+ private void checkNodeSet() throws TransformerException {
+ if (!isLocationPath)
{
- nextToken();
+ error(XPATHErrorResources.ER_UNION_MUST_BE_NODESET,
+ new Object[]{});
}
- else
- {
- error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected,
- m_token }); //"Expected "+expected+", but found: "+m_token);
-
- // Patch for Christina's gripe. She wants her errorHandler to return from
- // this error and continue trying to parse, rather than throwing an exception.
- // Without the patch, that put us into an endless loop.
- throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR);
- }
}
/**
@@ -1209,13 +1226,17 @@ public class XPathParser
if (tokenIs(Token.VBAR))
{
+ // check whether the operand before the union is a location path
+ checkNodeSet();
+
if (false == foundUnion)
{
foundUnion = true;
-
insertOp(opPos, 2, OpCodes.OP_UNION);
}
+ isLocationPath = false;
+ lPathRequired = true;
nextToken();
}
else
@@ -1464,7 +1485,7 @@ public class XPathParser
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1);
nextToken();
- consumeExpected(':');
+ consumeExpected(Token.COLON);
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1);
@@ -1494,6 +1515,10 @@ public class XPathParser
m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok);
}
+ // XML Signature here() function returns a node-set
+ if (isNodesetFunction(funcTok)) {
+ isLocationPath = true;
+ }
nextToken();
}
@@ -1544,7 +1569,7 @@ public class XPathParser
*/
protected void LocationPath() throws TransformerException
{
-
+ isLocationPath = true;
int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH);
// int locationPathOpPos = opPos;
@@ -1861,7 +1886,7 @@ public class XPathParser
}
nextToken();
- consumeExpected(':');
+ consumeExpected(Token.COLON);
}
else
{
@@ -1908,7 +1933,7 @@ public class XPathParser
nextToken();
PredicateExpr();
countPredicate--;
- consumeExpected(']');
+ consumeExpected(Token.RBRACK);
}
}
@@ -1950,7 +1975,7 @@ public class XPathParser
m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1);
nextToken();
- consumeExpected(':');
+ consumeExpected(Token.COLON);
}
else
{
@@ -1969,7 +1994,7 @@ public class XPathParser
* NCName ::= (Letter | '_') (NCNameChar)
* NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender
*/
- protected void NCName()
+ protected void NCName() throws TransformerException
{
m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1);
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java
index ac1fd12745f..6fe57d28d2f 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -34,13 +34,14 @@ import com.sun.org.apache.xpath.internal.res.XPATHErrorResources;
import com.sun.org.apache.xpath.internal.res.XPATHMessages;
import java.util.ArrayList;
import java.util.List;
+import javax.xml.transform.TransformerException;
/**
* An object of this class represents an extension call expression. When
* the expression executes, it calls ExtensionsTable#extFunction, and then
* converts the result to the appropriate XObject.
* @xsl.usage advanced
- * @LastModified: Oct 2017
+ * @LastModified: May 2022
*/
public class FuncExtFunction extends Function
{
@@ -183,8 +184,7 @@ public class FuncExtFunction extends Function
*
* @throws javax.xml.transform.TransformerException
*/
- public XObject execute(XPathContext xctxt)
- throws javax.xml.transform.TransformerException
+ public XObject execute(XPathContext xctxt) throws TransformerException
{
if (xctxt.isSecureProcessing())
throw new javax.xml.transform.TransformerException(
@@ -209,6 +209,12 @@ public class FuncExtFunction extends Function
}
//dml
ExtensionsProvider extProvider = (ExtensionsProvider)xctxt.getOwnerObject();
+ if (extProvider == null) {
+ String fmsg = XSLMessages.createXPATHMessage(
+ XPATHErrorResources.ER_NO_XPATH_FUNCTION_PROVIDER,
+ new Object[] {argVec} );
+ throw new TransformerException ( fmsg );
+ }
Object val = extProvider.extFunction(this, argVec);
if (null != val)
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java
index 46e7996fd9a..4a75ae4f1ff 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java
@@ -1,6 +1,5 @@
/*
- * reserved comment block
- * DO NOT REMOVE OR ALTER!
+ * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
@@ -38,6 +37,7 @@ import com.sun.org.apache.xalan.internal.res.XSLMessages;
* {@link javax.xml.xpath.XPathVariableResolver}.
*
* @author Ramesh Mandava
+ * @LastModified: May 2022
*/
public class JAXPVariableStack extends VariableStack {
@@ -61,6 +61,13 @@ public class JAXPVariableStack extends VariableStack {
new javax.xml.namespace.QName(
qname.getNamespace(),
qname.getLocalPart());
+
+ if (resolver == null) {
+ String fmsg = XSLMessages.createXPATHMessage(
+ XPATHErrorResources.ER_NO_XPATH_VARIABLE_RESOLVER,
+ new Object[] { name.toString()} );
+ throw new TransformerException( fmsg );
+ }
Object varValue = resolver.resolveVariable( name );
if ( varValue == null ) {
String fmsg = XSLMessages.createXPATHMessage(
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java
index 79f6dc382bb..5ded2b99eaa 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java
@@ -20,6 +20,7 @@
package com.sun.org.apache.xpath.internal.jaxp;
+import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException;
import com.sun.org.apache.xpath.internal.objects.XObject;
import javax.xml.namespace.QName;
import javax.xml.transform.TransformerException;
@@ -27,6 +28,7 @@ import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathEvaluationResult;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFunctionException;
import javax.xml.xpath.XPathFunctionResolver;
import javax.xml.xpath.XPathVariableResolver;
import jdk.xml.internal.JdkXmlFeatures;
@@ -37,7 +39,7 @@ import org.xml.sax.InputSource;
* The XPathExpression interface encapsulates a (compiled) XPath expression.
*
* @author Ramesh Mandava
- * @LastModified: Apr 2022
+ * @LastModified: May 2022
*/
public class XPathExpressionImpl extends XPathImplUtil implements XPathExpression {
@@ -77,7 +79,7 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio
}
public Object eval(Object item, QName returnType)
- throws javax.xml.transform.TransformerException {
+ throws TransformerException {
XObject resultObject = eval(item, xpath);
return getResultAsType(resultObject, returnType);
}
@@ -88,20 +90,20 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio
isSupported(returnType);
try {
return eval(item, returnType);
- } catch (java.lang.NullPointerException npe) {
- // If VariableResolver returns null Or if we get
- // NullPointerException at this stage for some other reason
- // then we have to reurn XPathException
- throw new XPathExpressionException (npe);
- } catch (javax.xml.transform.TransformerException te) {
+ } catch (TransformerException te) {
Throwable nestedException = te.getException();
- if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
- throw (javax.xml.xpath.XPathFunctionException)nestedException;
+ if (nestedException instanceof XPathFunctionException) {
+ throw (XPathFunctionException)nestedException;
} else {
// For any other exceptions we need to throw
// XPathExpressionException (as per spec)
throw new XPathExpressionException(te);
}
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
}
@@ -115,12 +117,18 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio
@Override
public Object evaluate(InputSource source, QName returnType)
throws XPathExpressionException {
+ requireNonNull(source, "Source");
isSupported (returnType);
try {
Document document = getDocument(source);
return eval(document, returnType);
} catch (TransformerException e) {
throw new XPathExpressionException(e);
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
}
@@ -143,8 +151,13 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio
return XPathResultImpl.getValue(resultObject, type);
}
- } catch (javax.xml.transform.TransformerException te) {
+ } catch (TransformerException te) {
throw new XPathExpressionException(te);
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
}
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java
index c070b2806f2..79a06eec785 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java
@@ -20,6 +20,7 @@
package com.sun.org.apache.xpath.internal.jaxp;
+import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException;
import com.sun.org.apache.xpath.internal.*;
import com.sun.org.apache.xpath.internal.objects.XObject;
import javax.xml.namespace.NamespaceContext;
@@ -47,7 +48,7 @@ import org.xml.sax.InputSource;
* New methods: evaluateExpression
* Refactored to share code with XPathExpressionImpl.
*
- * @LastModified: Jan 2022
+ * @LastModified: May 2022
*/
public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
@@ -134,11 +135,6 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
XObject resultObject = eval(expression, item);
return getResultAsType(resultObject, returnType);
- } catch (java.lang.NullPointerException npe) {
- // If VariableResolver returns null Or if we get
- // NullPointerException at this stage for some other reason
- // then we have to reurn XPathException
- throw new XPathExpressionException (npe);
} catch (TransformerException te) {
Throwable nestedException = te.getException();
if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
@@ -146,10 +142,14 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
} else {
// For any other exceptions we need to throw
// XPathExpressionException (as per spec)
- throw new XPathExpressionException (te);
+ throw new XPathExpressionException(te);
}
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
-
}
//-Override-
@@ -172,26 +172,18 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
return ximpl;
} catch (TransformerException te) {
throw new XPathExpressionException (te) ;
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
}
//-Override-
public Object evaluate(String expression, InputSource source,
QName returnType) throws XPathExpressionException {
- isSupported(returnType);
-
- try {
- Document document = getDocument(source);
- XObject resultObject = eval(expression, document);
- return getResultAsType(resultObject, returnType);
- } catch (TransformerException te) {
- Throwable nestedException = te.getException();
- if (nestedException instanceof javax.xml.xpath.XPathFunctionException) {
- throw (javax.xml.xpath.XPathFunctionException)nestedException;
- } else {
- throw new XPathExpressionException (te);
- }
- }
+ return evaluate(expression, getDocument(source), returnType);
}
//-Override-
@@ -210,7 +202,8 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
//-Override-
public T evaluateExpression(String expression, Object item, Class type)
throws XPathExpressionException {
- isSupportedClassType(type);
+ requireNonNull(expression, "XPath expression");
+ isSupportedClassType(type);
try {
XObject resultObject = eval(expression, item);
if (type == XPathEvaluationResult.class) {
@@ -219,7 +212,12 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath {
return XPathResultImpl.getValue(resultObject, type);
}
} catch (TransformerException te) {
- throw new XPathExpressionException (te);
+ throw new XPathExpressionException(te);
+ } catch (RuntimeException re) {
+ if (re instanceof WrappedRuntimeException) {
+ throw new XPathExpressionException(((WrappedRuntimeException)re).getException());
+ }
+ throw new XPathExpressionException(re);
}
}
diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
index 9df76ef6632..409d06e638e 100644
--- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
+++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java
@@ -31,7 +31,7 @@ import java.util.ListResourceBundle;
* Also you need to update the count of messages(MAX_CODE)or
* the count of warnings(MAX_WARNING) [ Information purpose only]
* @xsl.usage advanced
- * @LastModified: Apr 2022
+ * @LastModified: May 2022
*/
public class XPATHErrorResources extends ListResourceBundle
{
@@ -140,6 +140,7 @@ public class XPATHErrorResources extends ListResourceBundle
"ER_EXPECTED_SINGLE_QUOTE";
public static final String ER_EMPTY_EXPRESSION = "ER_EMPTY_EXPRESSION";
public static final String ER_EXPECTED_BUT_FOUND = "ER_EXPECTED_BUT_FOUND";
+ public static final String ER_UNION_MUST_BE_NODESET = "ER_UNION_MUST_BE_NODESET";
public static final String ER_INCORRECT_PROGRAMMER_ASSERTION =
"ER_INCORRECT_PROGRAMMER_ASSERTION";
public static final String ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL =
@@ -203,9 +204,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
"ER_FUNCTION_TOKEN_NOT_FOUND";
public static final String ER_CANNOT_DEAL_XPATH_TYPE =
"ER_CANNOT_DEAL_XPATH_TYPE";
- public static final String ER_NODESET_NOT_MUTABLE = "ER_NODESET_NOT_MUTABLE";
- public static final String ER_NODESETDTM_NOT_MUTABLE =
- "ER_NODESETDTM_NOT_MUTABLE";
/** Variable not resolvable: */
public static final String ER_VAR_NOT_RESOLVABLE = "ER_VAR_NOT_RESOLVABLE";
/** Null error handler */
@@ -309,6 +307,8 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
//BEGIN: Keys needed for exception messages of JAXP 1.3 XPath API implementation
public static final String ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED = "ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED";
public static final String ER_RESOLVE_VARIABLE_RETURNS_NULL = "ER_RESOLVE_VARIABLE_RETURNS_NULL";
+ public static final String ER_NO_XPATH_VARIABLE_RESOLVER = "ER_NO_XPATH_VARIABLE_RESOLVER";
+ public static final String ER_NO_XPATH_FUNCTION_PROVIDER = "ER_NO_XPATH_FUNCTION_PROVIDER";
public static final String ER_UNSUPPORTED_RETURN_TYPE = "ER_UNSUPPORTED_RETURN_TYPE";
public static final String ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL = "ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL";
public static final String ER_ARG_CANNOT_BE_NULL = "ER_ARG_CANNOT_BE_NULL";
@@ -460,6 +460,9 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
{ ER_EXPECTED_BUT_FOUND,
"Expected {0}, but found: {1}"},
+ { ER_UNION_MUST_BE_NODESET,
+ "Operands for a union must be node-sets."},
+
{ ER_INCORRECT_PROGRAMMER_ASSERTION,
"Programmer assertion is incorrect! - {0}"},
@@ -574,12 +577,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
{ ER_CANNOT_DEAL_XPATH_TYPE,
"Can not deal with XPath type: {0}"},
- { ER_NODESET_NOT_MUTABLE,
- "This NodeSet is not mutable"},
-
- { ER_NODESETDTM_NOT_MUTABLE,
- "This NodeSetDTM is not mutable"},
-
{ ER_VAR_NOT_RESOLVABLE,
"Variable not resolvable: {0}"},
@@ -780,6 +777,12 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED =
{ ER_RESOLVE_VARIABLE_RETURNS_NULL,
"resolveVariable for variable {0} returning null"},
+ { ER_NO_XPATH_VARIABLE_RESOLVER,
+ "Attempting to resolve variable {0}, but a variable resolver is not set."},
+
+ { ER_NO_XPATH_FUNCTION_PROVIDER,
+ "Attempting to call an extension function {0}, but an extension provider is not set."},
+
/** Field ER_UNSUPPORTED_RETURN_TYPE */
{ ER_UNSUPPORTED_RETURN_TYPE,
diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java
index 3b6a61745db..9069f0ad4ac 100644
--- a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java
+++ b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java
@@ -24,34 +24,96 @@
package xpath;
import java.io.StringReader;
+import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
+import static javax.xml.xpath.XPathConstants.BOOLEAN;
+import static javax.xml.xpath.XPathConstants.NODE;
+import static javax.xml.xpath.XPathConstants.NODESET;
+import static javax.xml.xpath.XPathConstants.NUMBER;
+import static javax.xml.xpath.XPathConstants.STRING;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
+import javax.xml.xpath.XPathNodes;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
+import org.xml.sax.InputSource;
/*
* @test
- * @bug 8284548
+ * @bug 8284400 8284548
* @run testng xpath.XPathExceptionTest
- * @summary This is a general test for Exception handling. Additional cases may
- * be added with a bug id in the test cases.
+ * @summary This is a general test for Exception handling in XPath. This test
+ * covers the followings:
+ * NPE: refer to DataProvider NPE for more details.
+ * IAE: covered by existing tests: Bug4991939, XPathAnyTypeTest, XPathExpAnyTypeTest
+ * and XPathTest
+ * XPathExpressionException: all other cass other than NPE and IAE.
*/
public class XPathExceptionTest {
+ private final String XPATH_EXPRESSION = "ext:helloWorld()";
/*
- * DataProvider: invalid XPath expressions
- * Illegal expressions and structures that may escape the validation check.
+ * DataProvider: used for NPE test, provides the following fields:
+ * expression, context, useSource, source, QName, class type, provided
+ * Refer to testNPEWithEvaluate.
*/
- @DataProvider(name = "invalid")
- public Object[][] getInvalid() throws Exception {
+ @DataProvider(name = "NPE")
+ public Object[][] getNullParameter() throws Exception {
return new Object[][]{
+ /**
+ * Existing NPE tests:
+ * for XPath::evaluate:
+ * Bug4992788: expression != null, source = null
+ * Bug4992793: expression = null, source != null
+ * Bug4992805: source != null, QName = null
+ *
+ * for XPath::evaluateExpression
+ * XPathAnyTypeTest: expression = null or classType = null
+ */
+ // NPE if expression = null
+ {null, (Node)null, false, null, STRING, String.class, true},
+ {null, getDummyDoc(), false, null, STRING, String.class, true},
+ {null, (Node)null, false, null, null, null, false},
+ {null, getDummyDoc(), false, null, null, null, false},
+ // NPE if returnType = null
+ {"exp", (Node)null, false, null, null, null, true},
+ {"exp", getDummyDoc(), false, null, null, null, true},
+ // NPE if source = null
+ {"exp", (Node)null, true, null, STRING, String.class, true},
+ {"exp", getDummyDoc(), true, null, STRING, String.class, true},
+ {"exp", (Node)null, true, null, null, null, false},
+ {"exp", getDummyDoc(), true, null, null, null, false},
+ };
+ }
+
+ /*
+ * DataProvider: used for compile-time error test, provides:
+ * invalid XPath expressions
+ */
+ @DataProvider(name = "invalidExp")
+ public Object[][] getInvalidExp() throws Exception {
+ return new Object[][]{
+ {"8|b"},
+ {"8[x=2]|b"},
+ {"8/a|b"},
+ {"a|7"},
+ {"a|7|b"},
+ {"a|7[x=2]"},
+ {"b|'literal'"},
+ {"b|\"literal\""},
+ {"a|$x:y"},
+ {"a|(x or y)"},
+ {"a|(x and y)"},
+ {"a|(x=2)"},
+ {"a|string-length(\"xy\")"},
+ {"/a/b/preceding-sibling::comment()|7"},
// @bug JDK-8284548: expressions ending with relational operators
// throw StringIndexOutOfBoundsException instead of XPathExpressionException
{"/a/b/c[@d >"},
@@ -61,22 +123,476 @@ public class XPathExceptionTest {
};
}
+ /*
+ * DataProvider: expressions that cause exceptions in the given context.
+ */
+ @DataProvider(name = "expInContext1")
+ public Object[][] getExpressionAndContext1() throws Exception {
+ InputSource source = new InputSource(new StringReader(""));
+ return new Object[][]{
+
+ // expressions invalid for the null context, return type not provided
+ {"x+1", (Node)null, false, null, null, null, false},
+ {"5 mod a", (Node)null, false, null, null, null, false},
+ {"8 div ", (Node)null, false, null, null, null, false},
+ {"/bookstore/book[price>xx]", (Node)null, false, null, null, null, false},
+ // expressions invalid for the null context, return type is irrelevant
+ // for the eval, but needs to be a valid one when used
+ // Note that invalid class type was tested in XPathAnyTypeTest,
+ // and invalid QName tested in Bug4991939.
+ {"x+1", (Node)null, false, null, STRING, String.class, true},
+ {"5 mod a", (Node)null, false, null, STRING, String.class, true},
+ {"8 div ", (Node)null, false, null, STRING, String.class, true},
+ {"/bookstore/book[price>xx]", (Node)null, false, null, STRING, String.class, true},
+
+ // undefined variable, context not relevant, return type not provided
+ {"/ * [n*$d2]/s", getDummyDoc(), false, null, null, null, false},
+ {"/ * [n|$d1]/s", getDummyDoc(), false, null, null, null, false},
+ {"/ * [n*$d2]/s", null, true, source, null, null, false},
+ {"/ * [n|$d1]/s", null, true, source, null, null, false},
+ // undefined variable, context/return type not relevant for the eval
+ // but need to be valid when provided
+ {"/ * [n*$d2]/s", getDummyDoc(), false, null, STRING, String.class, true},
+ {"/ * [n|$d1]/s", getDummyDoc(), false, null, STRING, String.class, true},
+ {"/ * [n*$d2]/s", null, true, source, STRING, String.class, true},
+ {"/ * [n|$d1]/s", null, true, source, STRING, String.class, true},
+ };
+ }
+
+ /*
+ * DataProvider: provides edge cases that are valid
+ */
+ @DataProvider(name = "expInContext2")
+ public Object[][] getExpressionAndContext2() throws Exception {
+ return new Object[][]{
+ // The context can be empty
+ {"/node[x=2]", getEmptyDocument(), false, null, STRING, String.class, true},
+ {"/a/b/c", getEmptyDocument(), false, null, BOOLEAN, Boolean.class, true},
+ };
+ }
+
+ /*
+ * DataProvider: provides expressions that contain function calls.
+ */
+ @DataProvider(name = "functions")
+ public Object[][] getExpressionWithFunctions() throws Exception {
+ InputSource source = new InputSource(new StringReader(""));
+ return new Object[][]{
+ // expression with a function call
+ {XPATH_EXPRESSION, getEmptyDocument(), false, null, STRING, String.class, true},
+ {XPATH_EXPRESSION, null, true, source, BOOLEAN, Boolean.class, true},
+ };
+ }
+
/**
- * Verifies that the XPath processor throws XPathExpressionException upon
- * encountering illegal XPath expressions.
+ * Verifies that NPE is thrown if the expression, source, or returnType is
+ * null.
+ * This test tests these methods:
+ * XPath::evaluate and XPathExpression::evaluate.
+ * XPath::evaluateExpression and XPathExpression::evaluateExpression.
+ *
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param cls the return type in the form of a class type, can be null
+ * @param provided a flag indicating whether the return type is provided
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "NPE")
+ public void testNPEWithEvaluate(String expression, Object item, boolean useSource,
+ InputSource source, QName qn, Class> cls, boolean provided)
+ throws Exception {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ // test with XPath::evaluate
+ Assert.assertThrows(NullPointerException.class, () -> xpathEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPathExpression::evaluate
+ Assert.assertThrows(NullPointerException.class, () -> xpathExpressionEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPath::evaluateExpression
+ Assert.assertThrows(NullPointerException.class, () -> xpathEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+
+ // test with XPathExpression::evaluateExpression
+ Assert.assertThrows(NullPointerException.class, () -> xpathExpressionEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+ }
+
+ /**
+ * Verifies that XPathExpressionException is thrown upon encountering illegal
+ * XPath expressions when XPath::compile is called.
+ *
* @param invalidExp an illegal XPath expression
* @throws Exception if the test fails
*/
- @Test(dataProvider = "invalid")
- public void testIllegalExp(String invalidExp) throws Exception {
- DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
- Document doc = builder.parse(new org.xml.sax.InputSource(new StringReader("")));
- Assert.assertThrows(XPathExpressionException.class, () -> evaluate(doc, invalidExp));
+ @Test(dataProvider = "invalidExp")
+ public void testXPathCompile(String invalidExp) throws Exception {
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathCompile(invalidExp));
}
- private void evaluate(Document doc, String s) throws XPathExpressionException {
+ /**
+ * Verifies that XPathExpressionException is thrown upon encountering illegal
+ * XPath expressions.
+ * This test tests these methods:
+ * XPath::evaluate and XPathExpression::evaluate.
+ * XPath::evaluateExpression and XPathExpression::evaluateExpression.
+
+ *
+ * @param expression an illegal XPath expression
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "invalidExp")
+ public void testCompileErrorWithEvaluate(String expression)
+ throws Exception {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ // test with XPath::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate(
+ xPath, expression, (Node)null, false, null, null, false));
+
+ // test with XPathExpression::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate(
+ xPath, expression, (Node)null, false, null, null, false));
+
+ // test with XPath::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression(
+ xPath, expression, (Node)null, false, null, null, false));
+
+ // test with XPathExpression::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression(
+ xPath, expression, (Node)null, false, null, null, false));
+ }
+
+ /**
+ * Verifies that XPathExpressionException is thrown if the expression is
+ * invalid with the given context.
+ * This test tests these methods:
+ * XPath::evaluate and XPathExpression::evaluate.
+ * XPath::evaluateExpression and XPathExpression::evaluateExpression.
+ *
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param cls the return type in the form of a class type, can be null
+ * @param provided a flag indicating whether the return type is provided
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "expInContext1")
+ public void testExpInContextEval1(String expression, Object item, boolean useSource,
+ InputSource source, QName qn, Class> cls, boolean provided)
+ throws Exception {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ // test with XPath::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPathExpression::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPath::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+
+ // test with XPathExpression::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+ }
+
+ /**
+ * Verifies that XPathExpressionException is thrown if the expression is
+ * invalid with the given context.
+ * This test tests these methods:
+ * XPath::evaluate and XPathExpression::evaluate.
+ * XPath::evaluateExpression and XPathExpression::evaluateExpression.
+ *
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param cls the return type in the form of a class type, can be null
+ * @param provided a flag indicating whether the return type is provided
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "expInContext1")
+ public void testExpInContext(String expression, Object item, boolean useSource,
+ InputSource source, QName qn, Class> cls, boolean provided)
+ throws Exception {
+ QName[] qns = {NUMBER, STRING, BOOLEAN, NODESET, NODE};
+ Class[] classes = {Integer.class, Boolean.class, String.class, XPathNodes.class, Node.class};
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ for (QName qn1 : qns) {
+ // test with XPath::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate(
+ xPath, expression, item, useSource, source, qn1, provided));
+
+ // test with XPathExpression::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate(
+ xPath, expression, item, useSource, source, qn1, provided));
+ }
+
+ for (Class> cls1 : classes) {
+ // test with XPath::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression(
+ xPath, expression, item, useSource, source, cls1, provided));
+
+ // test with XPathExpression::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression(
+ xPath, expression, item, useSource, source, cls1, provided));
+ }
+ }
+
+ /**
+ * Verifies that the expression is valid with the given context.
+ * This test tests these methods:
+ * XPath::evaluate and XPathExpression::evaluate.
+ * XPath::evaluateExpression and XPathExpression::evaluateExpression.
+ *
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param cls the return type in the form of a class type, can be null
+ * @param provided a flag indicating whether the return type is provided
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "expInContext2")
+ public void testExpInContextEval2(String expression, Object item, boolean useSource,
+ InputSource source, QName qn, Class> cls, boolean provided)
+ throws Exception {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ // test with XPath::evaluate
+ xpathEvaluate(xPath, expression, item, useSource, source, qn, provided);
+
+ // test with XPathExpression::evaluate
+ xpathExpressionEvaluate(xPath, expression, item, useSource, source, qn, provided);
+
+ // test with XPath::evaluateExpression
+ xpathEvaluateExpression(xPath, expression, item, useSource, source, cls, provided);
+
+ // test with XPathExpression::evaluateExpression
+ xpathExpressionEvaluateExpression(xPath, expression, item, useSource, source, cls, provided);
+ }
+
+ /**
+ * Verifies that the XPath processor without XPathFunctionResolver throws
+ * XPathExpressionException upon processing an XPath expression that attempts
+ * to call a function.
+ *
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param cls the return type in the form of a class type, can be null
+ * @param provided a flag indicating whether the return type is provided
+ * @throws Exception if the test fails
+ */
+ @Test(dataProvider = "functions")
+ public void testFunction(String expression, Object item, boolean useSource,
+ InputSource source, QName qn, Class> cls, boolean provided)
+ throws Exception {
+ XPath xPath = XPathFactory.newInstance().newXPath();
+
+ // test with XPath::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPathExpression::evaluate
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate(
+ xPath, expression, item, useSource, source, qn, provided));
+
+ // test with XPath::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+
+ // test with XPathExpression::evaluateExpression
+ Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression(
+ xPath, expression, item, useSource, source, cls, provided));
+ }
+
+// ---- utility methods ----
+ /**
+ * Compiles the specified expression.
+ * @param s the expression
+ * @throws XPathExpressionException if the expression is invalid
+ */
+ private void xpathCompile(String s) throws XPathExpressionException {
XPath xp = XPathFactory.newInstance().newXPath();
XPathExpression xe = xp.compile(s);
- xe.evaluateExpression(doc, Node.class);
+ }
+
+ /**
+ * Runs evaluation using the XPath::evaluate methods.
+ *
+ * @param xPath the XPath object
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param qnProvided a flag indicating whether the QName is provided
+ * @throws XPathExpressionException
+ */
+ private void xpathEvaluate(XPath xPath, String expression, Object item,
+ boolean useSource, InputSource source, QName qn, boolean qnProvided)
+ throws XPathExpressionException {
+ if (useSource) {
+ if (!qnProvided) {
+ xPath.evaluate(expression, source);
+ } else {
+ xPath.evaluate(expression, source, qn);
+ }
+ } else {
+ if (!qnProvided) {
+ xPath.evaluate(expression, item);
+ } else {
+ xPath.evaluate(expression, item, qn);
+ }
+ }
+ }
+
+ /**
+ * Runs evaluation using the XPathExpression::evaluate methods.
+ *
+ * @param xe the XPathExpression object
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param qnProvided a flag indicating whether the QName is provided
+ * @throws XPathExpressionException
+ */
+ private void xpathExpressionEvaluate(XPath xPath, String expression, Object item,
+ boolean useSource, InputSource source, QName qn, boolean qnProvided)
+ throws XPathExpressionException {
+ XPathExpression xe = xPath.compile(expression);
+ if (useSource) {
+ if (!qnProvided) {
+ xe.evaluate(source);
+ } else {
+ xe.evaluate(source, qn);
+ }
+ } else {
+ if (!qnProvided) {
+ xe.evaluate(item);
+ } else {
+ xe.evaluate(item, qn);
+ }
+ }
+ }
+
+ /**
+ * Runs evaluation using the XPath::evaluateExpression methods.
+ *
+ * @param xPath the XPath object
+ * @param expression the expression
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param cls the class type, can be null
+ * @param clsProvided a flag indicating whether the class type is provided
+ * @throws XPathExpressionException
+ */
+ private void xpathEvaluateExpression(XPath xPath, String expression, Object item,
+ boolean useSource, InputSource source, Class> cls, boolean clsProvided)
+ throws XPathExpressionException {
+ if (useSource) {
+ if (!clsProvided) {
+ xPath.evaluateExpression(expression, source);
+ } else {
+ xPath.evaluateExpression(expression, source, cls);
+ }
+ } else {
+ if (!clsProvided) {
+ xPath.evaluateExpression(expression, item);
+ } else {
+ xPath.evaluateExpression(expression, item, cls);
+ }
+ }
+ }
+
+ /**
+ * Runs evaluation using the XPathExpression::evaluateExpression methods.
+ *
+ * @param xe the XPathExpression object
+ * @param item the context item, can be null (for non-context dependent expressions)
+ * @param useSource a flag indicating whether the source shall be used instead
+ * of the context item
+ * @param source the source
+ * @param qn the return type in the form of a QName, can be null
+ * @param qnProvided a flag indicating whether the QName is provided
+ * @throws XPathExpressionException
+ */
+ private void xpathExpressionEvaluateExpression(XPath xPath, String expression,
+ Object item, boolean useSource, InputSource source, Class> cls, boolean qnProvided)
+ throws XPathExpressionException {
+ XPathExpression xe = xPath.compile(expression);
+ if (useSource) {
+ if (!qnProvided) {
+ xe.evaluateExpression(source);
+ } else {
+ xe.evaluateExpression(source, cls);
+ }
+ } else {
+ if (!qnProvided) {
+ xe.evaluateExpression(item);
+ } else {
+ xe.evaluateExpression(item, cls);
+ }
+ }
+ }
+
+ /**
+ * Returns an empty {@link org.w3c.dom.Document}.
+ * @return a DOM Document, null in case of Exception
+ */
+ private Document getEmptyDocument() {
+ try {
+ return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
+ } catch (ParserConfigurationException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Returns a DOM Document with dummy content.
+ * @return a DOM Document
+ * @throws Exception
+ */
+ private Document getDummyDoc() throws Exception {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ return builder.parse(new InputSource(new StringReader("")));
+ }
+
+ /**
+ * Returns a DOM Document with the specified source.
+ * @param s the source
+ * @return a DOM Document
+ * @throws Exception
+ */
+ private Document getDoc(InputSource s) throws Exception {
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ return builder.parse(s);
}
}