Expressions: add implementation and test
authorIgor Kulaychuk <igor.kulaychuk@gmail.com>
Wed, 3 Oct 2018 14:12:02 +0000 (17:12 +0300)
committerPetr Bred/AI Ecosystem Lab /SRR/Staff Engineer/삼성전자 <p.bred@samsung.com>
Sun, 14 Oct 2018 07:04:19 +0000 (10:04 +0300)
Signed-off-by: Alexander Aksenov <a.aksenov@samsung.com>
15 files changed:
src/debug/netcoredbg/SymbolReader.cs
src/debug/netcoredbg/SymbolReader.csproj
src/debug/netcoredbg/debugger.h
src/debug/netcoredbg/manageddebugger.h
src/debug/netcoredbg/miprotocol.cpp
src/debug/netcoredbg/symbolreader.cpp
src/debug/netcoredbg/symbolreader.h
src/debug/netcoredbg/valueprint.cpp
src/debug/netcoredbg/valueprint.h
src/debug/netcoredbg/variables.cpp
src/debug/netcoredbg/vscodeprotocol.cpp
tests/ExpressionsTest/ExpressionsTest.csproj [new file with mode: 0644]
tests/ExpressionsTest/Program.cs [new file with mode: 0644]
tests/runner/Runner.cs
tests/tests.sln

index 2527c0d..3c0edcc 100644 (file)
@@ -14,10 +14,13 @@ using Microsoft.CodeAnalysis.CSharp.Scripting;
 using Microsoft.CodeAnalysis.Scripting;
 using Microsoft.CodeAnalysis;
 using System.Reflection;
