559247: OQL method call improvements

Varargs calls and restrictions on classes/methods

Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=559247

Change-Id: Ie1555937af43181ca29142b964a8f1c808101f26
diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/Messages.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/Messages.java
index 6e2d5e6..a29997b 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/Messages.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/Messages.java
@@ -44,6 +44,7 @@
     public static String IndexWriter_StoredError;

     public static String IndexWriter_StoredException;

     public static String MethodCallExpression_Error_MethodNotFound;

+    public static String MethodCallExpression_Error_MethodProhibited;

     public static String MultiplePathsFromGCRootsComputerImpl_FindingPaths;

     public static String SnapshotFactoryImpl_ClassIDNotFound;

     public static String SnapshotFactoryImpl_ClassImplNotFound;

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/messages.properties b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/messages.properties
index 7aad84c..8c4ce8c 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/messages.properties
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/messages.properties
@@ -37,6 +37,7 @@
 IndexWriter_StoredError=stored error from writer

 IndexWriter_StoredException=stored IO exception from writer

 MethodCallExpression_Error_MethodNotFound=Method {0}({1}) not found in object {2} of type {3}

+MethodCallExpression_Error_MethodProhibited=Method {0} prohibited by method filter {1} from {2}

 MultiplePathsFromGCRootsComputerImpl_FindingPaths=Finding paths

 SnapshotFactoryImpl_EmptyOutbounds=Empty outbounds for index {0} address {1} type {2}

 SnapshotFactoryImpl_Error_NoParserRegistered=No parser registered for file ''{0}''

@@ -127,4 +128,4 @@
 OQLParser_Missing_return_statement_in_function=Missing return statement in function

 

 ThreadStackHelper_InvalidThread=Invalid thread {0}: {1}

-ThreadStackHelper_InvalidThreadLocal=Invalid thread local {0} for thread {1} : {2}
\ No newline at end of file
+ThreadStackHelper_InvalidThreadLocal=Invalid thread local {0} for thread {1} : {2}

diff --git a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/compiler/MethodCallExpression.java b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/compiler/MethodCallExpression.java
index e58d53e..4ce1dd0 100644
--- a/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/compiler/MethodCallExpression.java
+++ b/plugins/org.eclipse.mat.parser/src/org/eclipse/mat/parser/internal/oql/compiler/MethodCallExpression.java
@@ -1,5 +1,5 @@
 /*******************************************************************************

- * Copyright (c) 2008, 2012 SAP AG and IBM Corporation.

+ * Copyright (c) 2008, 2019 SAP AG and IBM Corporation.

  * All rights reserved. This program and the accompanying materials

  * are made available under the terms of the Eclipse Public License v1.0

  * which accompanies this distribution, and is available at

@@ -11,9 +11,12 @@
  *******************************************************************************/

 package org.eclipse.mat.parser.internal.oql.compiler;

 

+import java.lang.reflect.Array;

 import java.lang.reflect.InvocationTargetException;

 import java.lang.reflect.Method;

 import java.lang.reflect.Modifier;

+import java.security.AccessControlException;

+import java.text.MessageFormat;

 import java.util.ArrayList;

 import java.util.Arrays;

 import java.util.Iterator;

