1 /*
   2  * Copyright (c) 2012 Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /* @test
  25  * @bug 6206780
  26  * @summary Test that all public unsynchronized methods of StringBuffer are either directly or indirectly synchronized
  27  */
  28 import java.lang.reflect.Constructor;
  29 import java.lang.reflect.InvocationTargetException;
  30 import java.lang.reflect.Method;
  31 import java.lang.reflect.Modifier;
  32 import java.util.ArrayList;
  33 import java.util.Arrays;
  34 import java.util.List;
  35 
  36 /**
  37  * TestSynchronization tests whether synchronized methods calls on an object
  38  * result in synchronized calls. Note that this may not test all cases desired.
  39  * It only tests whether some synchronization has occurred on the object during
  40  * the call chain, and can't tell whether the object was locked across all
  41  * operations that have been performed on the object.
  42  */
  43 public class TestSynchronization {
  44 
  45     /**
  46      * Define parameters used in methods of StringBuffer - admittedly a bit of
  47      * hack but 'purpose-built' for StringBuffer. Something more general could
  48      * probably be developed if the test needs to be more widely adopted.
  49      * <p/>
  50      * boolean char char[] int double float long Object CharSequence String
  51      * StringBuffer StringBuilder
  52      * <p/>
  53      */
  54     private static final boolean BOOLEAN_VAL = true;
  55     private static final char CHAR_VAL = 'x';
  56     private static final char[] CHAR_ARRAY_VAL = {'c', 'h', 'a', 'r', 'a', 'r',
  57         'r', 'a', 'y'};
  58     private static final int INT_VAL = 1;
  59     private static final double DOUBLE_VAL = 1.0d;
  60     private static final float FLOAT_VAL = 1.0f;
  61     private static final long LONG_VAL = 1L;
  62     private static final Object OBJECT_VAL = new Object();
  63     private static final String STRING_VAL = "String value";
  64     private static final StringBuilder STRING_BUILDER_VAL =
  65             new StringBuilder("StringBuilder value");
  66     private static final StringBuffer STRING_BUFFER_VAL =
  67             new StringBuffer("StringBuffer value");
  68     private static final CharSequence[] CHAR_SEQUENCE_VAL = {STRING_VAL,
  69         STRING_BUILDER_VAL, STRING_BUFFER_VAL};
  70 
  71     public static void main(String... args) throws Exception {
  72         // First, test the tester
  73         testClass(MyTestClass.class, /*
  74                  * self-test
  75                  */ true);
  76         // Finally, test StringBuffer
  77         testClass(StringBuffer.class, /*
  78                  * self-test
  79                  */ false);
  80     }
  81 
  82     /**
  83      * Test all the public, unsynchronized methods of the given class. If
  84      * isSelfTest is true, this is a self-test to ensure that the test program
  85      * itself is working correctly. Should help ensure correctness of this
  86      * program if it changes.
  87      * <p/>
  88      * @param aClass - the class to test
  89      * @param isSelfTest - true if this is the special self-test class
  90      * @throws SecurityException
  91      */
  92     private static void testClass(Class<?> aClass, boolean isSelfTest) throws
  93             Exception {
  94         // Get all unsynchronized public methods via reflection.  We don't need
  95         // to test synchronized methods.  By definition. they are already doing
  96         // the right thing.
  97         List<Method> methods = Arrays.asList(aClass.getDeclaredMethods());
  98         for (Method m : methods) {
  99             int modifiers = m.getModifiers();
 100             if (Modifier.isPublic(modifiers)
 101                     && !Modifier.isSynchronized(modifiers)) {
 102                 try {
 103                     testMethod(aClass, m);
 104                 } catch (TestFailedException e) {
 105                     if (isSelfTest) {
 106                         String methodName = e.getMethod().getName();
 107                         switch (methodName) {
 108                             case "should_pass":
 109                                 throw new RuntimeException(
 110                                         "Test failed: self-test failed.  The 'should_pass' method did not pass the synchronization test. Check the test code.");
 111                             case "should_fail":
 112                                 break;
 113                             default:
 114                                 throw new RuntimeException(
 115                                         "Test failed: something is amiss with the test. A TestFailedException was generated on a call to "
 116                                         + methodName + " which we didn't expect to test in the first place.");
 117                         }
 118                     } else {
 119                         throw new RuntimeException("Test failed: the method "
 120                                 + e.getMethod().toString()
 121                                 + " should be synchronized, but isn't.");
 122                     }
 123                 }
 124             }
 125         }
 126     }
 127 
 128     private static void invokeMethod(Class<?> aClass, final Method m,
 129             final Object[] args) throws TestFailedException, Exception {
 130         //System.out.println( "Invoking " + m.toString() + " with parameters " + Arrays.toString(args));
 131         final Constructor<?> objConstructor;
 132         Object obj = null;
 133 
 134         objConstructor = aClass.getConstructor(String.class);
 135         obj = objConstructor.newInstance("LeftPalindrome-emordnilaP-thgiR");
 136 
 137         // test method m for synchronization
 138         if (!isSynchronized(m, obj, args)) {
 139             throw new TestFailedException(m);
 140         }
 141     }
 142 
 143     private static void testMethod(Class<?> aClass, Method m) throws
 144             Exception {
 145         /*
 146          * Construct call with arguments of the correct type. Note that the
 147          * values are somewhat irrelevant. If the call actually succeeds, it
 148          * means we aren't synchronized and the test has failed.
 149          */
 150         Class<?>[] pTypes = m.getParameterTypes();
 151         List<Integer> charSequenceArgs = new ArrayList<>();
 152         Object[] args = new Object[pTypes.length];
 153         for (int i = 0; i < pTypes.length; i++) {
 154             // determine the type and create the corresponding actual argument
 155             Class<?> pType = pTypes[i];
 156             if (pType.equals(boolean.class)) {
 157                 args[i] = BOOLEAN_VAL;
 158             } else if (pType.equals(char.class)) {
 159                 args[i] = CHAR_VAL;
 160             } else if (pType.equals(int.class)) {
 161                 args[i] = INT_VAL;
 162             } else if (pType.equals(double.class)) {
 163                 args[i] = DOUBLE_VAL;
 164             } else if (pType.equals(float.class)) {
 165                 args[i] = FLOAT_VAL;
 166             } else if (pType.equals(long.class)) {
 167                 args[i] = LONG_VAL;
 168             } else if (pType.equals(Object.class)) {
 169                 args[i] = OBJECT_VAL;
 170             } else if (pType.equals(StringBuilder.class)) {
 171                 args[i] = STRING_BUILDER_VAL;
 172             } else if (pType.equals(StringBuffer.class)) {
 173                 args[i] = STRING_BUFFER_VAL;
 174             } else if (pType.equals(String.class)) {
 175                 args[i] = STRING_VAL;
 176             } else if (pType.isArray() && pType.getComponentType().equals(char.class)) {
 177                 args[i] = CHAR_ARRAY_VAL;
 178             } else if (pType.equals(CharSequence.class)) {
 179                 charSequenceArgs.add(new Integer(i));
 180             } else {
 181                 throw new RuntimeException("Test Failed: not accounting for method call with parameter type of " + pType.getName() + " You must update the test.");
 182             }
 183         }
 184         /*
 185          * If there are no CharSequence args, we can simply invoke our method
 186          * and test it
 187          */
 188         if (charSequenceArgs.isEmpty()) {
 189             invokeMethod(aClass, m, args);
 190         } else {
 191             /*
 192              * Iterate through the different CharSequence types and invoke the
 193              * method for each type.
 194              */
 195             if (charSequenceArgs.size() > 1) {
 196                 throw new RuntimeException("Test Failed: the test cannot handle a method with multiple CharSequence arguments.  You must update the test to handle the method "
 197                         + m.toString());
 198             }
 199             for (int j = 0; j < CHAR_SEQUENCE_VAL.length; j++) {
 200                 args[charSequenceArgs.get(0)] = CHAR_SEQUENCE_VAL[j];
 201                 invokeMethod(aClass, m, args);
 202             }
 203         }
 204     }
 205 
 206     @SuppressWarnings("serial")
 207     private static class TestFailedException extends Exception {
 208 
 209         final Method m;
 210 
 211         public Method getMethod() {
 212             return m;
 213         }
 214 
 215         public TestFailedException(Method m) {
 216             this.m = m;
 217         }
 218     }
 219 
 220     static class InvokeTask implements Runnable {
 221 
 222         private final Method m;
 223         private final Object target;
 224         private final Object[] args;
 225 
 226         InvokeTask(Method m, Object target, Object... args) {
 227             this.m = m;
 228             this.target = target;
 229             this.args = args;
 230         }
 231 
 232         @Override
 233         public void run() {
 234             try {
 235                 m.invoke(target, args);
 236             } catch (IllegalAccessException | IllegalArgumentException |
 237                     InvocationTargetException e) {
 238                 e.printStackTrace();
 239             }
 240         }
 241     }
 242 
 243     /**
 244      * isSynchronized tests whether the given method is synchronized or not by
 245      * invoking it in a thread and testing the thread state after starting the
 246      * thread
 247      * <p/>
 248      * @param m the method to test
 249      * @param target the object the method is executed on
 250      * @param args the arguments passed to the method
 251      * @return true iff the method is synchronized
 252      */
 253     private static boolean isSynchronized(Method m, Object target,
 254             Object... args) {
 255         Thread t = new Thread(new InvokeTask(m, target, args));
 256 
 257         Boolean isSynchronized = null;
 258 
 259         synchronized (target) {
 260             t.start();
 261 
 262             while (isSynchronized == null) {
 263                 switch (t.getState()) {
 264                     case NEW:
 265                     case RUNNABLE:
 266                     case WAITING:
 267                     case TIMED_WAITING:
 268                         Thread.yield();
 269                         break;
 270                     case BLOCKED:
 271                         isSynchronized = true;
 272                         break;
 273                     case TERMINATED:
 274                         isSynchronized = false;
 275                         break;
 276                 }
 277             }
 278         }
 279 
 280         try {
 281             t.join();
 282         } catch (InterruptedException ex) {
 283             ex.printStackTrace();
 284         }
 285 
 286         return isSynchronized;
 287     }
 288 
 289     /*
 290      * This class is used to test the synchronization tester above. It has a
 291      * method, should_pass, that is unsynchronized but calls a synchronized
 292      * method. It has another method, should_fail, which isn't synchronized and
 293      * doesn't call a synchronized method. The former should pass and the latter
 294      * should fail.
 295      */
 296     private static class MyTestClass {
 297 
 298         @SuppressWarnings("unused")
 299         public MyTestClass(String s) {
 300         }
 301 
 302         @SuppressWarnings("unused")
 303         public void should_pass() {
 304             // call sync method
 305             sync_shouldnt_be_tested();
 306         }
 307 
 308         @SuppressWarnings("unused")
 309         public void should_fail() {
 310         }
 311 
 312         public synchronized void sync_shouldnt_be_tested() {
 313         }
 314     }
 315 }