+using System.Dynamic;
+using Microsoft.CodeAnalysis.CSharp;
+using System.Text;
 
 namespace SOS
 {
-    internal class SymbolReader
+    public class SymbolReader
     {
         [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
         internal struct DebugInfo
@@ -1029,6 +1032,337 @@ namespace SOS
             }
         }
 
+        private static string[] basicTypes = new string[] {
+            "System.Object",
+            "System.Boolean",
+            "System.Byte",
+            "System.SByte",
+            "System.Char",
+            "System.Double",
+            "System.Single",
+            "System.Int32",
+            "System.UInt32",
+            "System.Int64",
+            "System.UInt64",
+            "System.Int16",
+            "System.UInt16",
+            "System.IntPtr",
+            "System.UIntPtr",
+            "System.Decimal",
+            "System.String"
+        };
+
+        internal delegate bool GetChildDelegate(IntPtr opaque, IntPtr corValue, string name, out int dataTypeId, out IntPtr dataPtr);
+
+        private static GetChildDelegate getChild;
+
+        internal static void RegisterGetChild(GetChildDelegate cb)
+        {
+            getChild = cb;
+        }
+
+        public class ContextVariable : DynamicObject
+        {
+            private IntPtr m_opaque;
+            public IntPtr m_corValue { get; }
+
+            public ContextVariable(IntPtr opaque, IntPtr corValue)
+            {
+                this.m_opaque = opaque;
+                this.m_corValue = corValue;
+            }
+
+            private bool UnmarshalResult(int dataTypeId, IntPtr dataPtr, out object result)
+            {
+                if (dataTypeId < 0)
+                {
+                    result = new ContextVariable(m_opaque, dataPtr);
+                    return true;
+                }
+                if (dataTypeId == 0) // special case for null object
+                {
+                    result = null;
+                    return true;
+                }
+                if (dataTypeId >= basicTypes.Length)
+                {
+                    result = null;
+                    return false;
+                }
+                Type dataType = Type.GetType(basicTypes[dataTypeId]);
+                if (dataType == typeof(string))
+                {
+                    if (dataPtr == IntPtr.Zero)
+                    {
+                        result = string.Empty;
+                        return true;
+                    }
+                    result = Marshal.PtrToStringBSTR(dataPtr);
+                    Marshal.FreeBSTR(dataPtr);
+                    return true;
+                }
+                if (dataType == typeof(char))
+                {
+                    BlittableChar c = Marshal.PtrToStructure<BlittableChar>(dataPtr);
+                    Marshal.FreeCoTaskMem(dataPtr);
+                    result = (char)c;
+                    return true;
+                }
+                if (dataType == typeof(bool))
+                {
+                    BlittableBoolean b = Marshal.PtrToStructure<BlittableBoolean>(dataPtr);
+                    Marshal.FreeCoTaskMem(dataPtr);
+                    result = (bool)b;
+                    return true;
+                }
+                result = Marshal.PtrToStructure(dataPtr, dataType);
+                Marshal.FreeCoTaskMem(dataPtr);
+                return true;
+            }
+
+            public override bool TryGetMember(
+                GetMemberBinder binder, out object result)
+            {
+                IntPtr dataPtr;
+                int dataTypeId;
+                if (!getChild(m_opaque, m_corValue, binder.Name, out dataTypeId, out dataPtr))
+                {
+                    result = null;
+                    return false;
+                }
+                return UnmarshalResult(dataTypeId, dataPtr, out result);
+            }
+
+            public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
+            {
+                IntPtr dataPtr;
+                int dataTypeId;
+                if (!getChild(m_opaque, m_corValue, "[" + string.Join(", ", indexes) + "]", out dataTypeId, out dataPtr))
+                {
+                    result = null;
+                    return false;
+                }
+                return UnmarshalResult(dataTypeId, dataPtr, out result);
+            }
+        }
+
+        public class Globals
+        {
+            public dynamic __context;
+        }
+
+        // Stores unresolved symbols, now only variables are supported
+        // Symbols are unique in list
+        class SyntaxAnalyzer
+        {
+            class FrameVars
+            {
+                Stack<string> vars = new Stack<string>();
+                Stack<int> varsInFrame = new Stack<int>();
+                int curFrameVars = 0;
+
+                public void Add(string name)
+                {
+                    vars.Push(name);
+                    curFrameVars++;
+                }
+
+                public void NewFrame()
+                {
+                    varsInFrame.Push(curFrameVars);
+                    curFrameVars = 0;
+                }
+
+                public void ExitFrame()
+                {
+                    for (int i = 0; i < curFrameVars; i++)
+                        vars.Pop();
+
+                    curFrameVars = varsInFrame.Pop();
+                }
+
+                public bool Contains(string name)
+                {
+                    return vars.Contains(name);
+                }
+            }
+
+            enum ParsingState
+            {
+                Common,
+                InvocationExpression,
+                GenericName
+            };
+
+            public List<string> unresolvedSymbols { get; private set; } = new List<string>();
+            FrameVars frameVars = new FrameVars();
+            SyntaxTree tree;
+
+            public SyntaxAnalyzer(string expression)
+            {
+                tree = CSharpSyntaxTree.ParseText(expression, options: new CSharpParseOptions(kind: SourceCodeKind.Script));
+                var root = tree.GetCompilationUnitRoot();
+                foreach (SyntaxNode sn in root.ChildNodes())
+                    ParseNode(sn, ParsingState.Common);
+            }
+
+            void ParseAccessNode(SyntaxNode sn, ParsingState state)
+            {
+                SyntaxNodeOrToken snt = sn.ChildNodesAndTokens().First();
+
+                if (snt.Kind().Equals(SyntaxKind.SimpleMemberAccessExpression))
+                    ParseAccessNode(snt.AsNode(), state);
+                else if (snt.IsNode)
+                    ParseNode(snt.AsNode(), state);
+                else if (snt.IsToken)
+                    ParseCommonToken(snt.AsToken(), state);
+            }
+
+            void ParseBlock(SyntaxNode sn, ParsingState state)
+            {
+                frameVars.NewFrame();
+                foreach (SyntaxNode snc in sn.ChildNodes())
+                    ParseNode(sn, ParsingState.Common);
+                frameVars.ExitFrame();
+            }
+
+            void ParseNode(SyntaxNode sn, ParsingState state)
+            {
+                if (sn.Kind().Equals(SyntaxKind.InvocationExpression))
+                    state = ParsingState.InvocationExpression;
+                else if (sn.Kind().Equals(SyntaxKind.GenericName))
+                    state = ParsingState.GenericName;
+                else if (sn.Kind().Equals(SyntaxKind.ArgumentList))
+                    state = ParsingState.Common;
+
+                foreach (SyntaxNodeOrToken snt in sn.ChildNodesAndTokens())
+                {
+                    if (snt.IsNode)
+                    {
+                        if (snt.Kind().Equals(SyntaxKind.SimpleMemberAccessExpression))
+                            ParseAccessNode(snt.AsNode(), state);
+                        else if (snt.Kind().Equals(SyntaxKind.Block))
+                            ParseBlock(snt.AsNode(), state);
+                        else
+                            ParseNode(snt.AsNode(), state);
+                    }
+                    else
+                    {
+                        if (sn.Kind().Equals(SyntaxKind.VariableDeclarator))
+                            ParseDeclarator(snt.AsToken());
+                        else
+                            ParseCommonToken(snt.AsToken(), state);
+                    }
+                }
+            }
+
+            void ParseCommonToken(SyntaxToken st, ParsingState state)
+            {
+                if (state == ParsingState.InvocationExpression ||
+                    state == ParsingState.GenericName)
+                    return;
+
+                if (st.Kind().Equals(SyntaxKind.IdentifierToken) &&
+                    !unresolvedSymbols.Contains(st.Value.ToString()) &&
+                    !frameVars.Contains(st.Value.ToString()))
+                    unresolvedSymbols.Add(st.Value.ToString());
+            }
+
+            void ParseDeclarator(SyntaxToken st)
+            {
+                if (st.Kind().Equals(SyntaxKind.IdentifierToken) && !frameVars.Contains(st.Value.ToString()))
+                    frameVars.Add(st.Value.ToString());
+            }
+        };
+
+
+        static void MarshalValue(object value, out int size, out IntPtr data)
+        {
+            if (value is string)
+            {
+                data = Marshal.StringToBSTR(value as string);
+                size = 0;
+            }
+            else if (value is char)
+            {
+                BlittableChar c = (BlittableChar)((char)value);
+                size = Marshal.SizeOf(c);
+                data = Marshal.AllocCoTaskMem(size);
+                Marshal.StructureToPtr(c, data, false);
+            }
+            else if (value is bool)
+            {
+                BlittableBoolean b = (BlittableBoolean)((bool)value);
+                size = Marshal.SizeOf(b);
+                data = Marshal.AllocCoTaskMem(size);
+                Marshal.StructureToPtr(b, data, false);
+            }
+            else
+            {
+                size = Marshal.SizeOf(value);
+                data = Marshal.AllocCoTaskMem(size);
+                Marshal.StructureToPtr(value, data, false);
+            }
+        }
+
+        internal static bool EvalExpression(string expr, IntPtr opaque, out IntPtr errorText, out int typeId, out int size, out IntPtr result)
+        {
+            SyntaxAnalyzer sa = new SyntaxAnalyzer(expr);
+
+            StringBuilder scriptText = new StringBuilder("#line hidden\n");
+
+            // Generate prefix with variables assignment to __context members
+            foreach (string us in sa.unresolvedSymbols)
+                scriptText.AppendFormat("var {0} = __context.{0};\n", us);
+
+            scriptText.Append("#line 1\n");
+            scriptText.Append(expr);
+
+            errorText = IntPtr.Zero;
+            result = IntPtr.Zero;
+            typeId = 0;
+            size = 0;
+            try
+            {
+                var scriptOptions = ScriptOptions.Default
+                    .WithImports("System")
+                    .WithReferences(typeof(Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo).Assembly);
+                var script = CSharpScript.Create(scriptText.ToString(), scriptOptions, globalsType: typeof(Globals));
+                script.Compile();
+                var returnValue = script.RunAsync(new Globals { __context = new ContextVariable(opaque, IntPtr.Zero) }).Result.ReturnValue;
+                if (returnValue is ContextVariable)
+                {
+                    typeId = -1;
+                    result = (returnValue as ContextVariable).m_corValue;
+                }
+                else
+                {
+                    if (returnValue is null)
+                    {
+                        typeId = 0;
+                        return true;
+                    }
+                    for (int i = 1; i < basicTypes.Length; i++)
+                    {
+                        if (returnValue.GetType() == Type.GetType(basicTypes[i]))
+                        {
+                            typeId = i;
+                            MarshalValue(returnValue, out size, out result);
+                            return true;
+                        }
+                    }
+                    return false;
+                    //errorText = Marshal.StringToBSTR(String.Format("{0}", returnValue));
+                }
+            }
+            catch(Exception e)
+            {
+                errorText = Marshal.StringToBSTR(e.ToString());
+                return false;
+            }
+            return true;
+        }
+
         internal static bool ParseExpression(string expr, string resultTypeName, out IntPtr data, out int size, out IntPtr errorText)
         {
             object value = null;
@@ -1075,30 +1409,7 @@ namespace SOS
                 errorText = Marshal.StringToBSTR("Value can not be null");
                 return false;
             }
-            if (value is string)
-            {
-                data = Marshal.StringToBSTR(value as string);
-            }
-            else if (value is char)
-            {
-                BlittableChar c = (BlittableChar)((char)value);
-                size = Marshal.SizeOf(c);
-                data = Marshal.AllocCoTaskMem(size);
-                Marshal.StructureToPtr(c, data, false);
-            }
-            else if (value is bool)
-            {
-                BlittableBoolean b = (BlittableBoolean)((bool)value);
-                size = Marshal.SizeOf(b);
-                data = Marshal.AllocCoTaskMem(size);
-                Marshal.StructureToPtr(b, data, false);
-            }
-            else
-            {
-                size = Marshal.SizeOf(value);
-                data = Marshal.AllocCoTaskMem(size);
-                Marshal.StructureToPtr(value, data, false);
-            }
+            MarshalValue(value, out size, out data);
             return true;
         }
     }
