Skip to content

Commit 99d278f

Browse files
authored
Merge pull request #3550 from line-o/fix/3474
[hotfix] add location info to element content errors
2 parents 89c6975 + 5dfdc29 commit 99d278f

File tree

6 files changed

+248
-42
lines changed

6 files changed

+248
-42
lines changed

exist-core/src/main/antlr/org/exist/xquery/parser/XQuery.g

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1663,13 +1663,22 @@ compElemConstructor throws XPathException
16631663
{ String eq; }
16641664
:
16651665
( "element" LCURLY ) =>
1666-
"element"! LCURLY! expr RCURLY! LCURLY! (expr)? RCURLY!
1666+
"element"! LCURLY! expr RCURLY! compElemConstructorContent
16671667
{ #compElemConstructor = #(#[COMP_ELEM_CONSTRUCTOR], #compElemConstructor); }
16681668
|
1669-
"element"! eq=eqName LCURLY! (e3:expr)? RCURLY!
1670-
{ #compElemConstructor = #(#[COMP_ELEM_CONSTRUCTOR, eq], #[STRING_LITERAL, eq], #e3); }
1669+
"element"! eq=eqName v:compElemConstructorContent
1670+
{ #compElemConstructor = #(#[COMP_ELEM_CONSTRUCTOR, eq], #[STRING_LITERAL, eq], #v); }
16711671
;
16721672
1673+
compElemConstructorContent throws XPathException
1674+
:
1675+
( LCURLY RCURLY ) => LCURLY! RCURLY!
1676+
{ #compElemConstructorContent= #(#[PARENTHESIZED, "Parenthesized"], null); }
1677+
| LCURLY! e:expr RCURLY!
1678+
{ #compElemConstructorContent.copyLexInfo(#e); }
1679+
;
1680+
1681+
16731682
compAttrConstructor throws XPathException
16741683
{ String eq; }
16751684
:

exist-core/src/main/antlr/org/exist/xquery/parser/XQueryTree.g

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3157,8 +3157,11 @@ throws PermissionDeniedException, EXistException, XPathException
31573157
qnameExpr=expr [qnamePathExpr]
31583158
(
31593159
{ elementContent = new PathExpr(context); }
3160-
contentExpr=expr[elementContent]
3161-
{ construct.addPath(elementContent); }
3160+
contentExpr=ec:expr[elementContent]
3161+
{
3162+
elementContent.setASTNode(ec);
3163+
construct.addPathIfNotFunction(elementContent);
3164+
}
31623165
)*
31633166
)
31643167
|

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
9898
while (next != null) {
9999
context.proceed(this, builder);
100100
if (Type.subTypeOf(next.getType(), Type.FUNCTION_REFERENCE)) {
101-
throw new XPathException(ErrorCodes.XQTY0105, "Enclosed expression contains function item");
101+
throw new XPathException(this, ErrorCodes.XQTY0105, "Enclosed expression contains function item");
102102

103103
// if item is an atomic value, collect the string values of all
104104
// following atomic values and separate them by a space.

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

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@
3535
*/
3636
public class SequenceConstructor extends PathExpr {
3737

38-
public SequenceConstructor(XQueryContext context) {
38+
public SequenceConstructor(final XQueryContext context) {
3939
super(context);
4040
}
4141

42-
public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
42+
@Override
43+
public void analyze(final AnalyzeContextInfo contextInfo) throws XPathException {
4344
contextInfo.setParent(this);
4445
inPredicate = (contextInfo.getFlags() & IN_PREDICATE) > 0;
4546
unordered = (contextInfo.getFlags() & UNORDERED) > 0;
@@ -58,20 +59,20 @@ public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
5859
contextInfo.setStaticReturnType(staticType);
5960
}
6061

61-
/* (non-Javadoc)
62-
* @see org.exist.xquery.Expression#eval(org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
63-
*/
64-
public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
62+
@Override
63+
public Sequence eval(final Sequence contextSequence, final Item contextItem) throws XPathException {
6564
if (context.getProfiler().isEnabled()) {
6665
context.getProfiler().start(this);
6766
context.getProfiler().message(this, Profiler.DEPENDENCIES,
6867
"DEPENDENCIES", Dependency.getDependenciesName(this.getDependencies()));
69-
if (contextSequence != null)
70-
{context.getProfiler().message(this, Profiler.START_SEQUENCES,
71-
"CONTEXT SEQUENCE", contextSequence);}
72-
if (contextItem != null)
73-
{context.getProfiler().message(this, Profiler.START_SEQUENCES,
74-
"CONTEXT ITEM", contextItem.toSequence());}
68+
if (contextSequence != null) {
69+
context.getProfiler().message(this, Profiler.START_SEQUENCES,
70+
"CONTEXT SEQUENCE", contextSequence);
71+
}
72+
if (contextItem != null) {
73+
context.getProfiler().message(this, Profiler.START_SEQUENCES,
74+
"CONTEXT ITEM", contextItem.toSequence());
75+
}
7576
}
7677
final ValueSequence result = new ValueSequence();
7778
result.keepUnOrdered(unordered);
@@ -86,45 +87,61 @@ public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathExc
8687
context.popDocumentContext();
8788
}
8889
}
89-
if (context.getProfiler().isEnabled())
90-
{context.getProfiler().end(this, "", result);}
90+
if (context.getProfiler().isEnabled()) {
91+
context.getProfiler().end(this, "", result);
92+
}
9193
return result;
9294
}
9395

94-
/* (non-Javadoc)
95-
* @see org.exist.xquery.PathExpr#dump(org.exist.xquery.util.ExpressionDumper)
96-
*/
97-
public void dump(ExpressionDumper dumper) {
96+
@Override
97+
public void dump(final ExpressionDumper dumper) {
9898
dumper.display("(");
9999
dumper.startIndent();
100100
boolean moreThanOne = false;
101101
for(final Expression step : steps) {
102-
if (moreThanOne)
103-
{dumper.display(", ");}
102+
if (moreThanOne) {
103+
dumper.display(", ");
104+
}
104105
moreThanOne = true;
105106
step.dump(dumper);
106107
}
107108
dumper.endIndent();
108109
dumper.nl().display(")");
109110
}
110111

112+
@Override
111113
public String toString() {
112114
final StringBuilder result = new StringBuilder();
113115
result.append("( ");
114116
boolean moreThanOne = false;
115117
for (final Expression step : steps) {
116-
if (moreThanOne)
117-
{result.append(", ");}
118+
if (moreThanOne) {
119+
result.append(", ");
120+
}
118121
moreThanOne = true;
119122
result.append(step.toString());
120123
}
121124
result.append(" )");
122125
return result.toString();
123126
}
124127

125-
/* (non-Javadoc)
126-
* @see org.exist.xquery.Expression#returnsType()
128+
/**
129+
* Add another PathExpr to this object's expression list.
130+
* Performs a static check, if return type is a function type other than array.
131+
* see #id-content section 1.e.i and 1.e.ii
132+
*
133+
* @throws XPathException when Path returns a function type
134+
* @param path A path to add to this path
127135
*/
136+
public void addPathIfNotFunction(final PathExpr path) throws XPathException {
137+
final int retType = path.returnsType();
138+
if (Type.subTypeOf(retType, Type.FUNCTION_REFERENCE) && retType != Type.ARRAY) {
139+
throw new XPathException(path, ErrorCodes.XQTY0105, "Function types are not allowed in element content. Got " + Type.getTypeName(retType));
140+
}
141+
super.addPath(path);
142+
}
143+
144+
@Override
128145
public int returnsType() {
129146
return Type.ITEM;
130147
}
@@ -139,10 +156,8 @@ public boolean allowMixedNodesInReturn() {
139156
return true;
140157
}
141158

142-
/* (non-Javadoc)
143-
* @see org.exist.xquery.AbstractExpression#resetState()
144-
*/
145-
public void resetState(boolean postOptimization) {
159+
@Override
160+
public void resetState(final boolean postOptimization) {
146161
super.resetState(postOptimization);
147162
}
148163
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/*
2+
* eXist-db Open Source Native XML Database
3+
* Copyright (C) 2001 The eXist-db Authors
4+
*
5+
6+
* http://www.exist-db.org
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; either
11+
* version 2.1 of the License, or (at your option) any later version.
12+
*
13+
* This library is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16+
* Lesser General Public License for more details.
17+
*
18+
* You should have received a copy of the GNU Lesser General Public
19+
* License along with this library; if not, write to the Free Software
20+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21+
*/
22+
package org.exist.xquery;
23+
24+
import org.exist.EXistException;
25+
import org.exist.security.PermissionDeniedException;
26+
import org.exist.test.XQueryCompilationTest;
27+
import org.junit.Test;
28+
29+
import static org.exist.test.DiffMatcher.elemSource;
30+
import static org.exist.test.XQueryAssertions.*;
31+
32+
/**
33+
* Ensure function types returned in element content throws at compile time and
34+
* has location information.
35+
* See issue :<a href="https://github.com/eXist-db/exist/issues/3474">#3474</a>.
36+
*
37+
* @author <a href="mailto:[email protected]">Juri Leino</a>
38+
*/
39+
public class FunctionTypeInElementContentTest extends XQueryCompilationTest {
40+
41+
@Test
42+
public void arrayLiteral() throws EXistException, PermissionDeniedException {
43+
final String query = "element test { [] }";
44+
assertXQResultSimilar(elemSource("<test/>"), executeQuery(query));
45+
}
46+
47+
// TODO(JL): array content could be removed after https://github.com/eXist-db/exist/issues/3472 is fixed
48+
@Test
49+
public void arrayConstructor() throws EXistException, PermissionDeniedException {
50+
final String query = "element test { array { () } }";
51+
assertXQResultSimilar(elemSource("<test/>"), executeQuery(query));
52+
}
53+
54+
@Test
55+
public void sequenceOfItems() throws EXistException, PermissionDeniedException {
56+
final String query = "element test { (1, map {})[1] }";
57+
assertXQResultSimilar(elemSource("<test>1</test>"), executeQuery(query));
58+
}
59+
60+
@Test
61+
public void partialBuiltIn() throws EXistException, PermissionDeniedException {
62+
final String query = "element test { sum(?) }";
63+
final String error = "Function types are not allowed in element content. Got function(*)";
64+
assertXQStaticError(ErrorCodes.XQTY0105, 1, 16, error, compileQuery(query));
65+
}
66+
67+
// TODO(JL): Does still throw without location info
68+
@Test
69+
public void functionReference() throws EXistException, PermissionDeniedException {
70+
final String query = "element test { sum#0 }";
71+
final String error = "Function types are not allowed in element content. Got function(*)";
72+
assertXQStaticError(ErrorCodes.XQTY0105, 0, 0, error, compileQuery(query));
73+
}
74+
75+
// TODO(JL): Does not throw at compile time
76+
@Test
77+
public void functionVariable() throws EXistException, PermissionDeniedException {
78+
final String query = "let $f := function () {} return element test { $f }";
79+
final String error = "Enclosed expression contains function item";
80+
assertXQDynamicError(ErrorCodes.XQTY0105, 1, 49, error, executeQuery(query));
81+
}
82+
83+
// TODO(JL): user defined function has its location offset to a weird location
84+
@Test
85+
public void userDefinedFunction() throws EXistException, PermissionDeniedException {
86+
final String query = "element test { function () {} }";
87+
final String error = "Function types are not allowed in element content. Got function(*)";
88+
assertXQStaticError(ErrorCodes.XQTY0105, 1, 25, error, compileQuery(query));
89+
}
90+
91+
@Test
92+
public void mapConstructor() throws EXistException, PermissionDeniedException {
93+
final String query = "element test { map {} }";
94+
final String error = "Function types are not allowed in element content. Got map(*)";
95+
assertXQStaticError(ErrorCodes.XQTY0105, 1, 16, error, compileQuery(query));
96+
}
97+
98+
@Test
99+
public void mapConstructorLookup() throws EXistException, PermissionDeniedException {
100+
final String query = "element test { map {1:1}?1 }";
101+
assertXQResultSimilar(elemSource("<test>1</test>"), executeQuery(query));
102+
}
103+
104+
/**
105+
* sequence in enclosed expression with only a function type
106+
*/
107+
@Test
108+
public void sequenceOfMaps() throws EXistException, PermissionDeniedException {
109+
final String query = "element test { (map {}) }";
110+
final String error = "Function types are not allowed in element content. Got map(*)";
111+
assertXQDynamicError(ErrorCodes.XQTY0105, 0, 0, error, executeQuery(query));
112+
}
113+
114+
// TODO(JL): add (sub-expression) location
115+
/**
116+
* This is an edge case, which would evaluate to empty sequence
117+
* but should arguably still throw.
118+
*/
119+
@Test
120+
public void sequenceOfMapsEdgeCase() throws EXistException, PermissionDeniedException {
121+
final String query = "element test { (map {})[2] }";
122+
final String error = "Function types are not allowed in element content. Got map(*)";
123+
assertXQStaticError(ErrorCodes.XQTY0105, 0, 0, error, compileQuery(query));
124+
}
125+
126+
// TODO(JL): add (sub-expression) location
127+
// TODO(JL): this could throw at compile time
128+
@Test
129+
public void arrayOfMaps() throws EXistException, PermissionDeniedException {
130+
final String query = "element test { [map {}] }";
131+
final String error = "Enclosed expression contains function item";
132+
assertXQDynamicError(ErrorCodes.XQTY0105, 1, 16, error, executeQuery(query));
133+
};
134+
135+
// TODO(JL): add (sub-expression) location
136+
// TODO(JL): This should throw at compile time, but does not
137+
@Test
138+
public void mapConstructorInSubExpression() throws EXistException, PermissionDeniedException {
139+
final String query = "element test { \"a\", map {} }";
140+
final String error = "Enclosed expression contains function item";
141+
assertXQDynamicError(ErrorCodes.XQTY0105, 0, 0, error, executeQuery(query));
142+
}
143+
}

exist-core/src/test/xquery/errors.xql

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -325,14 +325,50 @@ function et:issue3473c() {
325325
}
326326
};
327327

328-
(: https://github.com/eXist-db/exist/issues/3474 :)
328+
(:~
329+
: function type item returned in element content in sequence at runtime
330+
: related to https://github.com/eXist-db/exist/issues/3474
331+
:)
329332
declare
330-
%test:pending("to be addressed in another PR")
331-
%test:assertEquals(333)
332-
function et:issue3474() {
333+
%test:assertTrue
334+
%test:pending("location info still missing")
335+
function et:subexpression-in-enclosed-expression-evaluates-to-map() {
333336
try {
334-
element foo { map { "x": "y" } }
335-
} catch * {
336-
$err:line-number
337+
element test { 1, map {} }
338+
}
339+
catch err:XQTY0105 {
340+
exists($err:line-number) and
341+
$err:line-number > 0 and
342+
exists($err:column-number) and
343+
$err:column-number > 0
344+
}
345+
};
346+
347+
declare
348+
%test:assertTrue
349+
%test:pending("location info still missing")
350+
function et:enclosed-expression-evaluates-to-map() {
351+
try {
352+
element test { ( "a", map {} )[2] }
353+
}
354+
catch err:XQTY0105 {
355+
exists($err:line-number) and
356+
$err:line-number > 0 and
357+
exists($err:column-number) and
358+
$err:column-number > 0
337359
}
338-
};
360+
};
361+
362+
declare
363+
%test:assertTrue
364+
function et:array-in-enclosed-expression-evaluates-to-map() {
365+
try {
366+
element test { [map {}] }
367+
}
368+
catch err:XQTY0105 {
369+
exists($err:line-number) and
370+
$err:line-number > 0 and
371+
exists($err:column-number) and
372+
$err:column-number > 0
373+
}
374+
};

0 commit comments

Comments
 (0)