Skip to content

Commit ce193ae

Browse files
committed
Fix #356
1 parent 21a7542 commit ce193ae

File tree

10 files changed

+280
-89
lines changed

10 files changed

+280
-89
lines changed

release-notes/VERSION

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ JSON library.
2828
(reported by Brad H)
2929
#340: Making `WriterBasedJsonGenerator` non-final
3030
(requested by rfoltyns@github)
31+
#356: Improve indication of "source reference" in `JsonLocation` wrt `byte[]`,`char[]`
3132

3233
2.8.7 (not yet released)
3334

src/main/java/com/fasterxml/jackson/core/JsonLocation.java

+85-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package com.fasterxml.jackson.core;
77

8+
import java.nio.charset.Charset;
9+
810
/**
911
* Object that encapsulates Location information used for reporting
1012
* parsing (or potentially generation) errors, as well as current location
@@ -15,17 +17,29 @@ public class JsonLocation
1517
{
1618
private static final long serialVersionUID = 1L;
1719

20+
/**
21+
* Include at most first 500 characters/bytes from contents; should be enough
22+
* to give context, but not cause unfortunate side effects in things like
23+
* logs.
24+
*
25+
* @since 2.9
26+
*/
27+
public static final int MAX_CONTENT_SNIPPET = 500;
28+
1829
/**
1930
* Shared immutable "N/A location" that can be returned to indicate
20-
* that no location information is available
31+
* that no location information is available.
32+
*<p>
33+
* NOTE: before 2.9, Location was given as String "N/A"; with 2.9 it was
34+
* removed so that source should be indicated as "UNKNOWN".
2135
*/
22-
public final static JsonLocation NA = new JsonLocation("N/A", -1L, -1L, -1, -1);
36+
public final static JsonLocation NA = new JsonLocation(null, -1L, -1L, -1, -1);
2337

24-
final long _totalBytes;
25-
final long _totalChars;
38+
protected final long _totalBytes;
39+
protected final long _totalChars;
2640

27-
final int _lineNr;
28-
final int _columnNr;
41+
protected final int _lineNr;
42+
protected final int _columnNr;
2943

3044
/**
3145
* Displayable description for input source: file path, URL.
@@ -64,6 +78,17 @@ public JsonLocation(Object sourceRef, long totalBytes, long totalChars,
6478
*/
6579
public Object getSourceRef() { return _sourceRef; }
6680

81+
/**
82+
* Accessor for getting a textual description of source reference
83+
* (Object returned by {@link #getSourceRef()}), as included in
84+
* description returned by {@link #toString()}.
85+
*
86+
* @since 2.9
87+
*/
88+
public String getSourceDescription() {
89+
return _appendSourceDesc(new StringBuilder(100)).toString();
90+
}
91+
6792
/**
6893
* @return Line number of the location (1-based)
6994
*/
@@ -94,11 +119,7 @@ public String toString()
94119
{
95120
StringBuilder sb = new StringBuilder(80);
96121
sb.append("[Source: ");
97-
if (_sourceRef == null) {
98-
sb.append("UNKNOWN");
99-
} else {
100-
sb.append(_sourceRef.toString());
101-
}
122+
_appendSourceDesc(sb);
102123
sb.append("; line: ");
103124
sb.append(_lineNr);
104125
sb.append(", column: ");
@@ -107,6 +128,59 @@ public String toString()
107128
return sb.toString();
108129
}
109130