index e301d68..0e1a8c1 100644 (file)
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
     <OutputType>Library</OutputType>
     <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <NoStdLib>true</NoStdLib>
+    <!--NoStdLib>true</NoStdLib>
     <NoCompilerStandardLib>true</NoCompilerStandardLib>
     <IsDotNetFrameworkProductAssembly>true</IsDotNetFrameworkProductAssembly>
     <AssemblyKey>Open</AssemblyKey>
     <ExcludeMscorlibFacade>true</ExcludeMscorlibFacade>
-    <ContainsPackageReferences>true</ContainsPackageReferences>
+    <ContainsPackageReferences>true</ContainsPackageReferences-->
   </PropertyGroup>
 
   <ItemGroup>
index a72e985..f5a3154 100644 (file)
@@ -56,7 +56,7 @@ public:
     virtual HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) = 0;
     virtual HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) = 0;
     virtual int GetNamedVariables(uint32_t variablesReference) = 0;
-    virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) = 0;
+    virtual HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) = 0;
     virtual HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) = 0;
     virtual HRESULT SetVariableByExpression(uint64_t frameId, const std::string &name, const std::string &value, std::string &output) = 0;
 };
index 007d561..88e1689 100644 (file)
@@ -333,6 +333,9 @@ class Variables
         const std::string &value,
         std::string &output);
 
+    static BOOL VarGetChild(void *opaque, uint32_t varRef, const char* name, int *typeId, void **data);
+    bool GetChildDataByName(uint32_t varRef, const std::string &name, int *typeId, void **data);
+
 public:
 
     Variables(Evaluator &evaluator) : m_evaluator(evaluator), m_nextVariableReference(1) {}
@@ -363,7 +366,7 @@ public:
 
     HRESULT GetScopes(ICorDebugProcess *pProcess, uint64_t frameId, std::vector<Scope> &scopes);
 
-    HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable);
+    HRESULT Evaluate(ICorDebugProcess *pProcess, uint64_t frameId, const std::string &expression, Variable &variable, std::string &output);
 
     HRESULT GetValueByExpression(
         ICorDebugProcess *pProcess,
@@ -475,7 +478,7 @@ public:
     HRESULT GetScopes(uint64_t frameId, std::vector<Scope> &scopes) override;
     HRESULT GetVariables(uint32_t variablesReference, VariablesFilter filter, int start, int count, std::vector<Variable> &variables) override;
     int GetNamedVariables(uint32_t variablesReference) override;
-    HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable) override;
+    HRESULT Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output) override;
     HRESULT SetVariable(const std::string &name, const std::string &value, uint32_t ref, std::string &output) override;
     HRESULT SetVariableByExpression(uint64_t frameId, const std::string &expression, const std::string &value, std::string &output) override;
 };
index da2ecae..9047b1e 100644 (file)
@@ -329,7 +329,7 @@ HRESULT MIProtocol::CreateVar(int threadId, int level, const std::string &varobj
     uint64_t frameId = StackFrame(threadId, level, "").id;
 
     Variable variable;
-    IfFailRet(m_debugger->Evaluate(frameId, expression, variable));
+    IfFailRet(m_debugger->Evaluate(frameId, expression, variable, output));
 
     int print_values = 1;
     PrintNewVar(varobjName, variable, threadId, print_values, output);
index db3ae71..f306d51 100644 (file)
@@ -39,10 +39,13 @@ GetLineByILOffsetDelegate SymbolReader::getLineByILOffsetDelegate;
 GetStepRangesFromIPDelegate SymbolReader::getStepRangesFromIPDelegate;
 GetSequencePointsDelegate SymbolReader::getSequencePointsDelegate;
 ParseExpressionDelegate SymbolReader::parseExpressionDelegate = nullptr;
+EvalExpressionDelegate SymbolReader::evalExpressionDelegate = nullptr;
+RegisterGetChildDelegate SymbolReader::registerGetChildDelegate = nullptr;
 
 SysAllocStringLen_t SymbolReader::sysAllocStringLen;
 SysFreeString_t SymbolReader::sysFreeString;
 SysStringLen_t SymbolReader::sysStringLen;
+CoTaskMemAlloc_t SymbolReader::coTaskMemAlloc;
 CoTaskMemFree_t SymbolReader::coTaskMemFree;
 
 const int SymbolReader::HiddenLine = 0xfeefee;
@@ -125,6 +128,15 @@ HRESULT SymbolReader::LoadSymbolsForPortablePDB(
     return Status;
 }
 
+struct GetChildProxy
+{
+    SymbolReader::GetChildCallback &m_cb;
+    static BOOL GetChild(PVOID opaque, PVOID corValue, const char* name, int *typeId, PVOID *data)
+    {
+        return static_cast<GetChildProxy*>(opaque)->m_cb(corValue, name, typeId, data);
+    }
+};
+
 HRESULT SymbolReader::PrepareSymbolReader()
 {
     static bool attemptedSymbolReaderPreparation = false;
@@ -163,11 +175,13 @@ HRESULT SymbolReader::PrepareSymbolReader()
     sysAllocStringLen = (SysAllocStringLen_t)DLSym(coreclrLib, "SysAllocStringLen");
     sysFreeString = (SysFreeString_t)DLSym(coreclrLib, "SysFreeString");
     sysStringLen = (SysStringLen_t)DLSym(coreclrLib, "SysStringLen");
+    coTaskMemAlloc = (CoTaskMemAlloc_t)DLSym(coreclrLib, "CoTaskMemAlloc");
     coTaskMemFree = (CoTaskMemFree_t)DLSym(coreclrLib, "CoTaskMemFree");
 #else
     sysAllocStringLen = SysAllocStringLen;
     sysFreeString = SysFreeString;
     sysStringLen = SysStringLen;
+    coTaskMemAlloc = CoTaskMemAlloc;
     coTaskMemFree = CoTaskMemFree;
 #endif
 
@@ -189,6 +203,12 @@ HRESULT SymbolReader::PrepareSymbolReader()
         return E_FAIL;
     }
 
+    if (coTaskMemAlloc == nullptr)
+    {
+        fprintf(stderr, "Error: CoTaskMemAlloc not found\n");
+        return E_FAIL;
+    }
+
     if (coTaskMemFree == nullptr)
     {
         fprintf(stderr, "Error: CoTaskMemFree not found\n");
@@ -260,6 +280,10 @@ HRESULT SymbolReader::PrepareSymbolReader()
     IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetStepRangesFromIP", (void **)&getStepRangesFromIPDelegate));
     IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "GetSequencePoints", (void **)&getSequencePointsDelegate));
     IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "ParseExpression", (void **)&parseExpressionDelegate));
