/* * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ /* @test * @bug 6206780 * @summary Test that all public unsynchronized methods of StringBuffer are either directly or indirectly synchronized */ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * TestSynchronization tests whether synchronized methods calls on an object * result in synchronized calls. Note that this may not test all cases desired. * It only tests whether some synchronization has occurred on the object during * the call chain, and can't tell whether the object was locked across all * operations that have been performed on the object. */ public class TestSynchronization { /** * Define parameters used in methods of StringBuffer - admittedly a bit of * hack but 'purpose-built' for StringBuffer. Something more general could * probably be developed if the test needs to be more widely adopted. * <p/> * boolean char char[] int double float long Object CharSequence String * StringBuffer StringBuilder * <p/> */ private static final boolean BOOLEAN_VAL = true; private static final char CHAR_VAL = 'x'; private static final char[] CHAR_ARRAY_VAL = {'c', 'h', 'a', 'r', 'a', 'r', 'r', 'a', 'y'}; private static final int INT_VAL = 1; private static final double DOUBLE_VAL = 1.0d; private static final float FLOAT_VAL = 1.0f; private static final long LONG_VAL = 1L; private static final Object OBJECT_VAL = new Object(); private static final String STRING_VAL = "String value"; private static final StringBuilder STRING_BUILDER_VAL = new StringBuilder("StringBuilder value"); private static final StringBuffer STRING_BUFFER_VAL = new StringBuffer("StringBuffer value"); private static final CharSequence[] CHAR_SEQUENCE_VAL = {STRING_VAL, STRING_BUILDER_VAL, STRING_BUFFER_VAL}; public static void main(String... args) throws Exception { // First, test the tester testClass(MyTestClass.class, /* * self-test */ true); // Finally, test StringBuffer testClass(StringBuffer.class, /* * self-test */ false); } /** * Test all the public, unsynchronized methods of the given class. If * isSelfTest is true, this is a self-test to ensure that the test program * itself is working correctly. Should help ensure correctness of this * program if it changes. * <p/> * @param aClass - the class to test * @param isSelfTest - true if this is the special self-test class * @throws SecurityException */ private static void testClass(Class<?> aClass, boolean isSelfTest) throws Exception { // Get all unsynchronized public methods via reflection. We don't need // to test synchronized methods. By definition. they are already doing // the right thing. List<Method> methods = Arrays.asList(aClass.getDeclaredMethods()); for (Method m : methods) { // skip synthetic methods, like default interface methods and lambdas if (m.isSynthetic()) { continue; } int modifiers = m.getModifiers(); if (Modifier.isPublic(modifiers) && !Modifier.isSynchronized(modifiers)) { try { testMethod(aClass, m); } catch (TestFailedException e) { if (isSelfTest) { String methodName = e.getMethod().getName(); switch (methodName) { case "should_pass": throw new RuntimeException( "Test failed: self-test failed. The 'should_pass' method did not pass the synchronization test. Check the test code."); case "should_fail": break; default: throw new RuntimeException( "Test failed: something is amiss with the test. A TestFailedException was generated on a call to " + methodName + " which we didn't expect to test in the first place."); } } else { throw new RuntimeException("Test failed: the method " + e.getMethod().toString() + " should be synchronized, but isn't."); } } } } } private static void invokeMethod(Class<?> aClass, final Method m, final Object[] args) throws TestFailedException, Exception { //System.out.println( "Invoking " + m.toString() + " with parameters " + Arrays.toString(args)); final Constructor<?> objConstructor; Object obj = null; objConstructor = aClass.getConstructor(String.class); obj = objConstructor.newInstance("LeftPalindrome-emordnilaP-thgiR"); // test method m for synchronization if (!isSynchronized(m, obj, args)) { throw new TestFailedException(m); } } private static void testMethod(Class<?> aClass, Method m) throws Exception { /* * Construct call with arguments of the correct type. Note that the * values are somewhat irrelevant. If the call actually succeeds, it * means we aren't synchronized and the test has failed. */ Class<?>[] pTypes = m.getParameterTypes(); List<Integer> charSequenceArgs = new ArrayList<>(); Object[] args = new Object[pTypes.length]; for (int i = 0; i < pTypes.length; i++) { // determine the type and create the corresponding actual argument Class<?> pType = pTypes[i]; if (pType.equals(boolean.class)) { args[i] = BOOLEAN_VAL; } else if (pType.equals(char.class)) { args[i] = CHAR_VAL; } else if (pType.equals(int.class)) { args[i] = INT_VAL; } else if (pType.equals(double.class)) { args[i] = DOUBLE_VAL; } else if (pType.equals(float.class)) { args[i] = FLOAT_VAL; } else if (pType.equals(long.class)) { args[i] = LONG_VAL; } else if (pType.equals(Object.class)) { args[i] = OBJECT_VAL; } else if (pType.equals(StringBuilder.class)) { args[i] = STRING_BUILDER_VAL; } else if (pType.equals(StringBuffer.class)) { args[i] = STRING_BUFFER_VAL; } else if (pType.equals(String.class)) { args[i] = STRING_VAL; } else if (pType.isArray() && pType.getComponentType().equals(char.class)) { args[i] = CHAR_ARRAY_VAL; } else if (pType.equals(CharSequence.class)) { charSequenceArgs.add(new Integer(i)); } else { throw new RuntimeException("Test Failed: not accounting for method call with parameter type of " + pType.getName() + " You must update the test."); } } /* * If there are no CharSequence args, we can simply invoke our method * and test it */ if (charSequenceArgs.isEmpty()) { invokeMethod(aClass, m, args); } else { /* * Iterate through the different CharSequence types and invoke the * method for each type. */ if (charSequenceArgs.size() > 1) { throw new RuntimeException("Test Failed: the test cannot handle a method with multiple CharSequence arguments. You must update the test to handle the method " + m.toString()); } for (int j = 0; j < CHAR_SEQUENCE_VAL.length; j++) { args[charSequenceArgs.get(0)] = CHAR_SEQUENCE_VAL[j]; invokeMethod(aClass, m, args); } } } @SuppressWarnings("serial") private static class TestFailedException extends Exception { final Method m; public Method getMethod() { return m; } public TestFailedException(Method m) { this.m = m; } } static class InvokeTask implements Runnable { private final Method m; private final Object target; private final Object[] args; InvokeTask(Method m, Object target, Object... args) { this.m = m; this.target = target; this.args = args; } @Override public void run() { try { m.invoke(target, args); } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { e.printStackTrace(); } } } /** * isSynchronized tests whether the given method is synchronized or not by * invoking it in a thread and testing the thread state after starting the * thread * <p/> * @param m the method to test * @param target the object the method is executed on * @param args the arguments passed to the method * @return true iff the method is synchronized */ private static boolean isSynchronized(Method m, Object target, Object... args) { Thread t = new Thread(new InvokeTask(m, target, args)); Boolean isSynchronized = null; synchronized (target) { t.start(); while (isSynchronized == null) { switch (t.getState()) { case NEW: case RUNNABLE: case WAITING: case TIMED_WAITING: Thread.yield(); break; case BLOCKED: isSynchronized = true; break; case TERMINATED: isSynchronized = false; break; } } } try { t.join(); } catch (InterruptedException ex) { ex.printStackTrace(); } return isSynchronized; } /* * This class is used to test the synchronization tester above. It has a * method, should_pass, that is unsynchronized but calls a synchronized * method. It has another method, should_fail, which isn't synchronized and * doesn't call a synchronized method. The former should pass and the latter * should fail. */ private static class MyTestClass { @SuppressWarnings("unused") public MyTestClass(String s) { } @SuppressWarnings("unused") public void should_pass() { // call sync method sync_shouldnt_be_tested(); } @SuppressWarnings("unused") public void should_fail() { } public synchronized void sync_shouldnt_be_tested() { } } }