Skip to content

Commit e8b0905

Browse files
committed
[test] Tests to guard against memory leak from Arrow Operator
1 parent 905a7f8 commit e8b0905

3 files changed

Lines changed: 117 additions & 1 deletion

File tree

exist-core/pom.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,7 @@
761761
<include>src/main/java/org/exist/xmlrpc/ArrayWrapperParser.java</include>
762762
<include>src/main/java/org/exist/xmlrpc/ArrayWrapperSerializer.java</include>
763763
<include>src/main/java/org/exist/xmlrpc/XmlRpcExtensionConstants.java</include>
764+
<include>src/test/java/org/exist/xquery/ArrowOperatorTest.java</include>
764765
<include>src/test/resources-filtered/org/exist/xquery/import-from-pkg-test.conf.xml</include>
765766
<include>src/test/java/org/exist/xquery/ImportFromPkgTest.java</include>
766767
<include>src/main/java/org/exist/xquery/JavaBinding.java</include>
@@ -1994,6 +1995,7 @@
19941995
<exclude>src/test/java/org/exist/xqj/MarshallerTest.java</exclude>
19951996
<exclude>src/main/java/org/exist/xquery/AbstractInternalModule.java</exclude>
19961997
<exclude>src/main/java/org/exist/xquery/ArrowOperator.java</exclude>
1998+
<exclude>src/test/java/org/exist/xquery/ArrowOperatorTest.java</exclude>
19971999
<exclude>src/main/java/org/exist/xquery/Cardinality.java</exclude>
19982000
<exclude>src/test/java/org/exist/xquery/CardinalityTest.java</exclude>
19992001
<exclude>src/test/java/org/exist/xquery/CastExpressionTest.java</exclude>

exist-core/src/main/java/org/exist/xquery/NamedFunctionReference.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,14 @@
5757
import org.exist.xquery.value.Sequence;
5858
import org.exist.xquery.value.Type;
5959

60+
import javax.annotation.Nullable;
61+
6062
public class NamedFunctionReference extends AbstractExpression {
6163

6264
private QName qname;
6365
private int arity;
6466

65-
private FunctionCall resolvedFunction = null;
67+
@Nullable FunctionCall resolvedFunction = null;
6668

6769
public NamedFunctionReference(XQueryContext context, QName qname, int arity) {
6870
super(context);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Elemental
3+
* Copyright (C) 2024, Evolved Binary Ltd
4+
*
5+
* admin@evolvedbinary.com
6+
* https://www.evolvedbinary.com | https://www.elemental.xyz
7+
*
8+
* This library is free software; you can redistribute it and/or
9+
* modify it under the terms of the GNU Lesser General Public
10+
* License as published by the Free Software Foundation; version 2.1.
11+
*
12+
* This library is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+
* Lesser General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Lesser General Public
18+
* License along with this library; if not, write to the Free Software
19+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+
*/
21+
package org.exist.xquery;
22+
23+
import org.exist.EXistException;
24+
import org.exist.security.PermissionDeniedException;
25+
import org.exist.storage.BrokerPool;
26+
import org.exist.storage.DBBroker;
27+
import org.exist.test.ExistEmbeddedServer;
28+
import org.exist.xquery.value.Sequence;
29+
import org.junit.Rule;
30+
import org.junit.Test;
31+
32+
import static org.junit.Assert.*;
33+
34+
public class ArrowOperatorTest {
35+
36+
@Rule
37+
public final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, true);
38+
39+
/**
40+
* Test to ensure that the ContextParam for the Arrow Operator
41+
* (when directly calling a function) is correctly null'ed out
42+
* so that it doesn't leak memory if the query is cached for reuse.
43+
*/
44+
@Test
45+
public void lhsContextSequenceLeakFunctionReference() throws EXistException, XPathException, PermissionDeniedException {
46+
final BrokerPool brokerPool = existEmbeddedServer.getBrokerPool();
47+
final XQuery xquery = brokerPool.getXQueryService();
48+
49+
final XQueryContext xqueryContext = new XQueryContext(brokerPool);
50+
51+
final CompiledXQuery compiledXquery;
52+
try (final DBBroker broker = brokerPool.getBroker()) {
53+
compiledXquery = xquery.compile(xqueryContext, "<a/> => fn:local-name()");
54+
final Sequence results = xquery.execute(broker, compiledXquery, null);
55+
assertEquals(1, results.getItemCount());
56+
assertEquals("a", results.itemAt(0).toString());
57+
}
58+
59+
// Get the ArrowOperator's Function Call
60+
final PathExpr pathExpr = (PathExpr) compiledXquery;
61+
final ArrowOperator arrowOperator = (ArrowOperator) pathExpr.steps.getFirst();
62+
final FunctionCall functionCall = arrowOperator.functionCall;
63+
assertNotNull(functionCall);
64+
assertFalse(functionCall.steps.isEmpty());
65+
66+
// Get the ContextParam#Sequence for the Function Call
67+
final Expression contextParamExpr = functionCall.steps.getFirst();
68+
assertTrue(contextParamExpr instanceof ArrowOperator.ContextParam);
69+
final ArrowOperator.ContextParam contextParam = (ArrowOperator.ContextParam) contextParamExpr;
70+
71+
// Ensure that the ContextParam#sequence has been null'ed after execution to avoid a memory-leak
72+
assertNull(contextParam.sequence);
73+
}
74+
75+
/**
76+
* Test to ensure that the ContextParam for the Arrow Operator
77+
* (when indirectly calling a function due to expression evaluation)
78+
* is correctly null'ed out so that it doesn't leak memory if the
79+
* query is cached for reuse.
80+
*/
81+
@Test
82+
public void lhsContextSequenceLeakVariableReference() throws EXistException, XPathException, PermissionDeniedException {
83+
final BrokerPool brokerPool = existEmbeddedServer.getBrokerPool();
84+
final XQuery xquery = brokerPool.getXQueryService();
85+
86+
final XQueryContext xqueryContext = new XQueryContext(brokerPool);
87+
88+
final CompiledXQuery compiledXquery;
89+
try (final DBBroker broker = brokerPool.getBroker()) {
90+
compiledXquery = xquery.compile(xqueryContext, "let $f := fn:local-name#1 return <a/> => $f()");
91+
final Sequence results = xquery.execute(broker, compiledXquery, null);
92+
assertEquals(1, results.getItemCount());
93+
assertEquals("a", results.itemAt(0).toString());
94+
}
95+
96+
// Get the ArrowOperator's Function Call
97+
final PathExpr pathExpr = (PathExpr) compiledXquery;
98+
final LetExpr letExpr = (LetExpr) pathExpr.steps.getFirst();
99+
final NamedFunctionReference namedFunctionRef = (NamedFunctionReference) letExpr.inputSequence;
100+
final FunctionCall functionCall = namedFunctionRef.resolvedFunction;
101+
assertNotNull(functionCall);
102+
assertFalse(functionCall.steps.isEmpty());
103+
104+
// Get the ContextParam#Sequence for the Function Call
105+
final Expression contextParamExpr = functionCall.steps.getFirst();
106+
assertTrue(contextParamExpr instanceof ArrowOperator.ContextParam);
107+
final ArrowOperator.ContextParam contextParam = (ArrowOperator.ContextParam) contextParamExpr;
108+
109+
// Ensure that the ContextParam#sequence has been null'ed after execution to avoid a memory-leak
110+
assertNull(contextParam.sequence);
111+
}
112+
}

0 commit comments

Comments
 (0)