+    IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "EvalExpression", (void **)&evalExpressionDelegate));
+    IfFailRet(createDelegate(hostHandle, domainId, SymbolReaderDllName, SymbolReaderClassName, "RegisterGetChild", (void **)&registerGetChildDelegate));
+    if (!registerGetChildDelegate(GetChildProxy::GetChild))
+        return E_FAIL;
 
     // Warm up Roslyn
     std::thread([](){ std::string data; std::string err; SymbolReader::ParseExpression("1", "System.Int32", data, err); }).detach();
@@ -439,3 +463,73 @@ HRESULT SymbolReader::ParseExpression(
 
     return S_OK;
 }
+
+HRESULT SymbolReader::EvalExpression(const std::string &expr, std::string &result, int *typeId, ICorDebugValue **ppValue, GetChildCallback cb)
+{
+    HRESULT Status;
+
+    PrepareSymbolReader();
+
+    if (evalExpressionDelegate == nullptr)
+        return E_FAIL;
+
+    GetChildProxy proxy { cb };
+
+    PVOID valuePtr = nullptr;
+    int size = 0;
+    BSTR resultText;
+    BOOL ok = evalExpressionDelegate(expr.c_str(), &proxy, &resultText, typeId, &size, &valuePtr);
+    if (!ok)
+    {
+        if (resultText)
+        {
+            result = to_utf8(resultText);
+            sysFreeString(resultText);
+        }
+        return E_FAIL;
+    }
+
+    switch(*typeId)
+    {
+        case TypeCorValue:
+            *ppValue = static_cast<ICorDebugValue*>(valuePtr);
+            if (*ppValue)
+                (*ppValue)->AddRef();
+            break;
+        case TypeObject:
+            result = std::string();
+            break;
+        case TypeString:
+            result = to_utf8((BSTR)valuePtr);
+            sysFreeString((BSTR)valuePtr);
+            break;
+        default:
+            result.resize(size);
+            memmove(&result[0], valuePtr, size);
+            coTaskMemFree(valuePtr);
+            break;
+    }
+
+    return S_OK;
+}
+
+PVOID SymbolReader::AllocBytes(size_t size)
+{
+    PrepareSymbolReader();
+    if (coTaskMemAlloc == nullptr)
+        return nullptr;
+    return coTaskMemAlloc(size);
+}
+
+PVOID SymbolReader::AllocString(const std::string &str)
+{
+    PrepareSymbolReader();
+    if (sysAllocStringLen == nullptr)
+        return nullptr;
+    auto wstr = to_utf16(str);
+    BSTR bstr = sysAllocStringLen(0, wstr.size());
+    if (sysStringLen(bstr) == 0)
+        return nullptr;
+    memmove(bstr, wstr.data(), wstr.size() * sizeof(decltype(wstr[0])));
+    return bstr;
+}
index a249f62..e5607ab 100644 (file)
@@ -10,6 +10,7 @@
 
 #include <string>
 #include <vector>
+#include <functional>
 
 
 /// FIXME: Definition of `TADDR`
