8260695: The java.awt.color.ICC_Profile#getData/getData(int) are not thread safe

Reviewed-by: azvegint, aivanov
This commit is contained in:
Sergey Bylokhov 2021-02-22 22:34:53 +00:00
parent a30fb4fc68
commit a7e2e80ff4
7 changed files with 185 additions and 157 deletions

View File

@ -1131,22 +1131,8 @@ public class ICC_Profile implements Serializable {
* @see #setData(int, byte[])
*/
public byte[] getData() {
int profileSize;
byte[] profileData;
activate();
PCMM mdl = CMSManager.getModule();
/* get the number of bytes needed for this profile */
profileSize = mdl.getProfileSize(cmmProfile);
profileData = new byte [profileSize];
/* get the data for the profile */
mdl.getProfileData(cmmProfile, profileData);
return profileData;
return CMSManager.getModule().getProfileData(cmmProfile);
}
/**
@ -1170,25 +1156,12 @@ public class ICC_Profile implements Serializable {
}
static byte[] getData(Profile p, int tagSignature) {
int tagSize;
byte[] tagData;
private static byte[] getData(Profile p, int tagSignature) {
try {
PCMM mdl = CMSManager.getModule();
/* get the number of bytes needed for this tag */
tagSize = mdl.getTagSize(p, tagSignature);
tagData = new byte[tagSize]; /* get an array for the tag */
/* get the tag's data */
mdl.getTagData(p, tagSignature, tagData);
} catch(CMMException c) {
tagData = null;
return CMSManager.getModule().getTagData(p, tagSignature);
} catch (CMMException c) {
return null;
}
return tagData;
}
/**

View File

@ -92,33 +92,19 @@ public final class CMSManager {
return p;
}
public int getProfileSize(Profile p) {
System.err.print(cName + ".getProfileSize(ID=" + p + ")");
int size = tcmm.getProfileSize(p);
System.err.println("=" + size);
return size;
}
public void getProfileData(Profile p, byte[] data) {
public byte[] getProfileData(Profile p) {
System.err.print(cName + ".getProfileData(ID=" + p + ") ");
byte[] data = tcmm.getProfileData(p);
System.err.println("requested " + data.length + " byte(s)");
tcmm.getProfileData(p, data);
return data;
}
public int getTagSize(Profile p, int tagSignature) {
System.err.printf(cName + ".getTagSize(ID=%x, TagSig=%s)",
p, signatureToString(tagSignature));
int size = tcmm.getTagSize(p, tagSignature);
System.err.println("=" + size);
return size;
}
public void getTagData(Profile p, int tagSignature,
byte[] data) {
public byte[] getTagData(Profile p, int tagSignature) {
System.err.printf(cName + ".getTagData(ID=%x, TagSig=%s)",
p, signatureToString(tagSignature));
byte[] data = tcmm.getTagData(p, tagSignature);
System.err.println(" requested " + data.length + " byte(s)");
tcmm.getTagData(p, tagSignature, data);
return data;
}
public void setTagData(Profile p, int tagSignature,

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2006, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2006, 2021, 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
@ -33,10 +33,8 @@ public interface PCMM {
/* methods invoked from ICC_Profile */
public Profile loadProfile(byte[] data);
public int getProfileSize(Profile p);
public void getProfileData(Profile p, byte[] data);
public void getTagData(Profile p, int tagSignature, byte[] data);
public int getTagSize(Profile p, int tagSignature);
public byte[] getProfileData(Profile p);
public byte[] getTagData(Profile p, int tagSignature);
public void setTagData(Profile p, int tagSignature, byte[] data);
/* methods for creating ColorTransforms */

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2007, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2007, 2021, 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
@ -58,45 +58,23 @@ public class LCMS implements PCMM {
}
@Override
public int getProfileSize(final Profile p) {
synchronized (p) {
return getProfileSizeNative(getLcmsProfile(p).getLcmsPtr());
public byte[] getProfileData(final Profile p) {
LCMSProfile lcmsProfile = getLcmsProfile(p);
synchronized (lcmsProfile) {
return getProfileDataNative(lcmsProfile.getLcmsPtr());
}
}
private native int getProfileSizeNative(long ptr);
@Override
public void getProfileData(final Profile p, byte[] data) {
synchronized (p) {
getProfileDataNative(getLcmsProfile(p).getLcmsPtr(), data);
}
}
private native void getProfileDataNative(long ptr, byte[] data);
@Override
public int getTagSize(Profile p, int tagSignature) {
final LCMSProfile profile = getLcmsProfile(p);
synchronized (profile) {
TagData t = profile.getTag(tagSignature);
return t == null ? 0 : t.getSize();
}
}
private native byte[] getProfileDataNative(long ptr);
static native byte[] getTagNative(long profileID, int signature);
@Override
public void getTagData(Profile p, int tagSignature, byte[] data)
{
final LCMSProfile profile = getLcmsProfile(p);
synchronized (profile) {
TagData t = profile.getTag(tagSignature);
if (t != null) {
t.copyDataTo(data);
}
public byte[] getTagData(Profile p, int tagSignature) {
final LCMSProfile lcmsProfile = getLcmsProfile(p);
synchronized (lcmsProfile) {
TagData t = lcmsProfile.getTag(tagSignature);
return t != null ? t.getData() : null;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2021, 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
@ -25,9 +25,8 @@
package sun.java2d.cmm.lcms;
import java.awt.color.CMMException;
import java.util.Arrays;
import java.util.HashMap;
import sun.java2d.cmm.Profile;
final class LCMSProfile extends Profile {
@ -55,55 +54,40 @@ final class LCMSProfile extends Profile {
tagCache.clear();
}
static class TagCache {
final LCMSProfile profile;
private HashMap<Integer, TagData> tags;
private static final class TagCache {
private final LCMSProfile profile;
private final HashMap<Integer, TagData> tags = new HashMap<>();
TagCache(LCMSProfile p) {
private TagCache(LCMSProfile p) {
profile = p;
tags = new HashMap<>();
}
TagData getTag(int sig) {
private TagData getTag(int sig) {
TagData t = tags.get(sig);
if (t == null) {
byte[] tagData = LCMS.getTagNative(profile.getNativePtr(), sig);
if (tagData != null) {
t = new TagData(sig, tagData);
t = new TagData(tagData);
tags.put(sig, t);
}
}
return t;
}
void clear() {
private void clear() {
tags.clear();
}
}
static class TagData {
private int signature;
private byte[] data;
static final class TagData {
private final byte[] data;
TagData(int sig, byte[] data) {
this.signature = sig;
TagData(byte[] data) {
this.data = data;
}
int getSize() {
return data.length;
}
byte[] getData() {
return Arrays.copyOf(data, data.length);
}
void copyDataTo(byte[] dst) {
System.arraycopy(data, 0, dst, 0, data.length);
}
int getSignature() {
return signature;
return data.clone();
}
}
}

View File

@ -311,68 +311,44 @@ JNIEXPORT jlong JNICALL Java_sun_java2d_cmm_lcms_LCMS_loadProfileNative
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: getProfileSizeNative
* Signature: (J)I
* Method: getProfileDataNative
* Signature: (J[B)V
*/
JNIEXPORT jint JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileSizeNative
JNIEXPORT jbyteArray JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
(JNIEnv *env, jobject obj, jlong id)
{
lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
cmsUInt32Number pfSize = 0;
if (cmsSaveProfileToMem(sProf->pf, NULL, &pfSize) && ((jint)pfSize > 0)) {
return (jint)pfSize;
} else {
JNU_ThrowByName(env, "java/awt/color/CMMException",
"Can not access specified profile.");
return -1;
}
}
/*
* Class: sun_java2d_cmm_lcms_LCMS
* Method: getProfileDataNative
* Signature: (J[B)V
*/
JNIEXPORT void JNICALL Java_sun_java2d_cmm_lcms_LCMS_getProfileDataNative
(JNIEnv *env, jobject obj, jlong id, jbyteArray data)
{
lcmsProfile_p sProf = (lcmsProfile_p)jlong_to_ptr(id);
jint size;
jbyte* dataArray;
cmsUInt32Number pfSize = 0;
cmsBool status;
// determine actual profile size
if (!cmsSaveProfileToMem(sProf->pf, NULL, &pfSize)) {
JNU_ThrowByName(env, "java/awt/color/CMMException",
"Can not access specified profile.");
return;
return NULL;
}
// verify java buffer capacity
size = (*env)->GetArrayLength(env, data);
if (0 >= size || pfSize > (cmsUInt32Number)size) {
JNU_ThrowByName(env, "java/awt/color/CMMException",
"Insufficient buffer capacity.");
return;
jbyteArray data = (*env)->NewByteArray(env, pfSize);
if (data == NULL) {
// An exception should have already been thrown.
return NULL;
}
dataArray = (*env)->GetByteArrayElements (env, data, 0);
jbyte* dataArray = (*env)->GetByteArrayElements(env, data, 0);
if (dataArray == NULL) {
// An exception should have already been thrown.
return;
return NULL;
}
status = cmsSaveProfileToMem(sProf->pf, dataArray, &pfSize);
cmsBool status = cmsSaveProfileToMem(sProf->pf, dataArray, &pfSize);
(*env)->ReleaseByteArrayElements (env, data, dataArray, 0);
(*env)->ReleaseByteArrayElements(env, data, dataArray, 0);
if (!status) {
JNU_ThrowByName(env, "java/awt/color/CMMException",
"Can not access specified profile.");
return;
return NULL;
}
return data;
}
/* Get profile header info */

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) 2021, 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.
*/
import java.awt.color.ColorSpace;
import java.awt.color.ICC_Profile;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* @test
* @bug 8260695
* @summary Verifies MT safety of ICC_Profile#getData
*/
public final class MTGetData {
static volatile long endtime;
static volatile boolean failed;
public static void main(String[] args) throws Exception {
ICC_Profile[] profiles = {
ICC_Profile.getInstance(ColorSpace.CS_sRGB),
ICC_Profile.getInstance(ColorSpace.CS_LINEAR_RGB),
ICC_Profile.getInstance(ColorSpace.CS_CIEXYZ),
ICC_Profile.getInstance(ColorSpace.CS_PYCC),
ICC_Profile.getInstance(ColorSpace.CS_GRAY)
};
List<Integer> tags = new ArrayList<>();
for (Field field : ICC_Profile.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers())
&& Modifier.isPublic(field.getModifiers())
&& Modifier.isFinal(field.getModifiers())
&& field.getType() == int.class) {
tags.add(field.getInt(null));
}
}
List<Thread> tasks = new ArrayList<>();
for (int tag : tags) {
for (ICC_Profile profile1 : profiles) {
for (ICC_Profile profile2 : profiles) {
byte[] d1 = profile1.getData(tag);
byte[] d2 = profile2.getData(tag);
if (d1 == null || d2 == null || d1.length == d2.length) {
continue;
}
tasks.add(new Thread(() -> {
try {
test(profile1.getData(), d1, d2, tag);
} catch (Throwable throwable) {
throwable.printStackTrace();
failed = true;
}
}));
}
}
}
// Will run the test no more than 15 seconds
endtime = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
for (Thread t : tasks) {
t.start();
}
for (Thread t : tasks) {
t.join();
}
if (failed) {
throw new RuntimeException();
}
}
private static void test(byte[] all, byte[] data1, byte[] data2, int tag)
throws Exception {
ICC_Profile icc = ICC_Profile.getInstance(all);
Thread swap = new Thread(() -> {
try {
while (!isComplete()) {
icc.setData(tag, data1);
icc.setData(tag, data2);
}
} catch (IllegalArgumentException ignored) {
} catch (Throwable throwable) {
throwable.printStackTrace();
failed = true;
}
});
Thread fetch = new Thread(() -> {
try {
while (!isComplete()) {
icc.getData(tag);
icc.getData();
}
} catch (Throwable throwable) {
throwable.printStackTrace();
failed = true;
}
});
swap.start();
fetch.start();
swap.join();
fetch.join();
}
private static boolean isComplete() {
return endtime - System.nanoTime() < 0 || failed;
}
}