@@ -64,7 +67,7 @@
 

         /*

          * Finding the right method is tricky as the arguments have already been boxed.

-         * E.g. consider overloaded methods 

+         * E.g. consider overloaded methods

          * remove(int)

          * remove(Object)

          * with argument Integer(1).

@@ -73,7 +76,9 @@
         final Class<? extends Object> subjectClass = subject.getClass();

         Method[] methods;

         methods = subjectClass.getMethods();

-        if (!Modifier.isPublic(subjectClass.getModifiers()))

+        // If we checkMethodAccess then an interface method may be allowed even if the class method isn't

+        boolean alwaysInterfaces = true;

+        if (!Modifier.isPublic(subjectClass.getModifiers()) || alwaysInterfaces)

         {

             // Non-public class public methods are only accessible via

             // interfaces. For example java.util.Arrays$ArrayList.get()

@@ -97,6 +102,17 @@
         {

             firstChoiceMethods(extraMethods, subjectClass, arguments);

         }

+        // Add static methods if a class object is passed

+        if (subject instanceof Class)

+        {

+            for (Method m : ((Class<?>)subject).getMethods())

+            {

+                if (Modifier.isStatic(m.getModifiers()) && Modifier.isPublic(m.getModifiers()))

+                {

+                    extraMethods.add(m);

+                }

+            }

+        }

         if (extraMethods.size() > 0)

         {

             // Then add the original methods

@@ -105,12 +121,14 @@
             extraMethods = new ArrayList<Method>(new LinkedHashSet<Method>(extraMethods));

             methods = extraMethods.toArray(new Method[extraMethods.size()]);

         }

+        SnapshotException deferred = null;

         nextMethod: for (int ii = 0; ii < methods.length; ii++)

         {

             if (methods[ii].getName().equals(this.name))

             {

                 Class<?>[] parameterTypes = methods[ii].getParameterTypes();

-                if (parameterTypes.length == arguments.length)

+                if (parameterTypes.length == arguments.length ||

+                    methods[ii].isVarArgs() && parameterTypes.length < arguments.length)

                 {

                     Object savedArgs[] = null;

                     for (int jj = 0; jj < arguments.length; jj++)

@@ -119,7 +137,8 @@
                         {

                             arguments[jj] = null;

                         }

-                        if (arguments[jj] != null && !isConvertible(parameterTypes[jj], arguments[jj]))

+                        if (!(methods[ii].isVarArgs() && jj >= parameterTypes.length - 1) &&

+                            arguments[jj] != null && !isConvertible(parameterTypes[jj], arguments[jj]))

                         {

                             // we do some special magic here...

                             if (parameterTypes[jj].isAssignableFrom(Pattern.class))

@@ -148,7 +167,17 @@
 

                     try

                     {

-                        return methods[ii].invoke(subject, arguments);

+                        checkMethodAccess(methods[ii]);

+                        if (methods[ii].isVarArgs())

+                        {

+                            Object args2[] = convertVarArgs(parameterTypes, arguments);

+                            if (args2 != null)

+                                return methods[ii].invoke(subject, args2);

+                        }

+                        else

+                        {

+                            return methods[ii].invoke(subject, arguments);

+                        }

                     }

                     catch (IllegalArgumentException e)

                     {

@@ -162,15 +191,24 @@
                     {

                         throw new SnapshotException(e);

                     }

+                    catch (SecurityException e)

+                    {

+                        // Perhaps another method works

+                        deferred = new SnapshotException(methods[ii].toString(), e);

+                    }

                 }

             }

         }

+        if (deferred != null)

+            throw deferred;

 

         StringBuilder argTypes = new StringBuilder();

         for (Object arg : arguments)

         {

             if (argTypes.length() > 0)

                 argTypes.append(", "); //$NON-NLS-1$

+            if (arg == ConstantExpression.NULL)

+                arg = null;

             argTypes.append(arg != null ? unboxedType(arg.getClass()).getName() : null);

         }

         throw new SnapshotException(MessageUtil.format(Messages.MethodCallExpression_Error_MethodNotFound,

@@ -191,10 +229,12 @@
         boolean unbox = false;

         for (Object args : arguments)

         {

+            if  (args == ConstantExpression.NULL)

+                args = null;

             if (args != null)

                 argumentTypes1[i] = args.getClass();

             argumentTypes2[i] = unboxedType(argumentTypes1[i]);

-            if (argumentTypes2 != argumentTypes1)

+            if (argumentTypes2[i] != argumentTypes1[i])

                 unbox = true;

             i++;

         }

@@ -254,11 +294,11 @@
         }

         else if (arg == Double.class)

         {

-            arg =double.class;

+            arg = double.class;

         }

         return arg;

     }

-    

+

     /**

      * Can method invocation convert the argument via unboxing/widening conversion?

      */