@@ -25,10 +26,14 @@ typedef  BOOL (*GetLineByILOffsetDelegate)(PVOID, mdMethodDef, ULONG64, ULONG *,
 typedef  BOOL (*GetStepRangesFromIPDelegate)(PVOID, int, mdMethodDef, unsigned int*, unsigned int*);
 typedef  BOOL (*GetSequencePointsDelegate)(PVOID, mdMethodDef, PVOID*, int*);
 typedef  BOOL (*ParseExpressionDelegate)(const char*, const char*, PVOID*, int *, BSTR*);
+typedef  BOOL (*EvalExpressionDelegate)(const char*, PVOID, BSTR*, int*, int*, PVOID*);
+typedef  BOOL (*GetChildDelegate)(PVOID, PVOID, const char*, int *, PVOID*);
+typedef  BOOL (*RegisterGetChildDelegate)(GetChildDelegate);
 
 typedef BSTR (*SysAllocStringLen_t)(const OLECHAR*, UINT);
 typedef void (*SysFreeString_t)(BSTR);
 typedef UINT (*SysStringLen_t)(BSTR);
+typedef LPVOID (*CoTaskMemAlloc_t)(size_t);
 typedef void (*CoTaskMemFree_t)(LPVOID);
 
 BOOL SafeReadMemory (TADDR offset, PVOID lpBuffer, ULONG cb,
@@ -48,10 +53,13 @@ private:
     static GetStepRangesFromIPDelegate getStepRangesFromIPDelegate;
     static GetSequencePointsDelegate getSequencePointsDelegate;
     static ParseExpressionDelegate parseExpressionDelegate;
+    static EvalExpressionDelegate evalExpressionDelegate;
+    static RegisterGetChildDelegate registerGetChildDelegate;
 
     static SysAllocStringLen_t sysAllocStringLen;
     static SysFreeString_t sysFreeString;
     static SysStringLen_t sysStringLen;
+    static CoTaskMemAlloc_t coTaskMemAlloc;
     static CoTaskMemFree_t coTaskMemFree;
 
     static HRESULT PrepareSymbolReader();
@@ -76,6 +84,28 @@ public:
         int32_t offset;
     } PACK_END;
 
+    // Keep in sync with string[] basicTypes in SymbolReader.cs
+    enum BasicTypes {
+        TypeCorValue = -1,
+        TypeObject = 0, //     "System.Object",
+        TypeBoolean, //        "System.Boolean",
+        TypeByte,    //        "System.Byte",
+        TypeSByte,   //        "System.SByte",
+        TypeChar,    //        "System.Char",
+        TypeDouble,  //        "System.Double",
+        TypeSingle,  //        "System.Single",
+        TypeInt32,   //        "System.Int32",
+        TypeUInt32,  //        "System.UInt32",
+        TypeInt64,   //        "System.Int64",
+        TypeUInt64,  //        "System.UInt64",
+        TypeInt16,   //        "System.Int16",
+        TypeUInt16,  //        "System.UInt16",
+        TypeIntPtr,  //        "System.IntPtr",
+        TypeUIntPtr, //        "System.UIntPtr",
+        TypeDecimal, //        "System.Decimal",
+        TypeString,  //        "System.String"
+    };
+
     SymbolReader()
     {
         m_symbolReaderHandle = 0;
@@ -94,6 +124,8 @@ public:
 
     static void SetCoreCLRPath(const std::string &path) { coreClrPath = path; }
 
+    typedef std::function<bool(PVOID, const std::string&, int *, PVOID*)> GetChildCallback;
+
     HRESULT LoadSymbols(IMetaDataImport* pMD, ICorDebugModule* pModule);
     HRESULT GetLineByILOffset(mdMethodDef MethodToken, ULONG64 IlOffset, ULONG *pLinenum, WCHAR* pwszFileName, ULONG cchFileName);
     HRESULT GetNamedLocalVariableAndScope(ICorDebugILFrame * pILFrame, mdMethodDef methodToken, ULONG localIndex, WCHAR* paramName, ULONG paramNameLen, ICorDebugValue **ppValue, ULONG32* pIlStart, ULONG32* pIlEnd);
@@ -101,4 +133,7 @@ public:
     HRESULT GetStepRangesFromIP(ULONG32 ip, mdMethodDef MethodToken, ULONG32 *ilStartOffset, ULONG32 *ilEndOffset);
     HRESULT GetSequencePoints(mdMethodDef methodToken, std::vector<SequencePoint> &points);
     static HRESULT ParseExpression(const std::string &expr, const std::string &typeName, std::string &data, std::string &errorText);
+    static HRESULT EvalExpression(const std::string &expr, std::string &result, int *typeId, ICorDebugValue **ppValue, GetChildCallback cb);
+    static PVOID AllocBytes(size_t size);
+    static PVOID AllocString(const std::string &str);
 };
index e1369f3..f10be9e 100644 (file)
@@ -13,6 +13,7 @@
 #include "typeprinter.h"
 #include "torelease.h"
 #include "cputil.h"
+#include "symbolreader.h"
 
 
 // From strike.cpp
@@ -349,17 +350,13 @@ static std::string uint96_to_string(uint32_t *v)
     return result;
 }
 
