1
2
3
4 package joeq.Main;
5
6 import java.util.Collection;
7 import java.util.Comparator;
8 import java.util.HashSet;
9 import java.util.Iterator;
10 import java.util.LinkedList;
11 import java.util.List;
12 import java.util.StringTokenizer;
13 import java.util.TreeSet;
14 import java.io.BufferedReader;
15 import java.io.File;
16 import java.io.IOException;
17 import java.io.InputStreamReader;
18 import java.net.URL;
19 import joeq.Class.PrimordialClassLoader;
20 import joeq.Class.jq_Array;
21 import joeq.Class.jq_Class;
22 import joeq.Class.jq_Method;
23 import joeq.Class.jq_MethodVisitor;
24 import joeq.Class.jq_Primitive;
25 import joeq.Class.jq_StaticField;
26 import joeq.Class.jq_StaticMethod;
27 import joeq.Class.jq_Type;
28 import joeq.Class.jq_TypeVisitor;
29 import joeq.Compiler.Quad.BasicBlockVisitor;
30 import joeq.Compiler.Quad.CallGraph;
31 import joeq.Compiler.Quad.CodeCache;
32 import joeq.Compiler.Quad.ControlFlowGraphVisitor;
33 import joeq.Compiler.Quad.LoadedCallGraph;
34 import joeq.Compiler.Quad.QuadVisitor;
35 import joeq.Runtime.Reflection;
36 import joeq.UTF.Utf8;
37 import jwutil.console.SimpleInterpreter;
38 import jwutil.strings.Strings;
39 import jwutil.util.Assert;
40
41
42
43
44
45 public abstract class Driver {
46
47 public static void main(String[] args) {
48
49 HostedVM.initialize();
50
51 try {
52 interpreterClass = Class.forName("joeq.Interpreter.QuadInterpreter", false, Driver.class.getClassLoader());
53 } catch (ClassNotFoundException x) {
54 System.err.println("Warning: interpreter class not found.");
55 }
56
57 SimpleInterpreter si = new SimpleInterpreter((URL[])null);
58 if ((args.length == 0) || args[0].equals("-i")) {
59
60 BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
61 for (; ;) {
62 String[] commands;
63 try {
64 System.out.print("joeq> ");
65 String line = in.readLine();
66 if (line == null) return;
67 StringTokenizer st = new StringTokenizer(line);
68 int size = st.countTokens();
69 commands = new String[size];
70 for (int i = 0; i < size; ++i) {
71 commands[i] = st.nextToken();
72 }
73 Assert._assert(!st.hasMoreTokens());
74 } catch (IOException x) {
75 System.err.println(x.toString());
76 return;
77 }
78 for (int i = 0; i < commands.length; ++i) {
79 i = processCommand(commands, i, si);
80 }
81 }
82 }
83 for (int i = 0; i < args.length; ++i) {
84 i = processCommand(args, i, si);
85 }
86 }
87
88 public static List classesToProcess = new LinkedList();
89 static HashSet methodNamesToProcess;
90 static boolean trace_bb = false;
91 static boolean trace_cfg = false;
92 static boolean trace_method = false;
93 static boolean trace_type = false;
94
95 public static boolean ignore_linkage_errors = false;
96
97 static Class interpreterClass;
98
99 private static void addClassesInPackage(String pkgName, boolean recursive) {
100 String canonicalPackageName = pkgName.replace('.', '/');
101 if (!canonicalPackageName.endsWith("/")) canonicalPackageName += '/';
102 Iterator i = PrimordialClassLoader.loader.listPackage(canonicalPackageName, recursive);
103 if (!i.hasNext()) {
104 System.err.println("Package " + canonicalPackageName + " not found.");
105 }
106
107
108 HashSet loaded = new HashSet();
109 while (i.hasNext()) {
110 String canonicalClassName = canonicalizeClassName((String) i.next());
111 if (loaded.contains(canonicalClassName))
112 continue;
113 loaded.add(canonicalClassName);
114 try {
115 jq_Class c = (jq_Class) PrimordialClassLoader.loader.getOrCreateBSType(canonicalClassName);
116 c.load();
117 classesToProcess.add(c);
118 } catch (NoClassDefFoundError x) {
119 System.err.println("Package " + pkgName + ": Class not found (canonical name " + canonicalClassName + ").");
120 } catch (LinkageError le) {
121 if (!ignore_linkage_errors)
122 throw le;
123 System.err.println("Linkage error occurred while loading class (" + canonicalClassName + "):");
124 le.printStackTrace(System.err);
125 }
126 }
127 }
128
129 public static int processCommand(String[] commandBuffer, int index) {
130 return processCommand(commandBuffer, index, (SimpleInterpreter)null);
131 }
132
133 public static int processCommand(String[] commandBuffer, int index, SimpleInterpreter si) {
134 try {
135 if (commandBuffer[index].equalsIgnoreCase("addtoclasspath")) {
136 String path = commandBuffer[++index];
137 PrimordialClassLoader.loader.addToClasspath(path);
138 } else if (commandBuffer[index].equalsIgnoreCase("trace")) {
139 String which = commandBuffer[++index];
140 if (which.equalsIgnoreCase("bb")) {
141 trace_bb = true;
142 } else if (which.equalsIgnoreCase("cfg")) {
143 trace_cfg = true;
144 } else if (which.equalsIgnoreCase("method")) {
145 trace_method = true;
146 } else if (which.equalsIgnoreCase("type")) {
147 trace_type = true;
148 } else {
149 System.err.println("Unknown trace option " + which);
150 }
151 } else if (commandBuffer[index].equalsIgnoreCase("method")) {
152 if (methodNamesToProcess == null) methodNamesToProcess = new HashSet();
153 methodNamesToProcess.add(commandBuffer[++index]);
154 } else if (commandBuffer[index].equalsIgnoreCase("class")) {
155 String canonicalClassName = canonicalizeClassName(commandBuffer[++index]);
156 try {
157 jq_Class c = (jq_Class) PrimordialClassLoader.loader.getOrCreateBSType(canonicalClassName);
158 c.load();
159 classesToProcess.add(c);
160 } catch (NoClassDefFoundError x) {
161 System.err.println("Class " + commandBuffer[index] + " (canonical name " + canonicalClassName + ") not found.");
162 }
163 } else if (commandBuffer[index].equalsIgnoreCase("package")) {
164 addClassesInPackage(commandBuffer[++index],
165 } else if (commandBuffer[index].equalsIgnoreCase("packages")) {
166 addClassesInPackage(commandBuffer[++index],
167 } else if (commandBuffer[index].equalsIgnoreCase("callgraph")) {
168 String callgraphFile = commandBuffer[++index];
169 try {
170 CallGraph cg = new LoadedCallGraph(callgraphFile);
171 HashSet set = new HashSet();
172 for (Iterator i = cg.getAllMethods().iterator(); i.hasNext(); ) {
173 jq_Method m = (jq_Method) i.next();
174 set.add(m.getDeclaringClass());
175 }
176 classesToProcess.addAll(set);
177 } catch (IOException x) {
178 }
179 } else if (commandBuffer[index].equalsIgnoreCase("setinterpreter")) {
180 String interpreterClassName = commandBuffer[++index];
181 try {
182 Class cl = Class.forName(interpreterClassName);
183 if (Class.forName("joeq.Interpreter.QuadInterpreter").isAssignableFrom(cl)) {
184 interpreterClass = cl;
185 System.out.println("Interpreter class changed to " + interpreterClass);
186 } else {
187 System.err.println("Class " + interpreterClassName + " does not subclass joeq.Interpreter.QuadInterpreter.");
188 }
189 } catch (java.lang.ClassNotFoundException x) {
190 System.err.println("Cannot find interpreter named " + interpreterClassName + ".");
191 System.err.println("Check your classpath and make sure you compiled your interpreter.");
192 return index;
193 }
194
195 } else if (commandBuffer[index].equalsIgnoreCase("interpret")) {
196 String fullName = commandBuffer[++index];
197 int b = fullName.lastIndexOf('.') + 1;
198 String methodName = fullName.substring(b);
199 String className = canonicalizeClassName(fullName.substring(0, b - 1));
200 try {
201 jq_Class c = (jq_Class) PrimordialClassLoader.loader.getOrCreateBSType(className);
202 c.cls_initialize();
203 jq_StaticMethod m = null;
204 Utf8 rootm_name = Utf8.get(methodName);
205 for (Iterator it = java.util.Arrays.asList(c.getDeclaredStaticMethods()).iterator(); it.hasNext();) {
206 jq_StaticMethod sm = (jq_StaticMethod) it.next();
207 if (sm.getName() == rootm_name) {
208 m = sm;
209 break;
210 }
211 }
212 if (m != null) {
213 Object[] args = new Object[m.getParamTypes().length];
214 index = parseMethodArgs(args, m.getParamTypes(), commandBuffer, index);
215 joeq.Interpreter.QuadInterpreter s = null;
216 java.lang.reflect.Method im = interpreterClass.getMethod("interpretMethod", new Class[]{Class.forName("Class.jq_Method"), new Object[0].getClass()});
217 s = (joeq.Interpreter.QuadInterpreter) im.invoke(null, new Object[]{m, args});
218
219 System.out.flush();
220 System.out.println("Result of interpretation: " + s);
221 } else {
222 System.err.println("Class " + fullName.substring(0, b - 1) + " doesn't contain a void static no-argument method with name " + methodName);
223 }
224 } catch (NoClassDefFoundError x) {
225 System.err.println("Class " + fullName.substring(0, b - 1) + " (canonical name " + className + ") not found.");
226 return index;
227 } catch (NoSuchMethodException x) {
228 System.err.println("Interpreter method in " + interpreterClass + " not found! " + x);
229 return index;
230 } catch (ClassNotFoundException x) {
231 System.err.println("Class.jq_Method class not found! " + x);
232 return index;
233 } catch (IllegalAccessException x) {
234 System.err.println("Cannot access interpreter " + interpreterClass + ": " + x);
235 return index;
236 } catch (java.lang.reflect.InvocationTargetException x) {
237 System.err.println("Interpreter threw exception: " + x.getTargetException());
238 x.getTargetException().printStackTrace();
239 return index;
240 }
241
242 } else if (commandBuffer[index].equalsIgnoreCase("set")) {
243 String fullName = commandBuffer[++index];
244 int b = fullName.lastIndexOf('.') + 1;
245 String fieldName = fullName.substring(b);
246 String className = canonicalizeClassName(fullName.substring(0, b - 1));
247 try {
248 jq_Class c = (jq_Class) PrimordialClassLoader.loader.getOrCreateBSType(className);
249 c.cls_initialize();
250 jq_StaticField m = null;
251 Utf8 sf_name = Utf8.get(fieldName);
252 for (Iterator it = java.util.Arrays.asList(c.getDeclaredStaticFields()).iterator(); it.hasNext();) {
253 jq_StaticField sm = (jq_StaticField) it.next();
254 if (sm.getName() == sf_name) {
255 m = sm;
256 break;
257 }
258 }
259 if (m != null) {
260 java.lang.reflect.Field f = (java.lang.reflect.Field) Reflection.getJDKMember(m);
261 f.setAccessible(true);
262 Object[] o = new Object[1];
263 index = parseArg(o, 0, m.getType(), commandBuffer, index);
264 f.set(null, o[0]);
265 } else {
266 System.err.println("Class " + fullName.substring(0, b - 1) + " doesn't contain a static field with name " + fieldName);
267 }
268 } catch (NoClassDefFoundError x) {
269 System.err.println("Class " + fullName.substring(0, b - 1) + " (canonical name " + className + ") not found.");
270 return index;
271 } catch (IllegalAccessException x) {
272 System.err.println("Cannot access field: " + x);
273 return index;
274 }
275
276 } else if (commandBuffer[index].equalsIgnoreCase("addpass")) {
277 String passname = commandBuffer[++index];
278 ControlFlowGraphVisitor mv = null;
279 BasicBlockVisitor bbv = null;
280 QuadVisitor qv = null;
281 Object o;
282 try {
283 Class c = Class.forName(passname);
284 o = c.newInstance();
285 if (o instanceof ControlFlowGraphVisitor) {
286 mv = (ControlFlowGraphVisitor) o;
287 } else {
288 if (o instanceof BasicBlockVisitor) {
289 bbv = (BasicBlockVisitor) o;
290 } else {
291 if (o instanceof QuadVisitor) {
292 qv = (QuadVisitor) o;
293 } else {
294 System.err.println("Unknown pass type " + c);
295 return index;
296 }
297 bbv = new QuadVisitor.AllQuadVisitor(qv, trace_bb);
298 }
299 mv = new BasicBlockVisitor.AllBasicBlockVisitor(bbv, trace_method);
300 }
301 CodeCache.passes.add(mv);
302 } catch (java.lang.ClassNotFoundException x) {
303 System.err.println("Cannot find pass named " + passname + ".");
304 System.err.println("Check your classpath and make sure you compiled your pass.");
305 return index;
306 } catch (java.lang.InstantiationException x) {
307 System.err.println("Cannot instantiate pass " + passname + ": " + x);
308 return index;
309 } catch (java.lang.IllegalAccessException x) {
310 System.err.println("Cannot access pass " + passname + ": " + x);
311 System.err.println("Be sure that you made your class public?");
312 return index;
313 }
314 } else if (commandBuffer[index].equalsIgnoreCase("runpass")) {
315 String passname = commandBuffer[++index];
316 jq_TypeVisitor cv = null;
317 jq_MethodVisitor mv = null;
318 ControlFlowGraphVisitor cfgv = null;
319 BasicBlockVisitor bbv = null;
320 QuadVisitor qv = null;
321 Object o;
322 try {
323 passname = passname.replace('/', '.');
324 Class c = Class.forName(passname);
325 o = c.newInstance();
326 if (o instanceof jq_TypeVisitor) {
327 cv = (jq_TypeVisitor) o;
328 } else {
329 if (o instanceof jq_MethodVisitor) {
330 mv = (jq_MethodVisitor) o;
331 } else {
332 if (o instanceof ControlFlowGraphVisitor) {
333 cfgv = (ControlFlowGraphVisitor) o;
334 } else {
335 if (o instanceof BasicBlockVisitor) {
336 bbv = (BasicBlockVisitor) o;
337 } else {
338 if (o instanceof QuadVisitor) {
339 qv = (QuadVisitor) o;
340 } else {
341 System.err.println("Unknown pass type " + c);
342 return index;
343 }
344 bbv = new QuadVisitor.AllQuadVisitor(qv, trace_bb);
345 }
346 cfgv = new BasicBlockVisitor.AllBasicBlockVisitor(bbv, trace_cfg);
347 }
348 mv = new ControlFlowGraphVisitor.CodeCacheVisitor(cfgv, trace_method);
349 }
350 cv = new jq_MethodVisitor.DeclaredMethodVisitor(mv, methodNamesToProcess, trace_type);
351 }
352 } catch (java.lang.ClassNotFoundException x) {
353 System.err.println("Cannot find pass named " + passname + ".");
354 System.err.println("Check your classpath and make sure you compiled your pass.");
355 return index;
356 } catch (java.lang.InstantiationException x) {
357 System.err.println("Cannot instantiate pass " + passname + ": " + x);
358 return index;
359 } catch (java.lang.IllegalAccessException x) {
360 System.err.println("Cannot access pass " + passname + ": " + x);
361 System.err.println("Be sure that you made your class public?");
362 return index;
363 }
364
365 Collection s = new TreeSet(new Comparator() {
366 public int compare(Object o1, Object o2) {
367 return o1.toString().compareTo(o2.toString());
368 }
369 });
370 s.addAll(classesToProcess);
371 for (Iterator i = s.iterator(); i.hasNext();) {
372 jq_Type t = (jq_Type) i.next();
373 try {
374 t.accept(cv);
375 } catch (LinkageError le) {
376 if (!ignore_linkage_errors)
377 throw le;
378 System.err.println("Linkage error occurred while executing pass on " + t + " : " + le);
379 le.printStackTrace(System.err);
380 } catch (Exception x) {
381 System.err.println("Runtime exception occurred while executing pass on " + t + " : " + x);
382 x.printStackTrace(System.err);
383 }
384 }
385 System.err.println("Completed pass! " + o);
386 } else if (commandBuffer[index].equalsIgnoreCase("run")) {
387 String toRun = commandBuffer[++index];
388 Runnable runnable = null;
389 try {
390 runnable = (Runnable)Class.forName(toRun).newInstance();
391 } catch (InstantiationException e) {
392 e.printStackTrace();
393 System.err.println("Can't instantiate a " + toRun);
394 return index;
395 } catch (IllegalAccessException e) {
396
397 e.printStackTrace();
398 System.err.println("Can't access a field");
399 return index;
400 } catch (ClassNotFoundException e) {
401 e.printStackTrace();
402 System.err.println("Class can't be found");
403 return index;
404 }
405 Assert._assert(runnable != null);
406
407 runnable.run();
408 } else if (commandBuffer[index].equalsIgnoreCase("store")) {
409 System.out.println(si.getStore());
410 } else if (commandBuffer[index].equalsIgnoreCase("loaderpath")) {
411 try {
412 si.setClassPath(new URL[] { new File(commandBuffer[++index]).toURL() });
413 } catch (java.net.MalformedURLException e) {
414 e.printStackTrace(System.err);
415 }
416 } else if (commandBuffer[index].equalsIgnoreCase("new")) {
417 String name = commandBuffer[++index];
418 String type = commandBuffer[++index];
419 index = si.newObject(name, type, commandBuffer, index);
420 printObjectInStore(si, name);
421 } else if (commandBuffer[index].indexOf(".") != -1) {
422 String fullName = commandBuffer[index];
423 int b = fullName.lastIndexOf('.') + 1;
424 String objectName = fullName.substring(0, b-1);
425 String methodName = fullName.substring(b);
426 index = si.invokeMethod(objectName, methodName, commandBuffer, index);
427 printObjectInStore(si, "$last");
428 } else if (commandBuffer[index].equalsIgnoreCase("exit") || commandBuffer[index].equalsIgnoreCase("quit")) {
429 System.exit(0);
430 } else if (commandBuffer[index].equalsIgnoreCase("help")) {
431 printHelp();
432 } else {
433 int index2 = TraceFlags.setTraceFlag(commandBuffer, index);
434 if (index == index2)
435 System.err.println("Unknown command " + commandBuffer[index]);
436 else
437 index = index2 - 1;
438 }
439 } catch (ArrayIndexOutOfBoundsException x) {
440 System.err.println("Incomplete command");
441 x.printStackTrace(System.err);
442 }
443 return index;
444 }
445
446 private static void printObjectInStore(SimpleInterpreter si, Object name) {
447 Object newobj = si.getStore().get(name);
448 if (newobj != null) {
449 String s = newobj.toString();
450 if (s.length() > 1024) s = s.substring(0, 1024);
451 System.out.println(s);
452 } else {
453 System.err.println("object " + name + " not found - did the operation fail?");
454 }
455 }
456
457 public static void printHelp() {
458 String nl = Strings.lineSep;
459 String helpMessage =
460 nl+
461 "Usage: command1 [args...] [command2 [args...]]..."+nl+nl +
462 "1. addtoclasspath additional_classpath"+nl +
463 "----> add to the class path"+nl+nl +
464 "2. addpass pass_class_name"+nl +
465 "----> run compiler pass on all code generated from this point"+nl+nl +
466 "3. class class_name"+nl +
467 "----> add to the list of classes to process"+nl+nl +
468 "4. exit | quit"+nl +
469 "----> exit interactive mode"+nl+nl +
470 "5. help"+nl +
471 "----> print this message"+nl+nl +
472 "6. interpret class_name.static_method_name param_list"+nl +
473 "----> interpret a static method with the given arguments"+nl+nl +
474 "7. method method_name"+nl +
475 "----> only process methods of the given name"+nl+nl +
476 "8. package package_name"+nl +
477 "----> add all the classes in the specified package to the list of classes to process"+nl+nl +
478 "9. packages package_name"+nl +
479 "----> add all the classes in the specified package and all sub-packages recursively to the list of classes to process"+nl+nl +
480 "10. runpass pass_class_name"+nl +
481 "----> run compiler pass on classes in list"+nl+nl +
482 "11. set class_name.static_field_name value"+nl +
483 "----> set specified static field to specified value"+nl+nl +
484 "12. trace bb|cfg|method|type"+nl +
485 "----> enable tracing options when processing classes"+nl+nl +
486 "Other trace options are available, see Main/TraceFlags.java."+
487 nl;
488 System.out.println(helpMessage);
489 }
490
491 public static String canonicalizeClassName(String s) {
492 if (s.endsWith(".class")) s = s.substring(0, s.length() - 6);
493 s = s.replace('.', '/');
494 String desc = "L" + s + ";";
495 return desc;
496 }
497
498 public static int parseArg(Object[] args, int m, jq_Type type, String[] s_args, int j) {
499 if (type == PrimordialClassLoader.getJavaLangString())
500 args[m] = s_args[++j];
501 else if (type == jq_Primitive.BOOLEAN)
502 args[m] = Boolean.valueOf(s_args[++j]);
503 else if (type == jq_Primitive.BYTE)
504 args[m] = Byte.valueOf(s_args[++j]);
505 else if (type == jq_Primitive.SHORT)
506 args[m] = Short.valueOf(s_args[++j]);
507 else if (type == jq_Primitive.CHAR)
508 args[m] = new Character(s_args[++j].charAt(0));
509 else if (type == jq_Primitive.INT)
510 args[m] = Integer.valueOf(s_args[++j]);
511 else if (type == jq_Primitive.LONG) {
512 args[m] = Long.valueOf(s_args[++j]);
513 } else if (type == jq_Primitive.FLOAT)
514 args[m] = Float.valueOf(s_args[++j]);
515 else if (type == jq_Primitive.DOUBLE) {
516 args[m] = Double.valueOf(s_args[++j]);
517 } else if (type.isArrayType()) {
518 if (!s_args[++j].equals("{"))
519 Assert.UNREACHABLE("array parameter doesn't start with {");
520 int count = 0;
521 while (!s_args[++j].equals("}")) ++count;
522 jq_Type elementType = ((jq_Array) type).getElementType();
523 if (elementType == PrimordialClassLoader.getJavaLangString()) {
524 String[] array = new String[count];
525 for (int k = 0; k < count; ++k)
526 array[k] = s_args[j - count + k];
527 args[m] = array;
528 } else if (elementType == jq_Primitive.BOOLEAN) {
529 boolean[] array = new boolean[count];
530 for (int k = 0; k < count; ++k)
531 array[k] = Boolean.valueOf(s_args[j - count + k]).booleanValue();
532 args[m] = array;
533 } else if (elementType == jq_Primitive.BYTE) {
534 byte[] array = new byte[count];
535 for (int k = 0; k < count; ++k)
536 array[k] = Byte.parseByte(s_args[j - count + k]);
537 args[m] = array;
538 } else if (elementType == jq_Primitive.SHORT) {
539 short[] array = new short[count];
540 for (int k = 0; k < count; ++k)
541 array[k] = Short.parseShort(s_args[j - count + k]);
542 args[m] = array;
543 } else if (elementType == jq_Primitive.CHAR) {
544 char[] array = new char[count];
545 for (int k = 0; k < count; ++k)
546 array[k] = s_args[j - count + k].charAt(0);
547 args[m] = array;
548 } else if (elementType == jq_Primitive.INT) {
549 int[] array = new int[count];
550 for (int k = 0; k < count; ++k)
551 array[k] = Integer.parseInt(s_args[j - count + k]);
552 args[m] = array;
553 } else if (elementType == jq_Primitive.LONG) {
554 long[] array = new long[count];
555 for (int k = 0; k < count; ++k)
556 array[k] = Long.parseLong(s_args[j - count + k]);
557 args[m] = array;
558 } else if (elementType == jq_Primitive.FLOAT) {
559 float[] array = new float[count];
560 for (int k = 0; k < count; ++k)
561 array[k] = Float.parseFloat(s_args[j - count + k]);
562 args[m] = array;
563 } else if (elementType == jq_Primitive.DOUBLE) {
564 double[] array = new double[count];
565 for (int k = 0; k < count; ++k)
566 array[k] = Double.parseDouble(s_args[j - count + k]);
567 args[m] = array;
568 } else
569 Assert.UNREACHABLE("Parsing of type " + type + " is not implemented");
570 } else
571 Assert.UNREACHABLE("Parsing of type " + type + " is not implemented");
572 return j;
573 }
574
575 public static int parseMethodArgs(Object[] args, jq_Type[] paramTypes, String[] s_args, int j) {
576 try {
577 for (int i = 0, m = 0; i < paramTypes.length; ++i, ++m) {
578 j = parseArg(args, m, paramTypes[i], s_args, j);
579 }
580 } catch (ArrayIndexOutOfBoundsException x) {
581 x.printStackTrace();
582 Assert.UNREACHABLE("not enough method arguments");
583 }
584 return j;
585 }
586 }