@@ -271,7 +311,7 @@
                         parameterType == boolean.class || parameterType == Boolean.class))

             return true;

         if (argumentType == Byte.class && (

-                        parameterType == byte.class || parameterType == Byte.class 

+                        parameterType == byte.class || parameterType == Byte.class

                         || parameterType == short.class || parameterType == Short.class

                         || parameterType == int.class || parameterType == Integer.class

                         || parameterType == long.class || parameterType == Long.class

@@ -313,6 +353,91 @@
         return false;

     }

 

+    /**

+     * Check whether to allow this method call.

+     * Syntax for mat.oqlmethodFilter

+     * com.package1.* only classes/methods in this package

+     * com.package1.** classes/methods in this package or subpackages

+     * com.package* classes/methods starting with the prefix

+     * com.*#methodname prefix before * and suffix after * must match

+     * ! means not allowed

+     * ; separates components

+     * Throws an exception if not allowed.

+     */

+    private void checkMethodAccess(Method method)

+    {

+        /*

+         * Default allows a few safe methods.

+         */

+        String match = System.getProperty("mat.oql.methodFilter", //$NON-NLS-1$

+                        "!java.lang.ClassLoader#*;!java.lang.Compiler#*;!java.lang.Process*;!java.lang.Runtime#*;!java.lang.SecurityManager#*;!java.lang.System#*;!java.lang.Thread*;java.lang.*;java.util.*;org.eclipse.mat.snapshot.*;org.eclipse.mat.snapshot.model.*;!*"); //$NON-NLS-1$

+        String nm = method.getDeclaringClass().getName()+"#"+method.getName(); //$NON-NLS-1$

+        for (String pt : match.split(";")) //$NON-NLS-1$

+        {

+            boolean not = pt.startsWith("!"); //$NON-NLS-1$

+            if (not)

+                pt = pt.substring(1);

+            boolean m;

+            if (pt.endsWith(".**")) //$NON-NLS-1$

+                m = nm.startsWith(pt.substring(0, pt.length() - 2));

+            else if (pt.endsWith(".*")) //$NON-NLS-1$

+                m = nm.startsWith(pt.substring(0, pt.length() - 1))

+                && !nm.substring(pt.length() - 1).contains("."); //$NON-NLS-1$

+            else if (pt.endsWith("*")) //$NON-NLS-1$

+                m = nm.startsWith(pt.substring(0, pt.length() - 1));

+            else if (pt.contains("*")) //$NON-NLS-1$)

+            {

+                int i = pt.indexOf("*"); //$NON-NLS-1$)

+                m = nm.startsWith(pt.substring(0, i)) && nm.endsWith(pt.substring(i + 1));

+            }

+            else

+                m = nm.equals(pt);

+            if (not && m)

+                throw new AccessControlException(MessageFormat.format(Messages.MethodCallExpression_Error_MethodProhibited, nm, "!" + pt, match)); //$NON-NLS-1$

+            if (m)

+                break;

+        }

+    }

+

+    /**

+     * Collect varargs arguments into an array.

+     * @param parameterTypes

+     * @param arguments

+     * @return null if the arguments won't convert

+     */

+    private Object[] convertVarArgs(Class<?>[] parameterTypes, Object arguments[])

+    {

+        /*

+         *  If there is one var args argument and it looks like it matches the object array,

+         *  then don't wrap it.

+         */

+        if (!(arguments.length == parameterTypes.length

+                        && (arguments[arguments.length - 1] == null || parameterTypes[parameterTypes.length - 1]

+                                        .isAssignableFrom(arguments[arguments.length - 1].getClass()))))

+        {

+            Object args2[] = new Object[parameterTypes.length];

+            for (int i = 0; i < parameterTypes.length - 1; ++i)

+            {

+                args2[i] = arguments[i];

+            }

+            Class<?> componentType = parameterTypes[parameterTypes.length - 1].getComponentType();

+            Object varargs[] = (Object[]) Array.newInstance(componentType,

+                            arguments.length - (parameterTypes.length - 1));

+            args2[parameterTypes.length - 1] = varargs;

+            for (int i = parameterTypes.length - 1; i < arguments.length; ++i)

+            {

+                if (arguments[i] != null && !componentType.isAssignableFrom(arguments[i].getClass()))

+                    return null;

+                varargs[i - (parameterTypes.length - 1)] = arguments[i];

+            }

+            return args2;

+        }

+        else

+        {

+            return arguments;

+        }

+    }

+

     @Override

     public boolean isContextDependent(EvaluationContext ctx)

     {

diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/OQLTest.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/OQLTest.java
index 963319c..65c07a5 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/OQLTest.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/OQLTest.java
@@ -12,22 +12,24 @@
  *******************************************************************************/

 package org.eclipse.mat.tests.snapshot;

 

+import static org.hamcrest.CoreMatchers.isA;

+import static org.hamcrest.Matchers.anyOf;

 import static org.hamcrest.Matchers.equalTo;

-import static org.hamcrest.Matchers.nullValue;

 import static org.hamcrest.Matchers.greaterThan;

 import static org.hamcrest.Matchers.greaterThanOrEqualTo;

-import static org.hamcrest.Matchers.lessThanOrEqualTo;

+import static org.hamcrest.Matchers.hasItems;

 import static org.hamcrest.Matchers.instanceOf;

-import static org.hamcrest.Matchers.anyOf;

+import static org.hamcrest.Matchers.lessThanOrEqualTo;

+import static org.hamcrest.Matchers.nullValue;

 import static org.hamcrest.core.StringContains.containsString;

 import static org.junit.Assert.assertArrayEquals;

 import static org.junit.Assert.assertEquals;

 import static org.junit.Assert.assertNotNull;

+import static org.junit.Assert.assertNull;

 import static org.junit.Assert.assertThat;

 import static org.junit.Assert.assertTrue;

 

 import java.util.ArrayList;

-import java.util.Arrays;

 import java.util.Collection;

 import java.util.HashSet;

 import java.util.List;

@@ -48,10 +50,11 @@
 import org.eclipse.mat.snapshot.model.IClass;

 import org.eclipse.mat.snapshot.model.IObject;

 import org.eclipse.mat.tests.TestSnapshots;

-import org.eclipse.mat.util.IProgressListener;

 import org.eclipse.mat.util.MessageUtil;

 import org.eclipse.mat.util.VoidProgressListener;

+import org.junit.Rule;

 import org.junit.Test;

+import org.junit.rules.ExpectedException;

 

 @SuppressWarnings("nls")

 public class OQLTest

@@ -105,15 +108,15 @@
     {

         Object result = execute("select s.@objectId, s.value, s.count, s.offset from java.lang.String s");

 

-        assert result instanceof IResultTable : "'SELECT x, y, z' must return a result of type IResultTable";

+        assertThat("'SELECT x, y, z' must return a result of type IResultTable", result, instanceOf(IResultTable.class));

         IResultTable table = (IResultTable) result;

-        assert table.getRowCount() == 492 : "492 objects of type java.lang.String expected";

-        assert table.getColumns().length == 4 : "4 columns expected";

+        assertThat("492 objects of type java.lang.String expected", table.getRowCount(), equalTo(492));

+        assertThat("4 columns expected", table.getColumns().length, equalTo(4));

 

         // check if context is available

         Object row = table.getRow(0);

         int objectId = (Integer) table.getColumnValue(row, 0);

-        assert objectId == table.getContext(row).getObjectId() : "Result must return underlying object id as context";

+        assertThat("Result must return underlying object id as context", objectId, equalTo(table.getContext(row).getObjectId()));

         checkGetOQL(table);

     }

 

@@ -150,13 +153,13 @@
     public void testSelectObjects() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("select objects dominators(s) from objects 0x1295e2f8 s");

-        assert objectIds.length == 7;

+        assertThat(objectIds.length, equalTo(7));

 

         objectIds = (int[]) execute("select objects dominators(s) from objects ${snapshot}.getClassesByName(\"java.lang.String\", false) s");

-        assert objectIds.length == 2;

+        assertThat(objectIds.length, equalTo(2));

 

         objectIds = (int[]) execute("select objects dominators(s) from objects (select * from java.lang.String) s");

-        assert objectIds.length == 465;

+        assertThat(objectIds.length, equalTo(465));

     }

 

     @Test

@@ -250,12 +253,12 @@
     public void testUnion() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("select * from objects 0 union (select * from objects 1)");

-        assert objectIds.length == 2;

+        assertThat(objectIds.length, equalTo(2));

 

         IResultTable table = (IResultTable) execute("select toString(s) from objects 0x17c180b8 s union (select toHex(s.@objectAddress) from objects 1 s)");

-        assert table.getRowCount() == 2;

-        assert "main".equals(table.getColumnValue(table.getRow(0), 0));

-        assert "0x12832b50".equals(table.getColumnValue(table.getRow(1), 0));

+        assertThat(table.getRowCount(), equalTo(2));

+        assertThat(table.getColumnValue(table.getRow(0), 0), equalTo((Object)"main"));

+        assertThat(table.getColumnValue(table.getRow(1), 0), equalTo((Object)"0x12832b50"));

         checkGetOQL(table);

     }

 

