/******************************************************************************* * * Copyright (c) 2010 Overture. * * Author: Kenneth Lausdahl * * This file is part of VDMJ. * * VDMJ is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * VDMJ 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 for more details. * * You should have received a copy of the GNU General Public License * along with VDMJ. If not, see <http://www.gnu.org/licenses/>. * ******************************************************************************/ package org.overture.interpreter.debug; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintWriter; import java.io.Serializable; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Vector; import org.overture.ast.analysis.AnalysisException; import org.overture.ast.definitions.AMutexSyncDefinition; import org.overture.ast.definitions.APerSyncDefinition; import org.overture.ast.definitions.PDefinition; import org.overture.ast.definitions.SClassDefinition; import org.overture.ast.expressions.AHistoryExp; import org.overture.ast.expressions.PExp; import org.overture.ast.factory.AstFactory; import org.overture.ast.intf.lex.ILexNameToken; import org.overture.ast.lex.Dialect; import org.overture.ast.lex.LexNameList; import org.overture.ast.lex.LexNameToken; import org.overture.ast.lex.LexToken; import org.overture.ast.lex.VDMToken; import org.overture.ast.modules.AModuleModules; import org.overture.ast.util.definitions.ClassList; import org.overture.config.Release; import org.overture.config.Settings; import org.overture.interpreter.VDMJ; import org.overture.interpreter.VDMPP; import org.overture.interpreter.VDMRT; import org.overture.interpreter.VDMSL; import org.overture.interpreter.debug.DBGPExecProcesser.DBGPExecResult; import org.overture.interpreter.messages.Console; import org.overture.interpreter.messages.rtlog.RTLogger; import org.overture.interpreter.messages.rtlog.RTTextLogger; import org.overture.interpreter.messages.rtlog.nextgen.NextGenRTLogger; import org.overture.interpreter.runtime.ClassContext; import org.overture.interpreter.runtime.ClassInterpreter; import org.overture.interpreter.runtime.Context; import org.overture.interpreter.runtime.ContextException; import org.overture.interpreter.runtime.Interpreter; import org.overture.interpreter.runtime.ModuleInterpreter; import org.overture.interpreter.runtime.ObjectContext; import org.overture.interpreter.runtime.RuntimeValidator; import org.overture.interpreter.runtime.SourceFile; import org.overture.interpreter.runtime.StateContext; import org.overture.interpreter.runtime.VdmRuntime; import org.overture.interpreter.scheduler.BasicSchedulableThread; import org.overture.interpreter.scheduler.ISchedulableThread; import org.overture.interpreter.traces.TraceReductionType; import org.overture.interpreter.util.ExitStatus; import org.overture.interpreter.values.BooleanValue; import org.overture.interpreter.values.CPUValue; import org.overture.interpreter.values.CharacterValue; import org.overture.interpreter.values.FieldValue; import org.overture.interpreter.values.FunctionValue; import org.overture.interpreter.values.MapValue; import org.overture.interpreter.values.NameValuePair; import org.overture.interpreter.values.NameValuePairMap; import org.overture.interpreter.values.NilValue; import org.overture.interpreter.values.NumericValue; import org.overture.interpreter.values.ObjectValue; import org.overture.interpreter.values.RecordValue; import org.overture.interpreter.values.ReferenceValue; import org.overture.interpreter.values.SeqValue; import org.overture.interpreter.values.SetValue; import org.overture.interpreter.values.TokenValue; import org.overture.interpreter.values.TransactionValue; import org.overture.interpreter.values.TupleValue; import org.overture.interpreter.values.UpdatableValue; import org.overture.interpreter.values.Value; import org.overture.parser.config.Properties; import org.overture.parser.lex.LexException; import org.overture.parser.lex.LexTokenReader; import org.overture.util.Base64; /** * Extended DBGPReader adding support for: * <ul> * <li>In-depth variable inspection. * <li>Overture command for writing covtbl files after execution. * <li>Overture response. Used to check status after issuing an xcmd Overture * </ul> * * @author kela */ public class DBGPReaderV2 extends DBGPReader implements Serializable { private static final long serialVersionUID = 1L; private static final int SHORT_STRING_MAX = 200; /** * Map containing Values accessible from the client by a key. Should be emptied at resume */ private Map<Integer, Value> debugValueMap = new Hashtable<Integer, Value>(); /** * Debug key counter for the client */ private Integer debugValueKeyCounter = 0; /** * Indicating if the debugger is running in a trace mode */ private static Boolean traceExpression = false; @SuppressWarnings("unchecked") public static void main(String[] args) { Settings.usingDBGP = true; Settings.baseDir = new File(".").getParentFile(); String host = null; int port = -1; String ideKey = null; Settings.dialect = null; String expression = null; List<File> files = new Vector<File>(); List<String> largs = Arrays.asList(args); VDMJ controller = null; boolean warnings = true; boolean quiet = false; String logfile = null; String logTimeInvfile = null; boolean expBase64 = false; File coverage = null; String defaultName = null; String remoteName = null; Class<RemoteControl> remoteClass = null; Properties.init(); // Read properties file, if any Properties.parser_tabstop = 1;// required to match locations with the editor representation for (Iterator<String> i = largs.iterator(); i.hasNext();) { String arg = i.next(); if (arg.equals("-vdmsl")) { controller = new VDMSL(); } else if (arg.equals("-vdmpp")) { controller = new VDMPP(); } else if (arg.equals("-vdmrt")) { controller = new VDMRT(); } else if (arg.equals("-h")) { if (i.hasNext()) { host = i.next(); } else { usage("-h option requires a hostname"); } } else if (arg.equals("-p")) { try { port = Integer.parseInt(i.next()); } catch (Exception e) { usage("-p option requires a port"); } } else if (arg.equals("-k")) { if (i.hasNext()) { ideKey = i.next(); } else { usage("-k option requires a key"); } } else if (arg.equals("-e")) { if (i.hasNext()) { expression = i.next(); } else { usage("-e option requires an expression"); } } else if (arg.equals("-e64")) { if (i.hasNext()) { expression = i.next(); expBase64 = true; } else { usage("-e64 option requires an expression"); } } else if (arg.equals("-c")) { if (i.hasNext()) { if (controller == null) { usage("-c must come after <-vdmpp|-vdmsl|-vdmrt>"); } controller.setCharset(validateCharset(i.next())); } else { usage("-c option requires a charset name"); } } else if (arg.equals("-r")) { if (i.hasNext()) { Settings.release = Release.lookup(i.next()); if (Settings.release == null) { usage("-r option must be " + Release.list()); } } else { usage("-r option requires a VDM release"); } } else if (arg.equals("-pre")) { Settings.prechecks = false; } else if (arg.equals("-post")) { Settings.postchecks = false; } else if (arg.equals("-inv")) { Settings.invchecks = false; } else if (arg.equals("-dtc")) { // NB. Turn off both when no DTC Settings.invchecks = false; Settings.dynamictypechecks = false; } else if (arg.equals("-measures")) { Settings.measureChecks = false; } else if (arg.equals("-log")) { if (i.hasNext()) { try { logfile = new URI(i.next()).getPath(); } catch (URISyntaxException e) { usage(e.getMessage() + ": " + arg); } catch (IllegalArgumentException e) { usage(e.getMessage() + ": " + arg); } } else { usage("-log option requires a filename"); } } else if (arg.equals("-timeinv")) { if (i.hasNext()) { try { logTimeInvfile = new URI(i.next()).getPath(); } catch (URISyntaxException e) { usage(e.getMessage() + ": " + arg); } catch (IllegalArgumentException e) { usage(e.getMessage() + ": " + arg); } } else { usage("-timeinv option requires a filename"); } } else if (arg.equals("-w")) { warnings = false; } else if (arg.equals("-q")) { quiet = true; } else if (arg.equals("-coverage")) { if (i.hasNext()) { try { coverage = new File(new URI(i.next())); if (!coverage.isDirectory()) { usage("Coverage location is not a directory"); } } catch (URISyntaxException e) { usage(e.getMessage() + ": " + arg); } catch (IllegalArgumentException e) { usage(e.getMessage() + ": " + arg); } } else { usage("-coverage option requires a directory name"); } } else if (arg.equals("-default64")) { if (i.hasNext()) { defaultName = i.next(); } else { usage("-default64 option requires a name"); } } else if (arg.equals("-remote")) { if (i.hasNext()) { remoteName = i.next(); } else { usage("-remote option requires a Java classname"); } } else if (arg.equals("-t")) { traceExpression = true; } else if (arg.equals("-consoleName")) { if (i.hasNext()) { LexTokenReader.consoleFileName = i.next(); } else { usage("-consoleName option requires a console name"); } } else if (arg.equals("-baseDir")) { if (i.hasNext()) { try { Settings.baseDir = new File(new URI(i.next())); } catch (URISyntaxException e) { usage(e.getMessage() + ": " + arg); } catch (IllegalArgumentException e) { usage(e.getMessage() + ": " + arg); } } else { usage("-baseDir option requires a folder name"); } } else if (arg.startsWith("-")) { usage("Unknown option " + arg); } else { try { File dir = new File(new URI(arg)); if (dir.isDirectory()) { for (File file : dir.listFiles(Settings.dialect.getFilter())) { if (file.isFile()) { files.add(file); } } } else { files.add(dir); } } catch (URISyntaxException e) { usage(e.getMessage() + ": " + arg); } catch (IllegalArgumentException e) { usage(e.getMessage() + ": " + arg); } } } if (host == null) { usage("Missing mandatory host URL"); } if (port == -1) { usage("Missing mandatory port number"); } if (ideKey == null) { usage("Missing mandatory IDE key"); } if (expression == null) { usage("Missing mandatory expression"); } if (Settings.dialect == null) { usage("Missing mandatory dialect"); } if (files.isEmpty()) { usage("Missing mandatory specification files"); } if (host == null || port == -1 || controller == null || ideKey == null || expression == null || Settings.dialect == null || files.isEmpty()) { usage("Missing mandatory arguments"); } if (Settings.dialect != Dialect.VDM_RT && logfile != null) { usage("-log can only be used with -vdmrt"); } if (Settings.dialect != Dialect.VDM_RT && logTimeInvfile != null) { usage("-timeinv can only be used with -vdmrt"); } if (expBase64) { try { byte[] bytes = Base64.decode(expression); expression = new String(bytes, VDMJ.filecharset); } catch (Exception e) { usage("Malformed -e64 base64 expression"); } } if (defaultName != null) { try { byte[] bytes = Base64.decode(defaultName); defaultName = new String(bytes, VDMJ.filecharset); } catch (Exception e) { usage("Malformed -default64 base64 name"); } } if (remoteName != null) { try { Class<?> cls = ClassLoader.getSystemClassLoader().loadClass(remoteName); remoteClass = (Class<RemoteControl>) cls; } catch (ClassNotFoundException e) { usage("Cannot locate " + remoteName + " on the CLASSPATH"); } } controller.setWarnings(warnings); controller.setQuiet(quiet); if (controller.parse(files) == ExitStatus.EXIT_OK) { if (controller.typeCheck() == ExitStatus.EXIT_OK) { try { if (logfile != null) { RTLogger.setLogfile(RTTextLogger.class, new File(logfile)); RTLogger.setLogfile(NextGenRTLogger.class, new File(logfile)); } if (logTimeInvfile != null) { Settings.timingInvChecks = true; PrintWriter p = new PrintWriter(new FileOutputStream(logTimeInvfile, false)); RuntimeValidator.setLogFile(p); } Interpreter i = controller.getInterpreter(); if (defaultName != null) { i.setDefaultName(defaultName); } RemoteControl remote = remoteClass == null ? null : remoteClass.newInstance(); new DBGPReaderV2(host, port, ideKey, i, expression, null).startup(remote); if (coverage != null) { writeCoverage(i, coverage); } RTLogger.dump(true); System.exit(0); } catch (ContextException e) { System.err.println("Initialization: " + e); e.ctxt.printStackTrace(Console.out, true); RTLogger.dump(true); System.exit(3); } catch (Exception e) { System.err.println("Initialization: " + e); e.printStackTrace(); RTLogger.dump(true); System.exit(3); } } else { System.exit(2); } } else { System.exit(1); } } public DBGPReaderV2(String host, int port, String ideKey, Interpreter interpreter, String expression, CPUValue cpu) { super(host, port, ideKey, interpreter, expression, cpu); } /** * Overrides to use DBGPReaderV2 debug reader */ @Override public DBGPReaderV2 newThread(CPUValue _cpu) { DBGPReaderV2 r = new DBGPReaderV2(host, port, ideKey, interpreter, null, _cpu); r.command = DBGPCommandType.UNKNOWN; r.transaction = "?"; return r; } @Override protected boolean process(String line) { boolean carryOn = true; try { command = DBGPCommandType.UNKNOWN; transaction = "?"; String[] parts = line.split("\\s+"); DBGPCommand c = parse(parts); switch (c.type) { case STATUS: processStatus(c); break; case FEATURE_GET: processFeatureGet(c); break; case FEATURE_SET: processFeatureSet(c); break; case RUN: carryOn = processRun(c); break; case EVAL: carryOn = processEval(c); break; case EXPR: carryOn = processExpr(c); break; case EXEC: carryOn = processExec(c); break; case STEP_INTO: processStepInto(c); carryOn = false; break; case STEP_OVER: processStepOver(c); carryOn = false; break; case STEP_OUT: processStepOut(c); carryOn = false; break; case STOP: processStop(c); carryOn = false; break; case BREAKPOINT_GET: breakpointGet(c); break; case BREAKPOINT_SET: breakpointSet(c); break; case BREAKPOINT_UPDATE: breakpointUpdate(c); break; case BREAKPOINT_REMOVE: breakpointRemove(c); break; case BREAKPOINT_LIST: breakpointList(c); break; case STACK_DEPTH: stackDepth(c); break; case STACK_GET: stackGet(c); break; case CONTEXT_NAMES: contextNames(c); break; case CONTEXT_GET: contextGet(c); break; case PROPERTY_GET: propertyGet(c); break; case SOURCE: processSource(c); break; case STDOUT: processStdout(c); break; case STDERR: processStderr(c); break; case DETACH: carryOn = false; break; case XCMD_OVERTURE_CMD: processOvertureCmd(c); break; case PROPERTY_SET: propertySet(c); break; default: errorResponse(DBGPErrorCode.NOT_AVAILABLE, c.type.value); } } catch (DBGPException e) { errorResponse(e.code, e.reason); } catch (StackOverflowError e) { invocationError(e); } catch (Throwable e) { errorResponse(DBGPErrorCode.INTERNAL_ERROR, e.getMessage()); } return carryOn; } private void propertySet(DBGPCommand c) throws DBGPException, IOException { checkArgs(c, 4, false); DBGPOption option = c.getOption(DBGPOptionType.K); if (option == null) { throw new DBGPException(DBGPErrorCode.INVALID_OPTIONS, c.toString()); } boolean success = false; Integer key = Integer.parseInt(option.value); Value vOriginal = null; UpdatableValue uv = null; try { if (!debugValueMap.containsKey(key)) { throw new DBGPException(DBGPErrorCode.CANT_GET_PROPERTY, "Key = " + key); } Value v = debugValueMap.get(key); vOriginal = v.deepCopy();// inexpensive only support of simple types if (v instanceof UpdatableValue) { uv = (UpdatableValue) v; // TODO BUG: Here is a problem if the evaluate is suspended in a // property_set by the scheduler Value newval = interpreter.evaluate(c.data, interpreter.initialContext); if (newval != null && canAssignValue(newval.kind(), uv.kind())) { uv.set(breakpoint.location, newval, breakContext); success = true; } } } catch (ContextException e) { success = false; // aboard value update and put back the original value try { if (uv != null) { uv.set(breakpoint.location, vOriginal, breakContext); } } catch (Exception ex) { // this is fatal we cannot continue interpretation throw new DBGPException(DBGPErrorCode.INTERNAL_ERROR, ex.toString()); } } catch (Exception e) { throw new DBGPException(DBGPErrorCode.INTERNAL_ERROR, e.toString()); } StringBuilder hdr = new StringBuilder(); hdr.append("success=\"" + (success ? "1" : "0") + "\""); response(hdr, null); } private boolean canAssignValue(String newKind, String source) { if (newKind.equals(source)) { return true; } final String TYPE_REAL = "real"; final String TYPE_NAT = "nat"; final String TYPE_INT = "int"; if (newKind.contains(TYPE_NAT) && source.equals(TYPE_NAT)) { return true; } if (newKind.contains(TYPE_NAT) && source.equals(TYPE_REAL)) { return true; } if (newKind.contains(TYPE_NAT) && source.equals(TYPE_INT)) { return true; } if (newKind.contains(TYPE_INT) && source.equals(TYPE_REAL)) { return true; } return false; } /** * Send a xcmd Overture Response * * @param overtureCmd * The overture command which this is a response to * @param hdr * The header * @param body * The body * @throws IOException */ private void xcmdOvertureResponse(DBGPXCmdOvertureCommandType overtureCmd, StringBuilder hdr, StringBuilder body) throws IOException { StringBuilder sb = new StringBuilder(); sb.append("<xcmd_overture_response command=\""); sb.append(command); sb.append("\""); sb.append(" overtureCmd=\""); sb.append(overtureCmd); sb.append("\""); if (hdr != null) { sb.append(" "); sb.append(hdr); } sb.append(" transaction_id=\""); sb.append(transaction); sb.append("\""); if (body != null) { sb.append(">"); sb.append(body); sb.append("</xcmd_overture_response>\n"); } else { sb.append("/>\n"); } write(sb); } @Override protected void statusResponse(DBGPStatus s, DBGPReason reason) throws IOException { if (s == DBGPStatus.STOPPED) { stopped = true; } StringBuilder sb = new StringBuilder(); status = s; statusReason = reason; sb.append("status=\""); sb.append(status); sb.append("\""); sb.append(" reason=\""); sb.append(statusReason); sb.append("\""); StringBuilder body = new StringBuilder(); body.append("<internal "); ISchedulableThread th = BasicSchedulableThread.getThread(Thread.currentThread()); if (th != null) { body.append("threadId=\""); body.append(th.getId()); body.append("\" "); body.append("threadName=\""); body.append(th.getName()); body.append("\" "); body.append("threadState=\""); body.append(th.getRunState().toString()); body.append("\" "); } body.append("/>"); response(sb, body); } /** * Overrides super class by filtering all entries against isDebugVisible */ @Override protected StringBuilder propertyResponse(NameValuePairMap vars, DBGPContextType context) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); for (Entry<ILexNameToken, Value> e : vars.entrySet()) { if (!e.getKey().getName().equals("self")) { // This test makes the self not appear if (isDebugVisible(e.getValue())) { sb.append(propertyResponse(e.getKey(), e.getValue(), context)); } } } return sb; } @Override protected StringBuilder propertyResponse(ILexNameToken name, Value value, DBGPContextType context) throws UnsupportedEncodingException { String nameString = context == DBGPContextType.GLOBAL ? name.getModule() + "`" + name.getName() : name.getOld() ? name.getName() + "~" : name.getName(); return propertyResponse(nameString, name.getExplicit(true).toString(), name.getModule(), value); } private StringBuilder propertyResponse(String name, String fullname, String clazz, Value value) throws UnsupportedEncodingException { return propertyResponse(name, fullname, clazz, value, 3, 0); } private StringBuilder propertyResponse(String name, String fullname, String clazz, Value value, Integer depth, Integer currentDepth) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); currentDepth++; Integer numChildren = getChildCount(value); Integer page = 0; Integer pageSize = Integer.parseInt(features.getProperty(DBGPFeatures.MAX_CHILDREN)); Integer key = null; String data = null; StringBuilder nestedChildren = null; // store property for retrieval of additional pages or value editing if (numChildren > pageSize || depth == currentDepth || value instanceof UpdatableValue || value instanceof ObjectValue) { debugValueKeyCounter++; debugValueMap.put(debugValueKeyCounter, value); key = debugValueKeyCounter; } if (numChildren > pageSize || depth == currentDepth) { name += " (ref=" + debugValueKeyCounter + ")"; } if (numChildren > 0) { data = value.toShortString(SHORT_STRING_MAX); } else { data = value.toString(); } Value valDeref = value.deref(); if (valDeref instanceof FunctionValue) { data = formatFunctionValue((FunctionValue) valDeref); } if (currentDepth < depth && numChildren > 0) { // max depth not reached. Fetch children of page size sb.append(propertyResponseChild(value, depth, currentDepth, pageSize, 0)); nestedChildren = propertyResponseChild(value, depth, currentDepth, pageSize, 0); } boolean constant = numChildren > 0 || !(value instanceof UpdatableValue); return makeProperty(name, fullname, value.kind(), clazz, page, pageSize, constant, data.length(), key, numChildren, data, nestedChildren); } private String formatFunctionValue(FunctionValue value) { StringBuilder sb = new StringBuilder(); sb.append(value.name + ": " + value.toString() + " & " + value.body); return sb.toString(); } /** * @param size * Unused. */ private StringBuilder makeProperty(String name, String fullName, String type, String clazz, Integer page, Integer pageSize, boolean constant, Integer size, Integer key, Integer numChildren, String data, StringBuilder nestedProperties) throws UnsupportedEncodingException { StringBuilder sb = new StringBuilder(); Integer children = 0; if (numChildren > 0) { children = 1; } sb.append("<property"); sb.append(" name=\"" + quote(name) + "\""); sb.append(" fullname=\"" + quote(fullName) + "\""); sb.append(" type=\"" + quote(type) + "\""); sb.append(" classname=\"" + clazz + "\""); if (numChildren > 0) { sb.append(" page=\"" + page + "\""); sb.append(" pagesize=\"" + pageSize + "\""); } sb.append(" constant=\"" + (constant ? "1" : "0") + "\""); sb.append(" children=\"" + children + "\""); StringBuffer encodedData = Base64.encode(data.getBytes("UTF-8")); sb.append(" size=\"" + encodedData.length() + "\""); if (key != null) { sb.append(" key=\"" + key + "\""); } sb.append(" encoding=\"base64\""); if (numChildren > 0) { sb.append(" numchildren=\"" + numChildren + "\""); } sb.append("><![CDATA["); sb.append(encodedData); sb.append("]]>"); if (nestedProperties != null && nestedProperties.length() > 0) { sb.append(nestedProperties.toString()); } sb.append("</property>"); return sb; } /** * Calculates if a value has children and returns the child count * * @param value * The value to determine child count for * @return number of children */ private Integer getChildCount(Value value) { // all types listed here are directly toString() in the debugger if (value instanceof NumericValue || value instanceof CharacterValue || value instanceof NilValue || value instanceof TokenValue) { return 0; } else if (value instanceof SetValue) { return ((SetValue) value).values.size(); } else if (value instanceof SeqValue) { boolean isString = true; for (Value v : ((SeqValue) value).values) { if (!(deref(v) instanceof CharacterValue)) { isString = false; } } if (isString) { return 0; // tread as simple value } else { return ((SeqValue) value).values.size(); } } else if (value instanceof MapValue) { return ((MapValue) value).values.size(); } else if (value instanceof ObjectValue) { int count = 0; for (NameValuePair v : ((ObjectValue) value).members.asList()) { if (isDebugVisible(v.value)) { count++; } } return count; } else if (value instanceof UpdatableValue || value instanceof TransactionValue || value instanceof ReferenceValue) { return getChildCount(deref(value)); } else if (value instanceof RecordValue) { RecordValue rVal = (RecordValue) value; return rVal.fieldmap.size(); } else if (value instanceof TupleValue) { TupleValue tVal = (TupleValue) value; return tVal.values.size(); } return 0; } /** * Creates a string with property responses for all requested children of the parsed value, intended to be used as * the body of the value parsed * * @param value * The value which children should be fetched * @param depth * The max depth * @param currentDepth * The current depth (The method recurses over children if needed) * @param pageSize * The page size used when returning children * @param page * The current page * @return A string with property responses for all requested children * @throws UnsupportedEncodingException */ private StringBuilder propertyResponseChild(Value value, Integer depth, Integer currentDepth, Integer pageSize, Integer page) throws UnsupportedEncodingException { StringBuilder s = new StringBuilder(); if (value instanceof SeqValue) { SeqValue sVal = (SeqValue) value; for (Integer i = page * pageSize; i < sVal.values.size() && i < page + 1 * pageSize; i++) { Value element = sVal.values.get(i); Integer vdmIndex = i + 1; s.append(propertyResponse("Element[" + makeDisplayId(sVal.values.size(), vdmIndex) + "]", vdmIndex.toString(), "-", element, depth, currentDepth)); } } else if (value instanceof SetValue) { SetValue sVal = (SetValue) value; for (Integer i = page * pageSize; i < sVal.values.size() && i < (page + 1) * pageSize + 1; i++) { Value element = sVal.values.get(i); Integer vdmIndex = i + 1; s.append(propertyResponse("Element " + makeDisplayId(sVal.values.size(), vdmIndex), vdmIndex.toString(), "-", element, depth, currentDepth)); } } else if (value instanceof ObjectValue) { ObjectValue oVal = (ObjectValue) value; currentDepth++; for (ILexNameToken key : oVal.members.keySet()) { Value val = oVal.members.get(key); if (isDebugVisible(val)) { s.append(propertyResponse(key.getName(), key.getExplicit(true).toString(), key.getModule(), val, depth, currentDepth)); } } } else if (value instanceof UpdatableValue) { return propertyResponseChild(((UpdatableValue) value).deref(), depth, currentDepth, pageSize, page); } else if (value instanceof TransactionValue) { return propertyResponseChild(((TransactionValue) value).deref(), depth, currentDepth, pageSize, page); } else if (value instanceof ReferenceValue) { return propertyResponseChild(((ReferenceValue) value).deref(), depth, currentDepth, pageSize, page); } else if (value instanceof MapValue) { MapValue mVal = (MapValue) value; Value[] keys = mVal.values.keySet().toArray(new Value[mVal.values.keySet().size()]); for (Integer i = page * pageSize; i < keys.length && i < (page + 1) * pageSize + 1; i++) { Value dom = keys[i]; Value rng = mVal.values.get(dom); Integer vdmIndex = i + 1; StringBuilder entries = new StringBuilder(); entries.append(propertyResponse("dom", vdmIndex.toString(), "-", dom, depth, currentDepth)); entries.append(propertyResponse("rng", vdmIndex.toString(), "-", rng, depth, currentDepth)); s.append(makeProperty("Maplet " + makeDisplayId(mVal.values.keySet().size(), vdmIndex), vdmIndex.toString(), value.kind(), "", page, pageSize, true, 2, null, 2, "{" + dom + " |-> " + rng + "}", entries)); } } else if (value instanceof RecordValue) { RecordValue rVal = (RecordValue) value; for (Integer i = page * pageSize; i < rVal.fieldmap.size() && i < (page + 1) * pageSize + 1; i++) { FieldValue field = rVal.fieldmap.get(i); Integer vdmIndex = i + 1; s.append(propertyResponse(field.name, vdmIndex.toString(), "-", field.value, depth, currentDepth)); } } else if (value instanceof TupleValue) { TupleValue tVal = (TupleValue) value; for (Integer i = page * pageSize; i < tVal.values.size() && i < (page + 1) * pageSize + 1; i++) { Value v = tVal.values.get(i); Integer vdmIndex = i + 1; s.append(propertyResponse("#" + makeDisplayId(tVal.values.size(), vdmIndex), vdmIndex.toString(), "-", v, depth, currentDepth)); } } return s; } private String makeDisplayId(Integer size, Integer vdmIndex) { StringBuffer id = new StringBuffer(vdmIndex.toString()); while (size.toString().length() > id.length()) { id.insert(0, "0"); } return id.toString(); } /** * Deref Value of Reference and Updatable Value types * * @param value * The value to deref * @return The internal value of the parameter */ private Value deref(Value value) { if (value instanceof ReferenceValue || value instanceof UpdatableValue || value instanceof TransactionValue) { return value.deref(); } else { return value; } } /** * Determines if a value should be shown in the debug client * * @param v * The value to check * @return True if the value is allowed to be displayed in the client */ private boolean isDebugVisible(Value v) { return v instanceof ReferenceValue || v instanceof NumericValue || v instanceof CharacterValue || v instanceof BooleanValue || v instanceof SetValue || v instanceof SeqValue || v instanceof MapValue || v instanceof TokenValue || v instanceof RecordValue || v instanceof ObjectValue || v instanceof NilValue || v instanceof FunctionValue; } /** * Overridden to enable trace handling */ @Override protected boolean processRun(DBGPCommand c) throws DBGPException { checkArgs(c, 1, false); if (status == DBGPStatus.BREAK || status == DBGPStatus.STOPPING) { if (breakContext != null) { breakContext.threadState.setBreaks(null, null, null); status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; return false; // run means continue } else { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } } if (status == DBGPStatus.STARTING && expression == null) { status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; return false; // a run for a new thread, means continue } if (status != DBGPStatus.STARTING) { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } if (c.data != null) // data is in "expression" { throw new DBGPException(DBGPErrorCode.INVALID_OPTIONS, c.toString()); } if (remoteControl != null) { try { status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; final RemoteInterpreter remoteInterpreter = new RemoteInterpreter(interpreter, this); Thread remoteThread = new Thread(new Runnable() { public void run() { try { remoteControl.run(remoteInterpreter); } catch (Exception e) { status = DBGPStatus.STOPPED; statusReason = DBGPReason.ERROR; errorResponse(DBGPErrorCode.INTERNAL_ERROR, e.getMessage()); } } }); remoteThread.setName("RemoteControl runner"); remoteThread.setDaemon(true); remoteThread.start(); remoteInterpreter.processRemoteCalls(); stdout("Remote control completed"); statusResponse(DBGPStatus.STOPPED, DBGPReason.OK); run(); } catch (Exception e) { status = DBGPStatus.STOPPED; statusReason = DBGPReason.ERROR; errorResponse(DBGPErrorCode.INTERNAL_ERROR, e.getMessage()); } return false; // Do not continue after remote session } else { try { status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; if (!traceExpression) { if (expression.equals("###CONSOLE###")) { run(); } else { theAnswer = interpreter.execute(expression, this); } stdout("\n" + expression + " = " + theAnswer.toString() + "\n"); } else { String[] parts = expression.split("\\s+"); int testNo = 0; float reduction = 1.0F; TraceReductionType reductionType = TraceReductionType.NONE; long seed = 999; // Test`T1 4 {subset,reduction,seed} if (parts.length >= 2 && !parts[1].startsWith("{")) { try { testNo = Integer.parseInt(parts[1]); } catch (NumberFormatException e) { errorResponse(DBGPErrorCode.INTERNAL_ERROR, parts[0] + " <name> [test number]"); return true; } } if (parts.length >= 2 && parts[parts.length - 1].length() > 7 && parts[parts.length - 1].startsWith("{")) { try { String settings = parts[parts.length - 1]; String[] tmp = settings.substring(1, settings.length() - 1).split(","); if (tmp.length == 3) { reduction = Float.parseFloat(tmp[0]); reductionType = TraceReductionType.valueOf(tmp[1]); seed = Long.parseLong(tmp[2]); } } catch (NumberFormatException e) { errorResponse(DBGPErrorCode.INTERNAL_ERROR, parts[0] + " <name> [test number]"); return true; } } String traceExpression1 = parts[0]; interpreter.runtrace(traceExpression1, testNo, true, reduction, reductionType, seed); stdout("\n" + expression + " = " + "Trace completed\n"); } statusResponse(DBGPStatus.STOPPED, DBGPReason.OK); } catch (ContextException e) { dyingThread(e); } catch (Exception e) { status = DBGPStatus.STOPPED; statusReason = DBGPReason.ERROR; errorResponse(DBGPErrorCode.EVALUATION_ERROR, e.getMessage()); } return true; } } @Override protected boolean processEval(DBGPCommand c) throws DBGPException { checkArgs(c, 1, true); if (status != DBGPStatus.BREAK && status != DBGPStatus.STOPPING || breakpoint == null) { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } breaksSuspended = true; try { String exp = c.data; // Already base64 decoded by the parser interpreter.setDefaultName(breakpoint.location.getModule()); theAnswer = interpreter.evaluate(exp, breakContext); StringBuilder property = propertyResponse(exp, exp, interpreter.getDefaultName(), theAnswer); StringBuilder hdr = new StringBuilder("success=\"1\""); response(hdr, property); } catch (Exception e) { errorResponse(DBGPErrorCode.EVALUATION_ERROR, e.getMessage()); } finally { breaksSuspended = false; } return true; } @Override protected boolean processExpr(DBGPCommand c) throws DBGPException { checkArgs(c, 1, true); if (status == DBGPStatus.BREAK || status == DBGPStatus.STOPPING) { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } try { status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; String exp = c.data; // Already base64 decoded by the parser theAnswer = interpreter.execute(exp, this); StringBuilder property = propertyResponse(exp, exp, interpreter.getDefaultName(), theAnswer); StringBuilder hdr = new StringBuilder("success=\"1\""); status = DBGPStatus.STOPPED; statusReason = DBGPReason.OK; response(hdr, property); } catch (ContextException e) { dyingThread(e); } catch (Exception e) { status = DBGPStatus.STOPPED; statusReason = DBGPReason.ERROR; errorResponse(DBGPErrorCode.EVALUATION_ERROR, e.getMessage()); } return true; } protected boolean processExec(DBGPCommand c) throws DBGPException { checkArgs(c, 1, true); // TODO // if (status == DBGPStatus.BREAK || status == DBGPStatus.STOPPING) { // throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); // } try { status = DBGPStatus.RUNNING; statusReason = DBGPReason.OK; String exp = c.data; // Already base64 decoded by the parser DBGPReader dbgpReaderThread = newThread(null); DBGPExecResult result = DBGPExecProcesser.process(dbgpReaderThread, interpreter, exp); dbgpReaderThread.complete(DBGPReason.OK, null); StringBuilder property = makeProperty("", "", "", "", 0, 0, true, result.result.length(), -1, 0, result.result, new StringBuilder()); theAnswer = new CharacterValue('l'); StringBuilder hdr = new StringBuilder("success=\"1\""); status = DBGPStatus.STOPPED; statusReason = DBGPReason.OK; response(hdr, property); if (result.quit) { this.complete(DBGPReason.OK, null); } } catch (ContextException e) { dyingThread(e); } catch (Exception e) { status = DBGPStatus.STOPPED; statusReason = DBGPReason.ERROR; errorResponse(DBGPErrorCode.EVALUATION_ERROR, e.getMessage()); } return true; } @Override protected NameValuePairMap getContextValues(DBGPContextType context, int depth) { NameValuePairMap vars = new NameValuePairMap(); switch (context) { case LOCAL: if (depth == 0) { vars.putAll(breakContext.getVisibleVariables()); } else { Context frame = breakContext.getFrame(depth - 1).outer; if (frame != null) { vars.putAll(frame.getVisibleVariables()); } } if (breakContext instanceof ObjectContext) { ObjectContext octxt = (ObjectContext) breakContext; int line = breakpoint.location.getStartLine(); String opname = breakContext.guardOp == null ? "" : breakContext.guardOp.name.getName(); for (PDefinition d : octxt.self.type.getClassdef().getDefinitions()) { if (d instanceof APerSyncDefinition) { APerSyncDefinition pdef = (APerSyncDefinition) d; if (pdef.getOpname().getName().equals(opname) || pdef.getLocation().getStartLine() == line || octxt.assistantFactory.createPExpAssistant().findExpression(pdef.getGuard(), line) != null) { for (PExp sub : octxt.assistantFactory.createPExpAssistant().getSubExpressions(pdef.getGuard())) { if (sub instanceof AHistoryExp) { AHistoryExp hexp = (AHistoryExp) sub; try { Value v = hexp.apply(VdmRuntime.getExpressionEvaluator(), octxt); LexNameToken name = new LexNameToken(octxt.self.type.getName().getModule(), hexp.toString(), hexp.getLocation()); vars.put(name, v); } catch (Throwable e) { // Ignore } } } break; } } else if (d instanceof AMutexSyncDefinition) { AMutexSyncDefinition mdef = (AMutexSyncDefinition) d; for (ILexNameToken mop : mdef.getOperations()) { if (mop.getName().equals(opname)) { for (ILexNameToken op : mdef.getOperations()) { LexNameList ops = new LexNameList(op); PExp hexp = AstFactory.newAHistoryExp(mdef.getLocation(), new LexToken(null, VDMToken.ACTIVE), ops); try { Value v = hexp.apply(VdmRuntime.getExpressionEvaluator(), octxt); LexNameToken name = new LexNameToken(octxt.self.type.getName().getModule(), hexp.toString(), mdef.getLocation()); vars.put(name, v); } catch (Throwable e) { // Ignore } } break; } } } } } break; case CLASS: // Includes modules Context root = breakContext.getFrame(depth); if (root instanceof ObjectContext) { // Filter Values based in isDebugVisible instead of // vars.putAll(octxt.self.members) ObjectContext octxt = (ObjectContext) root; for (ILexNameToken key : octxt.self.members.keySet()) { Value v = octxt.self.members.get(key); if (isDebugVisible(v)) { vars.put(key, v); } } } else if (root instanceof ClassContext) { ClassContext cctxt = (ClassContext) root; vars.putAll(cctxt.assistantFactory.createSClassDefinitionAssistant().getStatics(cctxt.classdef)); } else if (root instanceof StateContext) { StateContext sctxt = (StateContext) root; if (sctxt.stateCtxt != null) { vars.putAll(sctxt.stateCtxt); } } break; case GLOBAL: vars.putAll(interpreter.initialContext); break; } return vars; } @Override protected void propertyGet(DBGPCommand c) throws DBGPException, IOException { if (c.data != null || c.options.size() > 5)// new parameter { throw new DBGPException(DBGPErrorCode.INVALID_OPTIONS, c.toString()); } if (status != DBGPStatus.BREAK && status != DBGPStatus.STOPPING) { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } DBGPOption option = c.getOption(DBGPOptionType.C); int type = 0; if (option != null) { type = Integer.parseInt(option.value); } DBGPContextType context = DBGPContextType.lookup(type); option = c.getOption(DBGPOptionType.D); int depth = -1; if (option != null) { depth = Integer.parseInt(option.value); } // option = c.getOption(DBGPOptionType.P); int page = 0; if (option != null) { page = Integer.parseInt(option.value); } option = c.getOption(DBGPOptionType.K); Integer key; if (option != null) { key = Integer.parseInt(option.value); if (debugValueMap.containsKey(key)) { response(null, propertyResponse(key, page)); return; } } // option = c.getOption(DBGPOptionType.N); if (option == null) { throw new DBGPException(DBGPErrorCode.CANT_GET_PROPERTY, c.toString()); } LexTokenReader ltr = new LexTokenReader(option.value, Dialect.VDM_PP); LexToken token = null; try { token = ltr.nextToken(); } catch (LexException e) { throw new DBGPException(DBGPErrorCode.CANT_GET_PROPERTY, option.value); } finally { ltr.close(); } if (token.isNot(VDMToken.NAME)) { throw new DBGPException(DBGPErrorCode.CANT_GET_PROPERTY, token.toString()); } NameValuePairMap vars = getContextValues(context, depth); LexNameToken longname = (LexNameToken) token; Value value = vars.get(longname); if (value == null) { throw new DBGPException(DBGPErrorCode.CANT_GET_PROPERTY, longname.toString()); } response(null, propertyResponse(longname, value, context)); } private StringBuilder propertyResponse(Integer key, Integer page) throws UnsupportedEncodingException { Value value = debugValueMap.get(key); StringBuilder sb = new StringBuilder(); Integer numChildren = getChildCount(value); Integer pageSize = Integer.parseInt(features.getProperty(DBGPFeatures.MAX_CHILDREN)); String data = null; StringBuilder nestedChildren = null; String name = "(ref=" + key + ")"; if (numChildren > 0) { data = value.kind().toString(); } else { data = value.toString(); } Integer defaultPageSize = Integer.parseInt(features.getProperty(DBGPFeatures.MAX_CHILDREN)); sb.append(propertyResponseChild(value, 1, 0, defaultPageSize, page)); nestedChildren = propertyResponseChild(value, 1, 0, defaultPageSize, page); boolean constant = numChildren > 0 || !(value instanceof UpdatableValue); return makeProperty(name, name, value.kind(), "", page, pageSize, constant, data.length(), key, numChildren, data, nestedChildren); } @Override protected void processOvertureCmd(DBGPCommand c) throws DBGPException, IOException, URISyntaxException, AnalysisException { checkArgs(c, 2, false); DBGPOption option = c.getOption(DBGPOptionType.C); if (option == null) { throw new DBGPException(DBGPErrorCode.INVALID_OPTIONS, c.toString()); } if (option.value.startsWith(DBGPXCmdOvertureCommandType.LATEX.toString())) { processLatex(c); } else { DBGPXCmdOvertureCommandType xcmd = DBGPXCmdOvertureCommandType.lookup(option.value); switch (xcmd) { case INIT: processInit(c); break; case CREATE: processCreate(c); break; case CURRENT_LINE: processCurrentLine(c); break; case SOURCE: processCurrentSource(c); break; case COVERAGE: processCoverage(c); break; case WRITE_COMPLETE_COVERAGE: processWriteCoverage(c); break; case POG: processPOG(c); break; case STACK: processStack(c); break; case TRACE: processTrace(c); break; case LIST: processList(); break; case FILES: processFiles(); break; case CLASSES: processClasses(c); break; case MODULES: processModules(c); break; case DEFAULT: processDefault(c); break; case LOG: processLog(c); break; default: throw new DBGPException(DBGPErrorCode.INVALID_OPTIONS, c.toString()); } } } private void processClasses(DBGPCommand c) throws IOException, DBGPException { if (!(interpreter instanceof ClassInterpreter)) { throw new DBGPException(DBGPErrorCode.INTERNAL_ERROR, c.toString()); } ClassInterpreter cinterpreter = (ClassInterpreter) interpreter; String def = cinterpreter.getDefaultName(); ClassList classes = cinterpreter.getClasses(); OutputStream out = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(out); for (SClassDefinition cls : classes) { if (cls.getName().getName().equals(def)) { pw.println(cls.getName().getName() + " (default)"); } else { pw.println(cls.getName().getName()); } } pw.close(); cdataResponse(out.toString()); } private void processModules(DBGPCommand c) throws DBGPException, IOException { if (!(interpreter instanceof ModuleInterpreter)) { throw new DBGPException(DBGPErrorCode.INTERNAL_ERROR, c.toString()); } ModuleInterpreter minterpreter = (ModuleInterpreter) interpreter; String def = minterpreter.getDefaultName(); List<AModuleModules> modules = minterpreter.getModules(); OutputStream out = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(out); for (AModuleModules m : modules) { if (m.getName().getName().equals(def)) { pw.println(m.getName().getName() + " (default)"); } else { pw.println(m.getName().getName()); } } pw.close(); cdataResponse(out.toString()); } /** * Overrides processLog to support URI file format and xcmdOvertureResponse as reply */ @Override protected void processLog(DBGPCommand c) throws IOException { StringBuilder out = new StringBuilder(); try { if (c.data == null) { if (RTLogger.getLogSize() > 0) { out.append("Flushing " + RTLogger.getLogSize() + " RT events\n"); } RTLogger.setLogfile(RTTextLogger.class, null); RTLogger.setLogfile(NextGenRTLogger.class, (File) null); out.append("RT events now logged to the console"); } else if (c.data.equals("off")) { RTLogger.enable(false); out.append("RT event logging disabled"); } else { File file = new File(new URI(c.data)); RTLogger.setLogfile(RTTextLogger.class, file); out.append("RT events now logged to " + c.data); } } catch (FileNotFoundException e) { out.append("Cannot create RT event log: " + e.getMessage()); } catch (URISyntaxException e) { out.append("Cannot decode log file from URI: " + e.getMessage()); } xcmdOvertureResponse(DBGPXCmdOvertureCommandType.LOG, null, out); } @Override protected void processStack(DBGPCommand c) throws IOException, DBGPException { if (status != DBGPStatus.BREAK && status != DBGPStatus.STOPPING || breakpoint == null) { throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); } OutputStream out = new ByteArrayOutputStream(); PrintWriter pw = new PrintWriter(out); pw.println("Stopped at " + breakpoint); breakContext.printStackTrace(pw, true); pw.close(); cdataResponse(out.toString()); } private void processWriteCoverage(DBGPCommand c) throws DBGPException, IOException, URISyntaxException { // if (status == DBGPStatus.BREAK) { // throw new DBGPException(DBGPErrorCode.NOT_AVAILABLE, c.toString()); // } File file = new File(new URI(c.data)); if (file == null || file.getName().length() == 0) { cdataResponse(file + ": folder not found"); } else { file.mkdirs(); writeCoverage(interpreter, file); StringBuilder sb = new StringBuilder(); sb.append("Coverage written to: " + file.toURI().toASCIIString()); xcmdOvertureResponse(DBGPXCmdOvertureCommandType.WRITE_COMPLETE_COVERAGE, null, sb); } } public static void writeCoverage(Interpreter interpreter, File coverage) throws IOException { Properties.init(); // Read properties file, if any for (File f : interpreter.getSourceFiles()) { SourceFile source = interpreter.getSourceFile(f); File data = new File(coverage.getPath() + File.separator + f.getName() + ".covtbl"); PrintWriter pw = new PrintWriter(data); source.writeCoverage(pw); pw.close(); } Properties.parser_tabstop = 1;// required to match locations with the editor representation } public static String getStackTrace(Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); t.printStackTrace(pw); pw.flush(); sw.flush(); return sw.toString(); } }