-static HRESULT PrintDecimalValue(ICorDebugValue *pValue,
-                                 std::string &output)
+static void PrintDecimal(
+    unsigned int hi,
+    unsigned int mid,
+    unsigned int lo,
+    unsigned int flags,
+    std::string &output)
 {
-    HRESULT Status = S_OK;
-
-    unsigned int hi;
-    unsigned int mid;
-    unsigned int lo;
-    unsigned int flags;
-    IfFailRet(GetDecimalFields(pValue, hi, mid, lo, flags));
-
     uint32_t v[3] = { lo, mid, hi };
 
     output = uint96_to_string(v);
@@ -386,10 +383,39 @@ static HRESULT PrintDecimalValue(ICorDebugValue *pValue,
 
     if (is_negative)
         output.insert(0, 1, '-');
+}
+
+static HRESULT PrintDecimalValue(ICorDebugValue *pValue,
+                                 std::string &output)
+{
+    HRESULT Status = S_OK;
+
+    unsigned int hi;
+    unsigned int mid;
+    unsigned int lo;
+    unsigned int flags;
+
+    IfFailRet(GetDecimalFields(pValue, hi, mid, lo, flags));
+
+    PrintDecimal(hi, mid, lo, flags, output);
 
     return S_OK;
 }
 
+PACK_BEGIN struct Decimal {
+    uint32_t flags;
+    uint32_t hi;
+    uint32_t lo;
+    uint32_t mid;
+} PACK_END;
+
+static void PrintDecimalValue(const std::string &rawValue,
+                              std::string &output)
+{
+    const Decimal *d = reinterpret_cast<const Decimal*>(&rawValue[0]);
+    PrintDecimal(d->hi, d->mid, d->lo, d->flags, output);
+}
+
 static HRESULT PrintArrayValue(ICorDebugValue *pValue,
                                std::string &output)
 {
@@ -687,3 +713,266 @@ HRESULT PrintValue(ICorDebugValue *pInputValue, std::string &output, bool escape
     output = ss.str();
     return S_OK;
 }
+
+HRESULT PrintBasicValue(int typeId, const std::string &rawData, std::string &typeName, std::string &value)
+{
+    std::ostringstream ss;
+    switch(typeId)
+    {
+        case SymbolReader::TypeCorValue:
+            ss << "null";
+            typeName = "object";
+            break;
+        case SymbolReader::TypeObject:
+            ss << "null";
+            typeName = "object";
+            break;
+        case SymbolReader::TypeBoolean:
+            ss << (rawData[0] == 0 ? "false" : "true");
+            typeName = "bool";
+            break;
+        case SymbolReader::TypeByte:
+            ss << (unsigned int) *(unsigned char*) &(rawData[0]);
+            typeName = "byte";
+            break;
+        case SymbolReader::TypeSByte:
+            ss << (int) *(char*) &(rawData[0]);
+            typeName = "sbyte";
+            break;
+        case SymbolReader::TypeChar:
+            {
+                WCHAR wc = * (WCHAR *) &(rawData[0]);
+                std::string printableVal = to_utf8(wc);
+                EscapeString(printableVal, '\'');
+                ss << (unsigned int)wc << " '" << printableVal << "'";
+                typeName = "char";
+            }
+            break;
+        case SymbolReader::TypeDouble:
+            ss << std::setprecision(16) << *(double*) &(rawData[0]);
+            typeName = "double";
+            break;
+        case SymbolReader::TypeSingle:
+            ss << std::setprecision(8) << *(float*) &(rawData[0]);
+            typeName = "float";
+            break;
+        case SymbolReader::TypeInt32:
+            ss << *(int*) &(rawData[0]);
+            typeName = "int";
+            break;
+        case SymbolReader::TypeUInt32:
+            ss << *(unsigned int*) &(rawData[0]);
+            typeName = "uint";
+            break;
+        case SymbolReader::TypeInt64:
+            ss << *(__int64*) &(rawData[0]);
+            typeName = "long";
+            break;
+        case SymbolReader::TypeUInt64:
+            typeName = "ulong";
+            ss << *(unsigned __int64*) &(rawData[0]);
+            break;
+        case SymbolReader::TypeInt16:
+            ss << *(short*) &(rawData[0]);
+            typeName = "short";
+            break;
+        case SymbolReader::TypeUInt16:
+            ss << *(unsigned short*) &(rawData[0]);
+            typeName = "ushort";
+            break;
+        case SymbolReader::TypeIntPtr:
+            ss << "0x" << std::hex << *(intptr_t*) &(rawData[0]);
+            typeName = "IntPtr";
+            break;
+        case SymbolReader::TypeUIntPtr:
+            ss << "0x" << std::hex << *(intptr_t*) &(rawData[0]);
+            typeName = "UIntPtr";
+            break;
+        case SymbolReader::TypeDecimal:
+            PrintDecimalValue(rawData, value);
+            typeName = "decimal";
+            return S_OK;
+        case SymbolReader::TypeString:
+            {
+                std::string rawStr = rawData;
+                EscapeString(rawStr, '"');
+                ss << "\"" << rawStr << "\"";
+            }
+            break;
+    }
+    value = ss.str();
+    return S_OK;
+}
+
+HRESULT MarshalValue(ICorDebugValue *pInputValue, int *typeId, void **data)
+{
+    HRESULT Status;
+
+    BOOL isNull = TRUE;
+    ToRelease<ICorDebugValue> pValue;
+    IfFailRet(DereferenceAndUnboxValue(pInputValue, &pValue, &isNull));
+
+    if (isNull)
+    {
+        *data = nullptr;
+        *typeId = SymbolReader::TypeObject;
+        return S_OK;
+    }
+
+    ULONG32 cbSize;
+    IfFailRet(Status = pValue->GetSize(&cbSize));
+
+    ArrayHolder<BYTE> rgbValue = new (std::nothrow) BYTE[cbSize];
+    if (rgbValue == nullptr)
+    {
+        return E_OUTOFMEMORY;
+    }
+
+    memset(rgbValue.GetPtr(), 0, cbSize * sizeof(BYTE));
+
+    CorElementType corElemType;
+    IfFailRet(pValue->GetType(&corElemType));
+
+    if (corElemType == ELEMENT_TYPE_STRING)
+    {
+        std::string raw_str;
+        IfFailRet(PrintStringValue(pValue, raw_str));
+
+        if (!raw_str.empty())
+        {
+            *data = SymbolReader::AllocString(raw_str);
+            if (*data == nullptr)
+                return E_FAIL;
+        }
+        else
+        {
+            *data = nullptr;
+        }
+
+        *typeId = SymbolReader::TypeString;
+        return S_OK;
+    }
+
+    if (corElemType == ELEMENT_TYPE_SZARRAY || corElemType == ELEMENT_TYPE_ARRAY)
+    {
+        pInputValue->AddRef();
+        *data = pInputValue;
+        *typeId = SymbolReader::TypeCorValue;
+        return S_OK;
+    }
+
+    ToRelease<ICorDebugGenericValue> pGenericValue;
+    IfFailRet(pValue->QueryInterface(IID_ICorDebugGenericValue, (LPVOID*) &pGenericValue));
+    IfFailRet(pGenericValue->GetValue((LPVOID) &(rgbValue[0])));
+
+    if (IsEnum(pValue))
+    {
+        return E_FAIL;
+        // TODO: Support enums, return PrintEnumValue(pValue, rgbValue, output);
+    }
+
+    switch (corElemType)
+    {
+    default:
+        return E_FAIL;
+
+    case ELEMENT_TYPE_PTR:
+        *typeId = SymbolReader::TypeIntPtr;
+        break;
+
+    case ELEMENT_TYPE_FNPTR:
+        {
+            CORDB_ADDRESS addr = 0;
+            ToRelease<ICorDebugReferenceValue> pReferenceValue;
+            if(SUCCEEDED(pValue->QueryInterface(IID_ICorDebugReferenceValue, (LPVOID*) &pReferenceValue)))
+                pReferenceValue->GetValue(&addr);
+            *(CORDB_ADDRESS*) &(rgbValue[0]) = addr;
+            *typeId = SymbolReader::TypeIntPtr;
+        }
+        break;
+
+    case ELEMENT_TYPE_VALUETYPE:
+    case ELEMENT_TYPE_CLASS:
+        {
+            std::string typeName;
+            TypePrinter::GetTypeOfValue(pValue, typeName);
+            if (typeName != "decimal")
+            {
+                pInputValue->AddRef();
+                *data = pInputValue;
+                *typeId = SymbolReader::TypeCorValue;
+                return S_OK;
+            }
+            *typeId = SymbolReader::TypeDecimal;
+        }
+        break;
+
+    case ELEMENT_TYPE_BOOLEAN:
+        *typeId = SymbolReader::TypeBoolean;
+        break;
+
+    case ELEMENT_TYPE_CHAR:
+        *typeId = SymbolReader::TypeChar;
+        break;
+
+    case ELEMENT_TYPE_I1:
+        *typeId = SymbolReader::TypeSByte;
+        break;
+
+    case ELEMENT_TYPE_U1:
+        *typeId = SymbolReader::TypeByte;
+        break;
+
+    case ELEMENT_TYPE_I2:
+        *typeId = SymbolReader::TypeInt16;
+        break;
+
+    case ELEMENT_TYPE_U2:
+        *typeId = SymbolReader::TypeUInt16;
+        break;
+
+    case ELEMENT_TYPE_I:
+        *typeId = SymbolReader::TypeIntPtr;
+        break;
+
+    case ELEMENT_TYPE_U:
+        *typeId = SymbolReader::TypeUIntPtr;
+        break;
+
+    case ELEMENT_TYPE_I4:
+        *typeId = SymbolReader::TypeInt32;
+        break;
+
+    case ELEMENT_TYPE_U4:
+        *typeId = SymbolReader::TypeUInt32;
+        break;
+
+    case ELEMENT_TYPE_I8:
+        *typeId = SymbolReader::TypeInt64;
+        break;
+
+    case ELEMENT_TYPE_U8:
+        *typeId = SymbolReader::TypeUInt64;
+        break;
+
+    case ELEMENT_TYPE_R4:
+        *typeId = SymbolReader::TypeSingle;
+        break;
+
+    case ELEMENT_TYPE_R8:
+        *typeId = SymbolReader::TypeDouble;
+        break;
+
+    case ELEMENT_TYPE_OBJECT:
+        return E_FAIL;
+
+    }
+
+    *data = SymbolReader::AllocBytes(cbSize);
+    if (*data == nullptr)
+    {
+        return E_FAIL;
+    }
+    memmove(*data, &(rgbValue[0]), cbSize);
+    return S_OK;
+}
index fb3252e..e53cbf1 100644 (file)
@@ -9,4 +9,6 @@
 #include <string>
 
 HRESULT PrintValue(ICorDebugValue *pInputValue, std::string &output, bool escape = true);
+HRESULT PrintBasicValue(int typeId, const std::string &rawData, std::string &typeName, std::string &value);
 HRESULT DereferenceAndUnboxValue(ICorDebugValue * pValue, ICorDebugValue** ppOutputValue, BOOL * pIsNull = nullptr);
+HRESULT MarshalValue(ICorDebugValue *pInputValue, int *typeId, void **data);
index 7420d82..66a5363 100644 (file)
@@ -7,13 +7,14 @@
 #include <unordered_set>
 #include <vector>
 #include <cstring>
-
+#include <algorithm>
 #include <sstream>
 
 #include "typeprinter.h"
 #include "valueprint.h"
 #include "valuewrite.h"
 #include "frames.h"
+#include "symbolreader.h"
 
 
 HRESULT Variables::GetNumChild(
@@ -397,16 +398,17 @@ HRESULT Variables::GetChildren(
     return S_OK;
 }
 
-HRESULT ManagedDebugger::Evaluate(uint64_t frameId, const std::string &expression, Variable &variable)
+HRESULT ManagedDebugger::Evaluate(uint64_t frameId, const std::string &expression, Variable &variable, std::string &output)
 {
-    return m_variables.Evaluate(m_pProcess, frameId, expression, variable);
+    return m_variables.Evaluate(m_pProcess, frameId, expression, variable, output);
 }
 
 HRESULT Variables::Evaluate(
     ICorDebugProcess *pProcess,
     uint64_t frameId,
     const std::string &expression,
-    Variable &variable)
+    Variable &variable,
+    std::string &output)
 {
     if (pProcess == nullptr)
         return E_FAIL;
@@ -418,15 +420,104 @@ HRESULT Variables::Evaluate(
     IfFailRet(pProcess->GetThread(stackFrame.GetThreadId(), &pThread));
     ToRelease<ICorDebugFrame> pFrame;
     IfFailRet(GetFrameAt(pThread, stackFrame.GetLevel(), &pFrame));
+    ToRelease<ICorDebugILFrame> pILFrame;
+    if (pFrame)
+        IfFailRet(pFrame->QueryInterface(IID_ICorDebugILFrame, (LPVOID*) &pILFrame));
 
     ToRelease<ICorDebugValue> pResultValue;
-    IfFailRet(m_evaluator.EvalExpr(pThread, pFrame, expression, &pResultValue));
+    int typeId;
+    std::vector< ToRelease<ICorDebugValue> > marshalledValues;
+
+    IfFailRet(SymbolReader::EvalExpression(
+        expression, output, &typeId, &pResultValue,
+        [&](void *corValue, const std::string &name, int *typeId, void **data) -> bool
+    {
+        ToRelease<ICorDebugValue> pThisValue;
+
+        if (!corValue) // Scope
+        {
+            bool found = false;
+            if (FAILED(m_evaluator.WalkStackVars(pFrame, [&](
+                ICorDebugILFrame *pILFrame,
+                ICorDebugValue *pValue,
+                const std::string &varName) -> HRESULT
+            {
+                if (!found && varName == "this")
+                {
+                    pThisValue = pValue;
+                    pValue->AddRef();
+                }
+                if (!found && varName == name)
+                {
+                    found = true;
+                    IfFailRet(MarshalValue(pValue, typeId, data));
+                    if (*typeId < 0)
+                    {
+                        marshalledValues.emplace_back(static_cast<ICorDebugValue*>(*data)); // FIXME: no exception safety
+                    }
+                }
+
+                return S_OK;
+            })))
+            {
+                return false;
+            }
+            if (found)
+                return true;
+            if (!pThisValue)
+                return false;
+
+            corValue = pThisValue;
+        }
+
+        std::vector<Member> members;
+
+        const bool fetchOnlyStatic = false;
+        bool hasStaticMembers = false;
+
+        ICorDebugValue *pValue = static_cast<ICorDebugValue*>(corValue);
+
+        if (FAILED(FetchFieldsAndProperties(pValue,
+                                        pThread,
+                                        pILFrame,
+                                        members,
+                                        fetchOnlyStatic,
+                                        hasStaticMembers,
+                                        0,
+                                        INT_MAX)))
+            return false;
+
+        FixupInheritedFieldNames(members);
+
+        auto memberIt = std::find_if(members.begin(), members.end(), [&name](const Member &m){ return m.name == name; });
+        if (memberIt == members.end())
+            return false;
+
+        if (!memberIt->value)
+            return false;
+
+        if (FAILED(MarshalValue(memberIt->value, typeId, data)))
+        {
+            return false;
+        }
+        if (*typeId < 0)
+            marshalledValues.emplace_back(static_cast<ICorDebugValue*>(*data));
+
+        return true;
+    }));
 
     variable.evaluateName = expression;
 
-    bool escape = true;
-    PrintValue(pResultValue, variable.value, escape);
-    TypePrinter::GetTypeOfValue(pResultValue, variable.type);
+    if (pResultValue)
+    {
+        const bool escape = true;
+        PrintValue(pResultValue, variable.value, escape);
+        TypePrinter::GetTypeOfValue(pResultValue, variable.type);
+    }
+    else
+    {
+        PrintBasicValue(typeId, output, variable.type, variable.value);
+    }
     AddVariableReference(variable, frameId, pResultValue, ValueIsVariable);
 
     return S_OK;
index 1d10b54..9957348 100644 (file)
@@ -385,7 +385,13 @@ HRESULT VSCodeProtocol::HandleCommand(const std::string &command, const json &ar
         uint64_t frameId = frameIdIter.value();
 
         Variable variable;
-        IfFailRet(m_debugger->Evaluate(frameId, expression, variable));
+        std::string output;
+        Status = m_debugger->Evaluate(frameId, expression, variable, output);
+        if (FAILED(Status))
+        {
+            body["message"] = output;
+            return Status;
+        }
 
         body["result"] = variable.value;
         body["type"] = variable.type;
diff --git a/tests/ExpressionsTest/ExpressionsTest.csproj b/tests/ExpressionsTest/ExpressionsTest.csproj
new file mode 100644 (file)
index 0000000..8e047a0
--- /dev/null
@@ -0,0 +1,8 @@
+<Project Sdk="Microsoft.NET.Sdk">\r
+\r
+  <PropertyGroup>\r
+    <OutputType>Exe</OutputType>\r
+    <TargetFramework>netcoreapp2.0</TargetFramework>\r
+  </PropertyGroup>\r
+\r
+</Project>\r
diff --git a/tests/ExpressionsTest/Program.cs b/tests/ExpressionsTest/Program.cs
new file mode 100644 (file)
index 0000000..920ce5d
--- /dev/null
@@ -0,0 +1,121 @@
+/*\r
+using System.IO;\r
+Send("1-file-exec-and-symbols dotnet");\r
+Send("2-exec-arguments " + TestBin);\r
+\r
+string filename = Path.GetFileName(TestSource);\r
+\r
+Send(String.Format("3-break-insert -f {0}:{1}", filename, Lines["BREAK1"]));\r
+r = Expect("3^done");\r
+int id1 = r.Find("bkpt").FindInt("number");\r
+\r
+Send(String.Format("4-break-insert -f {0}:{1}", filename, Lines["BREAK2"]));\r
+r = Expect("4^done");\r
+int id2 = r.Find("bkpt").FindInt("number");\r
+\r
+Send(String.Format("5-break-insert -f {0}:{1}", filename, Lines["BREAK3"]));\r
+r = Expect("5^done");\r
+int id3 = r.Find("bkpt").FindInt("number");\r
+\r
+Send("6-exec-run");\r
+*/\r
+\r
+using System;\r
+\r
+namespace ExpressionsTest\r
+{\r
+    class Program\r
+    {\r
+        static void Main(string[] args)\r
+        {                                               // //@START@\r
+/*\r
+var r = Expect("*stopped");\r
+Assert.Equal("entry-point-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["START"], r.Find("frame").FindInt("line"));\r
+\r
+Send("7-exec-continue");\r
+*/\r
+            int a = 10;\r
+            int b = 11;\r
+            TestStruct tc = new TestStruct(a + 1, b);\r
+            string str1 = "string1";\r
+            string str2 = "string2";\r
+            int c = tc.b + b;                           // //@BREAK1@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK1"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id1, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("8-var-create - * \"{0}\"", "a + b"));\r
+r = Expect("8^done");\r
+Assert.Equal("21", r.FindString("value"));\r
+\r
+Send(String.Format("9-var-create - * \"{0}\"", "tc.a + b"));\r
+r = Expect("9^done");\r
+Assert.Equal("22", r.FindString("value"));\r
+\r
+Send(String.Format("10-var-create - * \"{0}\"", "str1 + str2"));\r
+r = Expect("10^done");\r
+Assert.Equal("\"string1string2\"", r.FindString("value"));\r
+\r
+Send("11-exec-continue");\r
+*/\r
+            {\r
+                int d = 99;\r
+                int e = c + a;                          // //@BREAK2@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK2"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id2, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("12-var-create - * \"{0}\"", "d + a"));\r
+r = Expect("12^done");\r
+Assert.Equal("109", r.FindString("value"));\r
+\r
+Send("13-exec-continue");\r
+*/\r
+            }\r
+\r
+            Console.WriteLine(str1 + str2);\r
+\r
+            tc.IncA();\r
+\r
+            Console.WriteLine("Hello World!");\r
+        }\r
+    }\r
+\r
+    struct TestStruct\r
+    {\r
+        public int a;\r
+        public int b;\r
+\r
+        public TestStruct(int x, int y)\r
+        {\r
+            a = x;\r
+            b = y;\r
+        }\r
+\r
+        public void IncA()\r
+        {\r
+            a++;                                        // //@BREAK3@\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("breakpoint-hit", r.FindString("reason"));\r
+Assert.Equal(Lines["BREAK3"], r.Find("frame").FindInt("line"));\r
+Assert.Equal(id3, r.FindInt("bkptno"));\r
+\r
+Send(String.Format("14-var-create - * \"{0}\"", "a + 1"));\r
+r = Expect("14^done");\r
+Assert.Equal("12", r.FindString("value"));\r
+\r
+Send("15-exec-continue");\r
+*/\r
+        }\r
+    }\r
+/*\r
+r = Expect("*stopped");\r
+Assert.Equal("exited", r.FindString("reason"));\r
+*/\r
+}\r
index b690d30..d759f01 100644 (file)
@@ -39,6 +39,9 @@ namespace Runner
         [Fact]
         public void SetValuesTest() => ExecuteTest();
 
+        [Fact]
+        public void ExpressionsTest() => ExecuteTest();
+
         private const int DefaultTimeoutSec = 20;
         private int expectTimeoutSec;
 
index 9a23f3e..5636e8a 100644 (file)
@@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BreakpointAddRemoveTest", "
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetValuesTest", "SetValuesTest\SetValuesTest.csproj", "{9E6CD7E6-0B84-4088-9097-1839A14B49DB}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExpressionsTest", "ExpressionsTest\ExpressionsTest.csproj", "{3477E658-CB14-4D86-8909-F7448C17AC9A}"
+EndProject
 Global
        GlobalSection(SolutionConfigurationPlatforms) = preSolution
                Debug|Any CPU = Debug|Any CPU
@@ -100,5 +102,17 @@ Global
                {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x64.Build.0 = Release|x64
                {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.ActiveCfg = Release|x86
                {9E6CD7E6-0B84-4088-9097-1839A14B49DB}.Release|x86.Build.0 = Release|x86
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x64.ActiveCfg = Debug|x64
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x64.Build.0 = Debug|x64
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x86.ActiveCfg = Debug|x86
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Debug|x86.Build.0 = Debug|x86
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|Any CPU.Build.0 = Release|Any CPU
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x64.ActiveCfg = Release|x64
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x64.Build.0 = Release|x64
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x86.ActiveCfg = Release|x86
+               {3477E658-CB14-4D86-8909-F7448C17AC9A}.Release|x86.Build.0 = Release|x86
        EndGlobalSection
 EndGlobal