@@ -445,25 +448,25 @@
     {

         Object result = execute("select * from \"java.lang.*\"");

 

-        assert result instanceof int[] : "'SELECT *' must return a result of type int[]";

+        assertThat("'SELECT *' must return a result of type int[]", result, instanceOf(int[].class));

         int[] objectIds = (int[]) result;

-        assert objectIds.length == 1198 : "1198 objects of type java.lang.* expected";

+        assertThat("1198 objects of type java.lang.* expected", objectIds.length, equalTo(1198));

     }

 

     @Test

     public void testFromAddress() throws SnapshotException

     {

         Object result = execute("select * from objects 0x0");

-        assert result instanceof int[] : "'SELECT *' must return a result of type int[]";

+        assertThat("'SELECT *' must return a result of type int[]", result, instanceOf(int[].class));

         int[] objectIds = (int[]) result;

-        assert objectIds.length == 1 : "one object matching 0x0 expected";

+        assertThat("one object matching 0x0 expected", objectIds.length, equalTo(1));

     }

 

     @Test

     public void testFromObject() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("SELECT * FROM ${snapshot}.getClassesByName(\"java.lang.ref.Reference\", true)");

-        assert objectIds.length == 21 : "expected 21 instanceof of java.lang.ref.Reference";

+        assertThat("expected 21 instanceof of java.lang.ref.Reference", objectIds.length, equalTo(21));

     }

 

     @Test

