1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336
|
/*
* Copyright (c) 2004, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 5033550
* @summary JDWP back end uses modified UTF-8
* @author jjh
*
* @run build TestScaffold VMConnection TargetListener TargetAdapter
* @run compile -g UTF8Test.java
* @run driver UTF8Test
*/
/*
There is UTF-8 and there is modified UTF-8, which I will call M-UTF-8.
The two differ in the representation of binary 0, and
in some other more esoteric representations.
See
http://java.sun.com/developer/technicalArticles/Intl/Supplementary/#Modified_UTF-8
http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/types.html#wp16542
All the following are observations of the treatment
of binary 0. In UTF-8, this represented as one byte:
0x00
while in modified UTF-8, it is represented as two bytes
0xc0 0x80
** I haven't investigated if the other differences between UTF-8 and
M-UTF-8 are handled in the same way.
Here is how these our handled in our BE, JDWP, and FE:
- Strings in .class files are M-UTF-8.
- To get the value of a string object from the VM, our BE calls
char * utf = JNI_FUNC_PTR(env,GetStringUTFChars)(env, string, NULL);
which returns M-UTF-8.
- To create a string object in the VM, our BE VirtualMachine.createString() calls
string = JNI_FUNC_PTR(env,NewStringUTF)(env, cstring);
This function expects the string to be M-UTF-8
BUG: If the string came from JDWP, then it is actually UTF-8
- I haven't investigated strings in JVMTI.
- The JDWP spec says that strings are UTF-8. The intro
says this for all strings, and the createString command and
the StringRefernce.value command say it explicitly.
- Our FE java writes strings to JDWP as UTF-8.
- BE function outStream_writeString uses strlen meaning
it expects no 0 bytes, meaning that it expects M-UTF-8
This function writes the byte length and then calls
outStream.c::writeBytes which just writes the bytes to JDWP as is.
BUG: If such a string came from the VM via JNI, it is actually
M-UTF-8
FIX: - scan string to see if contains an M-UTF-8 char.
if yes,
- call String(bytes, 0, len, "UTF8")
to get a java string. Will this work -ie, the
input is M-UTF-8 instead of real UTF-8
- call some java method (NOT JNI which
would just come back with M-UTF-8)
on the String to get real UTF-8
- The JDWP StringReference.value command does reads a string
from the BE out of the JDWP stream and does this to
createe a Java String for it (see PacketStream.readString):
String readString() {
String ret;
int len = readInt();
try {
ret = new String(pkt.data, inCursor, len, "UTF8");
} catch(java.io.UnsupportedEncodingException e) {
This String ctor converts _both- the M-UTF-8 0xc0 0x80
and UTF-8 0x00 into a Java char containing 0x0000
Does it do this for the other differences too?
Summary:
1. JDWP says strings are UTF-8.
We interpret this to mean standard UTF-8.
2. JVMTI will be changed to match JNI saying that strings
are M-UTF-8.
3. The BE gets UTF-8 strings off JDWP and must convert them to
M-UTF-8 before giving it to JVMTI or JNI.
4. The BE gets M-UTF-8 strings from JNI and JVMTI and
must convert them to UTF-8 when writing to JDWP.
Here is how the supplementals are represented in java Strings.
This from java.lang.Character doc:
The Java 2 platform uses the UTF-16 representation in char arrays and
in the String and StringBuffer classes. In this representation,
supplementary characters are represented as a pair of char values,
the first from the high-surrogates range, (\uD800-\uDBFF), the second
from the low-surrogates range (\uDC00-\uDFFF).
See utf8.txt
----
NSK Packet.java in the nsk/share/jdwp framework does this to write
a string to JDWP:
public void addString(String value) {
final int count = JDWP.TypeSize.INT + value.length();
addInt(value.length());
try {
addBytes(value.getBytes("UTF-8"), 0, value.length());
} catch (UnsupportedEncodingException e) {
throw new Failure("Unsupported UTF-8 ecnoding while adding string value to JDWP packet:\n\t"
+ e);
}
}
?? Does this get the standard UTF-8? I would expect so.
and the readString method does this:
for (int i = 0; i < len; i++)
s[i] = getByte();
try {
return new String(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new Failure("Unsupported UTF-8 ecnoding while extracting string value from JDWP packet:\n\t"
+ e);
}
Thus, this won't notice the modified UTF-8 coming in from JDWP .
*/
import com.sun.jdi.*;
import com.sun.jdi.event.*;
import com.sun.jdi.request.*;
import java.io.UnsupportedEncodingException;
import java.util.*;
/********** target program **********/
/*
* The debuggee has a few Strings the debugger reads via JDI
*/
class UTF8Targ {
static String[] vals = new String[] {"xx\u0000yy", // standard UTF-8 0
"xx\ud800\udc00yy", // first supplementary
"xx\udbff\udfffyy" // last supplementary
// d800 = 1101 1000 0000 0000 dc00 = 1101 1100 0000 0000
// dbff = 1101 1011 1111 1111 dfff = 1101 1111 1111 1111
};
static String aField;
public static void main(String[] args){
System.out.println("Howdy!");
gus();
System.out.println("Goodbye from UTF8Targ!");
}
static void gus() {
}
}
/********** test program **********/
public class UTF8Test extends TestScaffold {
ClassType targetClass;
ThreadReference mainThread;
Field targetField;
UTF8Test (String args[]) {
super(args);
}
public static void main(String[] args) throws Exception {
new UTF8Test(args).startTests();
}
/********** test core **********/
protected void runTests() throws Exception {
/*
* Get to the top of main()
* to determine targetClass and mainThread
*/
BreakpointEvent bpe = startToMain("UTF8Targ");
targetClass = (ClassType)bpe.location().declaringType();
targetField = targetClass.fieldByName("aField");
ArrayReference targetVals = (ArrayReference)targetClass.getValue(targetClass.fieldByName("vals"));
/* For each string in the debuggee's 'val' array, verify that we can
* read that value via JDI.
*/
for (int ii = 0; ii < UTF8Targ.vals.length; ii++) {
StringReference val = (StringReference)targetVals.getValue(ii);
String valStr = val.value();
/*
* Verify that we can read a value correctly.
* We read it via JDI, and access it directly from the static
* var in the debuggee class.
*/
if (!valStr.equals(UTF8Targ.vals[ii]) ||
valStr.length() != UTF8Targ.vals[ii].length()) {
failure(" FAILED: Expected /" + printIt(UTF8Targ.vals[ii]) +
"/, but got /" + printIt(valStr) + "/, length = " + valStr.length());
}
}
/* Test 'all' unicode chars - send them to the debuggee via JDI
* and then read them back.
*/
doFancyVersion();
resumeTo("UTF8Targ", "gus", "()V");
try {
Thread.sleep(1000);
} catch (InterruptedException ee) {
}
/*
* resume the target listening for events
*/
listenUntilVMDisconnect();
/*
* deal with results of test
* if anything has called failure("foo") testFailed will be true
*/
if (!testFailed) {
println("UTF8Test: passed");
} else {
throw new Exception("UTF8Test: failed");
}
}
/**
* For each unicode value, send a string containing
* it to the debuggee via JDI, read it back via JDI, and see if
* we get the same value.
*/
void doFancyVersion() throws Exception {
// This does 4 chars at a time just to save time.
for (int ii = Character.MIN_CODE_POINT;
ii < Character.MIN_SUPPLEMENTARY_CODE_POINT;
ii += 4) {
// Skip the surrogates
if (ii == Character.MIN_SURROGATE) {
ii = Character.MAX_SURROGATE - 3;
break;
}
doFancyTest(ii, ii + 1, ii + 2, ii + 3);
}
// Do the supplemental chars.
for (int ii = Character.MIN_SUPPLEMENTARY_CODE_POINT;
ii <= Character.MAX_CODE_POINT;
ii += 2000) {
// Too many of these so just do a few
doFancyTest(ii, ii + 1, ii + 2, ii + 3);
}
}
void doFancyTest(int ... args) throws Exception {
String ss = new String(args, 0, 4);
targetClass.setValue(targetField, vm().mirrorOf(ss));
StringReference returnedVal = (StringReference)targetClass.getValue(targetField);
String returnedStr = returnedVal.value();
if (!ss.equals(returnedStr)) {
failure("Set: FAILED: Expected /" + printIt(ss) +
"/, but got /" + printIt(returnedStr) + "/, length = " + returnedStr.length());
}
}
/**
* Return a String containing binary representations of
* the chars in a String.
*/
String printIt(String arg) {
char[] carray = arg.toCharArray();
StringBuffer bb = new StringBuffer(arg.length() * 5);
for (int ii = 0; ii < arg.length(); ii++) {
int ccc = arg.charAt(ii);
bb.append(String.format("%1$04x ", ccc));
}
return bb.toString();
}
String printIt1(String arg) {
byte[] barray = null;
try {
barray = arg.getBytes("UTF-8");
} catch (UnsupportedEncodingException ee) {
}
StringBuffer bb = new StringBuffer(barray.length * 3);
for (int ii = 0; ii < barray.length; ii++) {
bb.append(String.format("%1$02x ", barray[ii]));
}
return bb.toString();
}
}
|