131+
protected StringBuilder _appendSourceDesc(StringBuilder sb)
132+
{
133+
final Object srcRef = _sourceRef;
134+
135+
if (srcRef == null) {
136+
sb.append("UNKNOWN");
137+
return sb;
138+
}
139+
// First, figure out what name to use as source type
140+
Class<?> srcType = (srcRef instanceof Class<?>) ?
141+
((Class<?>) srcRef) : srcRef.getClass();
142+
String tn = srcType.getName();
143+
// standard JDK types without package
144+
if (tn.startsWith("java.")) {
145+
tn = srcType.getSimpleName();
146+
} else if (srcRef instanceof byte[]) { // then some other special cases
147+
tn = "byte[]";
148+
} else if (srcRef instanceof char[]) {
149+
tn = "char[]";
150+
}
151+
sb.append('(').append(tn).append(')');
152+
// and then, include (part of) contents for selected types:
153+
int len;
154+
String charStr = " chars";
155+
156+
if (srcRef instanceof CharSequence) {
157+
CharSequence cs = (CharSequence) srcRef;
158+
len = cs.length();
159+
len -= _append(sb, cs.subSequence(0, Math.min(len, MAX_CONTENT_SNIPPET)).toString());
160+
} else if (srcRef instanceof char[]) {
161+
char[] ch = (char[]) srcRef;
162+
len = ch.length;
163+
len -= _append(sb, new String(ch, 0, Math.min(len, MAX_CONTENT_SNIPPET)));
164+
} else if (srcRef instanceof byte[]) {
165+
byte[] b = (byte[]) srcRef;
166+
int maxLen = Math.min(b.length, MAX_CONTENT_SNIPPET);
167+
_append(sb, new String(b, 0, maxLen, Charset.forName("UTF-8")));
168+
len = b.length - maxLen;
169+
charStr = " bytes";
170+
} else {
171+
len = 0;
172+
}
173+
if (len > 0) {
174+
sb.append("[truncated ").append(len).append(charStr).append(']');
175+
}
176+
return sb;
177+
}
178+
179+
private int _append(StringBuilder sb, String content) {
180+
sb.append('"').append(content).append('"');
181+
return content.length();
182+
}
183+
110184
@Override
111185
public int hashCode()
112186
{

src/main/java/com/fasterxml/jackson/core/JsonParser.java

+62-36
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public enum Feature {
5858
* Feature is enabled by default.
5959
*/
6060
AUTO_CLOSE_SOURCE(true),
61-
61+
6262
// // // Support for non-standard data format constructs
6363

6464
/**
@@ -169,6 +169,49 @@ public enum Feature {
169169
*/
170170
ALLOW_NON_NUMERIC_NUMBERS(false),
171171

172+
/**
173+
* Feature allows the support for "missing" values in a JSON array: missing
174+
* value meaning sequence of two commas, without value in-between but only
175+
* optional white space.
176+
* Enabling this feature will expose "missing" values as {@link JsonToken#VALUE_NULL}
177+
* tokens, which typically become Java nulls in arrays and {@link java.util.Collection}
178+
* in data-binding.
179+
* <p>
180+
* For example, enabling this feature will represent a JSON array <code>["value1",,"value3",]</code>
181+
* as <code>["value1", null, "value3", null]</code>
182+
* <p>
183+
* Since the JSON specification does not allow missing values this is a non-compliant JSON
184+
* feature and is disabled by default.
185+
*
186+
* @since 2.8
187+
*/
188+
ALLOW_MISSING_VALUES(false),
189+
190+
/**
191+
* Feature that determines whether {@link JsonParser} will allow for a single trailing
192+
* comma following the final value (in an Array) or member (in an Object). These commas
193+
* will simply be ignored.
194+
* <p>
195+
* For example, when this feature is enabled, <code>[true,true,]</code> is equivalent to
196+
* <code>[true, true]</code> and <code>{"a": true,}</code> is equivalent to
197+
* <code>{"a": true}</code>.
198+
* <p>
199+
* When combined with <code>ALLOW_MISSING_VALUES</code>, this feature takes priority, and
200+
* the final trailing comma in an array declaration does not imply a missing
201+
* (<code>null</code>) value. For example, when both <code>ALLOW_MISSING_VALUES</code>
202+
* and <code>ALLOW_TRAILING_COMMA</code> are enabled, <code>[true,true,]</code> is
203+
* equivalent to <code>[true, true]</code>, and <code>[true,true,,]</code> is equivalent to
204+
* <code>[true, true, null]</code>.
205+
* <p>
206+
* Since the JSON specification does not permit trailing commas, this is a non-standard
207+
* feature, and as such disabled by default.
208+
*
209+
* @since 2.9
210+
*/
211+
ALLOW_TRAILING_COMMA(false),
212+
213+
// // // Validity checks
214+
172215
/**
173216
* Feature that determines whether {@link JsonParser} will explicitly
174217
* check that no duplicate JSON Object field names are encountered.
@@ -211,46 +254,29 @@ public enum Feature {
211254
*/
212255
IGNORE_UNDEFINED(false),
213256

214-
/**
215-
* Feature allows the support for "missing" values in a JSON array: missing
216-
* value meaning sequence of two commas, without value in-between but only
217-
* optional white space.
218-
* Enabling this feature will expose "missing" values as {@link JsonToken#VALUE_NULL}
219-
* tokens, which typically become Java nulls in arrays and {@link java.util.Collection}
220-
* in data-binding.
221-
* <p>
222-
* For example, enabling this feature will represent a JSON array <code>["value1",,"value3",]</code>
223-
* as <code>["value1", null, "value3", null]</code>
224-
* <p>
225-
* Since the JSON specification does not allow missing values this is a non-compliant JSON
226-
* feature and is disabled by default.
227-
*
228-
* @since 2.8
229-
*/
230-
ALLOW_MISSING_VALUES(false),
257+
// // // Other
231258

232259
/**
233-
* Feature that determines whether {@link JsonParser} will allow for a single trailing
234-
* comma following the final value (in an Array) or member (in an Object). These commas
235-
* will simply be ignored.
236-
* <p>
237-
* For example, when this feature is enabled, <code>[true,true,]</code> is equivalent to
238-
* <code>[true, true]</code> and <code>{"a": true,}</code> is equivalent to
239-
* <code>{"a": true}</code>.
240-
* <p>
241-
* When combined with <code>ALLOW_MISSING_VALUES</code>, this feature takes priority, and
242-
* the final trailing comma in an array declaration does not imply a missing
243-
* (<code>null</code>) value. For example, when both <code>ALLOW_MISSING_VALUES</code>
244-
* and <code>ALLOW_TRAILING_COMMA</code> are enabled, <code>[true,true,]</code> is
245-
* equivalent to <code>[true, true]</code>, and <code>[true,true,,]</code> is equivalent to
246-
* <code>[true, true, null]</code>.
247-
* <p>
248-
* Since the JSON specification does not permit trailing commas, this is a non-standard
249-
* feature, and as such disabled by default.
260+
* Feature that determines whether {@link JsonLocation} instances should be constructed
261+
* with reference to source or not. If source reference is included, its type and contents
262+
* are included when `toString()` method is called (most notably when printing out parse
263+
* exception with that location information). If feature is disabled, no source reference
264+
* is passed and source is only indicated as "UNKNOWN".
265+
*<p>
266+
* Most common reason for disabling this feature is to avoid leaking information about
267+
* internal information; this may be done for security reasons.
268+
* Note that even if source reference is included, only parts of contents are usually
269+
* printed, and not the whole contents. Further, many source reference types can not
270+
* necessarily access contents (like streams), so only type is indicated, not contents.
271+
*<p>
272+
* Feature is enabled by default, meaning that "source reference" information is passed
273+
* and some or all of the source content may be included in {@link JsonLocation} information
274+
* constructed either when requested explicitly, or when needed for an exception.
250275
*
251276
* @since 2.9
252277
*/
253-
ALLOW_TRAILING_COMMA(false)
278+
INCLUDE_SOURCE_IN_LOCATION(true),
279+
254280
;
255281

256282
/**

src/main/java/com/fasterxml/jackson/core/base/ParserBase.java

+31-18
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ protected void _checkStdFeatureChanges(int newFeatureFlags, int changedFeatures)
442442
*/
443443
@Override
444444
public JsonLocation getTokenLocation() {
445-
return new JsonLocation(_ioContext.getSourceReference(),
445+
return new JsonLocation(_getSourceReference(),
446446
-1L, getTokenCharacterOffset(), // bytes, chars
447447
getTokenLineNr(),
448448
getTokenColumnNr());
@@ -455,7 +455,7 @@ public JsonLocation getTokenLocation() {
455455
@Override
456456
public JsonLocation getCurrentLocation() {
457457
int col = _inputPtr - _currInputRowStart + 1; // 1-based
458-
return new JsonLocation(_ioContext.getSourceReference(),
458+
return new JsonLocation(_getSourceReference(),
459459
-1L, _currInputProcessed + _inputPtr, // bytes, chars
460460
_currInputRow, col);
461461
}
@@ -504,7 +504,7 @@ public int getTokenColumnNr() {
504504

505505
/*
506506
/**********************************************************
507-
/* Abstract methods needed from sub-classes
507+
/* Abstract methods for sub-classes to implement
508508
/**********************************************************
509509
*/
510510

@@ -543,7 +543,7 @@ protected void _handleEOF() throws JsonParseException {
543543
_reportInvalidEOF(String.format(
544544
": expected close marker for %s (start marker at %s)",
545545
marker,
546-
_parsingContext.getStartLocation(_ioContext.getSourceReference())),
546+
_parsingContext.getStartLocation(_getSourceReference())),
547547
null);
548548
}
549549
}
@@ -555,17 +555,6 @@ protected final int _eofAsNextChar() throws JsonParseException {
555555
_handleEOF();
556556
return -1;
557557
}
558-
559-
/*
560-
/**********************************************************
561-
/* Internal/package methods: Error reporting
562-
/**********************************************************
563-
*/
564-
565-
protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException {
566-
String startDesc = ""+_parsingContext.getStartLocation(_ioContext.getSourceReference());
567-
_reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.typeDesc()+" starting at "+startDesc+")");
568-
}
569558

570559
/*
571560
/**********************************************************
@@ -1051,13 +1040,18 @@ protected void convertNumberToBigDecimal() throws IOException
10511040
}
10521041
_numTypesValid |= NR_BIGDECIMAL;
10531042
}
1054-
1043+
10551044
/*
10561045
/**********************************************************
1057-
/* Number handling exceptions
1046+
/* Internal/package methods: Error reporting
10581047
/**********************************************************
1059-
*/
1048+
*/
10601049

1050+
protected void _reportMismatchedEndMarker(int actCh, char expCh) throws JsonParseException {
1051+
String startDesc = ""+_parsingContext.getStartLocation(_getSourceReference());
1052+
_reportError("Unexpected close marker '"+((char) actCh)+"': expected '"+expCh+"' (for "+_parsingContext.typeDesc()+" starting at "+startDesc+")");
1053+
}
1054+
10611055
protected void reportUnexpectedNumberChar(int ch, String comment) throws JsonParseException {
10621056
String msg = "Unexpected character ("+_getCharDesc(ch)+") in numeric value";
10631057
if (comment != null) {
@@ -1162,6 +1156,25 @@ protected IllegalArgumentException reportInvalidBase64Char(Base64Variant b64vari
11621156
return new IllegalArgumentException(base);
11631157
}
11641158

1159+
/*
1160+
/**********************************************************
1161+
/* Internal/package methods: other
1162+
/**********************************************************
1163+
*/
1164+
1165+
/**
1166+
* Helper method used to encapsulate logic of including (or not) of
1167+
* "source reference" when constructing {@link JsonLocation} instances.
1168+
*
1169+
* @since 2.9
1170+
*/
1171+
protected Object _getSourceReference() {
1172+
if (JsonParser.Feature.INCLUDE_SOURCE_IN_LOCATION.enabledIn(_features)) {
1173+
return _ioContext.getSourceReference();
1174+
}
1175+
return null;
1176+
}
1177+
11651178
/*
11661179
/**********************************************************
11671180
/* Stuff that was abstract and required before 2.8, but that

src/main/java/com/fasterxml/jackson/core/base/ParserMinimalBase.java

+4-7
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,8 @@ protected ParserMinimalBase() { }
127127

128128
@Override
129129
public JsonToken nextValue() throws IOException {
130-
/* Implementation should be as trivial as follows; only
131-
* needs to change if we are to skip other tokens (for
132-
* example, if comments were exposed as tokens)
133-
*/
130+
// Implementation should be as trivial as follows; only needs to change if
131+
// we are to skip other tokens (for example, if comments were exposed as tokens)
134132
JsonToken t = nextToken();
135133
if (t == JsonToken.FIELD_NAME) {
136134
t = nextToken();
@@ -147,9 +145,8 @@ public JsonParser skipChildren() throws IOException
147145
}
148146
int open = 1;
149147

150-
/* Since proper matching of start/end markers is handled
151-
* by nextToken(), we'll just count nesting levels here
152-
*/
148+
// Since proper matching of start/end markers is handled
149+
// by nextToken(), we'll just count nesting levels here
153150
while (true) {
154151
JsonToken t = nextToken();
155152
if (t == null) {

0 commit comments

Comments
 (0)