@@ -540,7 +543,7 @@
                         + "FROM java.lang.Class c " //

                         + "WHERE c implements org.eclipse.mat.snapshot.model.IClass )";

         int[] objectIds = (int[]) execute(oql);

-        assert objectIds.length == 2058 : "expected 2058 instances of IClass";

+        assertThat("expected 2058 instances of IClass", objectIds.length, equalTo(2058));

     }

 

     @Test

@@ -559,7 +562,7 @@
     public void testFromInstanceOf() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("SELECT * FROM INSTANCEOF java.lang.ref.Reference");

-        assert objectIds.length == 21 : "expected 21 instances of java.lang.ref.Reference";

+        assertThat("expected 21 instances of java.lang.ref.Reference", objectIds.length, equalTo(21));

 

         ISnapshot snapshot = TestSnapshots.getSnapshot(TestSnapshots.SUN_JDK5_64BIT, false);

 

@@ -567,45 +570,49 @@
         assertNotNull(rClasses);

         Set<IClass> classes = new HashSet<IClass>(rClasses);

         for (int id : objectIds)

+        {

+            assertThat(MessageUtil.format("Object {0} not an instance of java.lang.ref.Reference ", id), 

+                            classes, hasItems(snapshot.getClassOf(id)));

             assert classes.contains(snapshot.getClassOf(id)) : MessageUtil.format(

                             "Object {0} not an instance of java.lang.ref.Reference ", id);

+        }

     }

 

     @Test

     public void testWhereRelationalOperators() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count > 51");

-        assert objectIds.length == 16;

+        assertThat(objectIds.length, equalTo(16));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count >= 51");

-        assert objectIds.length == 19;

+        assertThat(objectIds.length, equalTo(19));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count < 51");

-        assert objectIds.length == 473;

+        assertThat(objectIds.length, equalTo(473));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count <= 51");

-        assert objectIds.length == 476;

+        assertThat(objectIds.length, equalTo(476));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE toString(s) LIKE \"java.*\"");

-        assert objectIds.length == 27;

+        assertThat(objectIds.length, equalTo(27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE toString(s) NOT LIKE \"java.*\"");

-        assert objectIds.length == 492 - 27;

+        assertThat(objectIds.length, equalTo(492 - 27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.value IN dominators(s)");

-        assert objectIds.length == 492 - 27;

+        assertThat(objectIds.length, equalTo(492 - 27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.value NOT IN dominators(s)");

-        assert objectIds.length == 27;

+        assertThat(objectIds.length, equalTo(27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE toString(s) = \"file.separator\"");

-        assert objectIds.length == 1;

+        assertThat(objectIds.length, equalTo(1));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count > 100 AND s.@retainedHeapSize > s.@usedHeapSize");

-        assert objectIds.length == 6;

+        assertThat(objectIds.length, equalTo(6));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count > 1000 OR s.value.@length > 1000");

-        assert objectIds.length == 3;

+        assertThat(objectIds.length, equalTo(3));

     }

 

     /**

@@ -634,16 +641,16 @@
     public void testWhereArithmetic() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count < s.value.@length * 0.5");

-        assert objectIds.length == 16;

+        assertThat(objectIds.length, equalTo(16));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count < s.value.@length / 2");

-        assert objectIds.length == 16;

+        assertThat(objectIds.length, equalTo(16));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count < s.value.@length - 20");

-        assert objectIds.length == 16;

+        assertThat(objectIds.length, equalTo(16));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.count + 20 < s.value.@length");

-        assert objectIds.length == 16;

+        assertThat(objectIds.length, equalTo(16));

     }

 

     @Test

@@ -661,19 +668,19 @@
     public void testWhereLiterals() throws SnapshotException

     {

         int[] objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE ( s.count > 1000 ) = true");

-        assert objectIds.length == 3;

+        assertThat(objectIds.length, equalTo(3));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE dominators(s).size() = 0");

-        assert objectIds.length == 27;

+        assertThat(objectIds.length, equalTo(27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE dominators(s).length = 0");

-        assert objectIds.length == 27;

+        assertThat(objectIds.length, equalTo(27));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.String s WHERE s.@retainedHeapSize > 1024L");

-        assert objectIds.length == 4;

+        assertThat(objectIds.length, equalTo(4));

 

         objectIds = (int[]) execute("SELECT * FROM java.lang.Thread s WHERE s.@GCRootInfo != null");

-        assert objectIds.length == 4;

+        assertThat(objectIds.length, equalTo(4));

     }

 

     @Test

@@ -683,30 +690,30 @@
         int objectId = ((int[]) execute("select * from objects 0x17c38b80 s"))[0];

 

         IResultTable result = (IResultTable) execute("select toString(s) from objects 0x17c38b80 s");

-        assert "little".equals(result.getColumnValue(result.getRow(0), 0));

+        assertThat(result.getColumnValue(result.getRow(0), 0), equalTo((Object)"little"));

 

         result = (IResultTable) execute("select toHex(s.@objectAddress) from objects 0x17c38b80 s");

-        assert "0x17c38b80".equals(result.getColumnValue(result.getRow(0), 0));

+        assertThat(result.getColumnValue(result.getRow(0), 0), equalTo((Object)"0x17c38b80"));

 

         result = (IResultTable) execute("select dominators(s).length from objects 0x17c38b80 s");

-        assert Integer.valueOf(1).equals(result.getColumnValue(result.getRow(0), 0));

+        assertThat(result.getColumnValue(result.getRow(0), 0), equalTo((Object)1));

 

         result = (IResultTable) execute("select outbounds(s) from objects 0x17c38b80 s");

         int[] outbounds = (int[]) result.getColumnValue(result.getRow(0), 0);

-        assert outbounds.length == 2;

-        assert Arrays.toString(snapshot.getOutboundReferentIds(objectId)).equals(Arrays.toString(outbounds));

+        assertThat(outbounds.length, equalTo(2));

+        assertThat(outbounds, equalTo(snapshot.getOutboundReferentIds(objectId)));

 

         result = (IResultTable) execute("select inbounds(s) from objects 0x17c38b80 s");

         int[] inbounds = (int[]) result.getColumnValue(result.getRow(0), 0);

-        assert inbounds.length == 1;

-        assert Arrays.toString(snapshot.getInboundRefererIds(objectId)).equals(Arrays.toString(inbounds));

+        assertThat(inbounds.length, equalTo(1));

+        assertThat(inbounds, equalTo(snapshot.getInboundRefererIds(objectId)));

 

         result = (IResultTable) execute("select classof(s) from objects 0x17c38b80 s");

         IClass obj = (IClass) result.getColumnValue(result.getRow(0), 0);

-        assert "java.lang.String".equals(obj.getName());

+        assertThat(obj.getName(), equalTo("java.lang.String"));

 

         result = (IResultTable) execute("select dominatorof(s) from objects 0x17c38b80 s");

-        assert result.getColumnValue(result.getRow(0), 0) != null;

+        assertNotNull(result.getColumnValue(result.getRow(0), 0));

     }

 

     @Test(expected = SnapshotException.class)

@@ -780,7 +787,10 @@
         IOQLQuery q1 = SnapshotFactory.createQuery(queryString);

         String s = q1.toString();

         IOQLQuery q2 = SnapshotFactory.createQuery(s);

-        execute(s);

+        String s2 = q2.toString();

+        assertEquals(s, s2);

+        IResultTable r = (IResultTable)execute(s);

+        checkGetOQL(r);

     }

 

     /**

@@ -791,7 +801,7 @@
     public void testAndClause() throws SnapshotException

     {

         IResultTable res = (IResultTable)execute("SELECT s.value FROM INSTANCEOF java.lang.Number s WHERE (s.value and true)");

-        assert 3 ==  res.getRowCount() : "3 non-zero Numbers expected";

+        assertThat("3 non-zero Numbers expected", res.getRowCount(), equalTo(3));

     }

 

     @Test

@@ -1330,6 +1340,7 @@
         assertThat(irt.getRowCount(), equalTo(1));

         assertThat(irt.getColumns().length, equalTo(11));

         Object row = irt.getRow(0);

+        assertNotNull(row);

         checkGetOQL(irt);

     }

 

@@ -1339,6 +1350,7 @@
         assertThat(irt.getRowCount(), equalTo(1));

         assertThat(irt.getColumns().length, equalTo(11));

         Object row = irt.getRow(0);

+        assertNotNull(row);

         checkGetOQL(irt);

     }

 

@@ -1350,6 +1362,7 @@
         assertThat(irt.getRowCount(), equalTo(1));

         assertThat(irt.getColumns().length, equalTo(11));

         Object row = irt.getRow(0);

+        assertNotNull(row);

         checkGetOQL(irt);

     }

 

@@ -1371,6 +1384,7 @@
         assertThat(irt.getRowCount(), equalTo(1));

         assertThat(irt.getColumns().length, equalTo(11));

         Object row = irt.getRow(0);

+        assertNotNull(row);

         checkGetOQL(irt);

     }

 

@@ -1865,6 +1879,141 @@
         assertThat("Multiple objects expected", objs.length, greaterThanOrEqualTo(2));

     }

 

+    @Rule

+    public ExpectedException expectedException = ExpectedException.none();

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallClassLoader() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.ClassLoader\").getSystemClassLoader() FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallCompiler() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.Compiler\").disable() FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallProcess() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.ProcessBuilder\").getConstructor(s.@class.forName(\"[Ljava.lang.String;\")).newInstance(\"calc\") FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallRuntime() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.Runtime\").getRuntime() FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallSecurityManager() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.SecurityManager\").newInstance().checkExec(\"calc\") FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallSystem() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.System\").currentTimeMillis() FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls disallowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallThread() throws SnapshotException

+    {

+        expectedException.expectCause(isA(java.security.AccessControlException.class));

+        Object o = execute("SELECT s.@class.forName(\"java.lang.Thread\").activeCount() FROM OBJECTS 1 s");

+        assertNull(o);

+    }

+

+    /**

+     * Test method calls allowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallInteger() throws SnapshotException

+    {

+        Object result = execute("SELECT eval(123).intValue() FROM OBJECTS 1 s");

+        assertThat(result, instanceOf(IResultTable.class));

+        IResultTable table = (IResultTable) result;

+        Object row = table.getRow(1);

+        assertThat(table.getColumnValue(row, 0), equalTo((Object)123));

+    }

+

+    /**

+     * Test method calls allowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallVarArgs1() throws SnapshotException

+    {

+        Object result = execute("SELECT s.@class.forName(\"java.util.Arrays\").asList(1) FROM OBJECTS 1 s");

+        assertThat(result, instanceOf(IResultTable.class));

+        IResultTable table = (IResultTable) result;

+        Object row = table.getRow(0);

+        Object item = table.getColumnValue(row, 0);

+        assertThat(item, instanceOf(List.class));

+        List<?> al = (List<?>)item;

+        assertThat(al.size(), equalTo(1));

+    }

+

+    /**

+     * Test method calls allowed.

+     * @throws SnapshotException

+     */

+    @Test

+    public void testMethodCallVarArgs5() throws SnapshotException

+    {

+        Object result = execute("SELECT s.@class.forName(\"java.util.Arrays\").asList(1,2,3,4,5) FROM OBJECTS 1 s");

+        assertThat(result, instanceOf(IResultTable.class));

+        IResultTable table = (IResultTable) result;

+        Object row = table.getRow(0);

+        Object item = table.getColumnValue(row, 0);

+        assertThat(item, instanceOf(List.class));

+        List<?> al = (List<?>)item;

+        assertThat(al.size(), equalTo(5));

+    }

+

     // //////////////////////////////////////////////////////////////

     // internal helpers

     // //////////////////////////////////////////////////////////////