Skip to content

Commit

Permalink
org.eclipse.jdt.internal.core.Buffer: threadsafe
Browse files Browse the repository at this point in the history
Some field accesses had not been synchronized which may have caused NPEs
in access from different thread.

eclipse-jdt/eclipse.jdt.ui#736
  • Loading branch information
EcljpseB0T authored and jukzi committed Jun 19, 2024
1 parent 4e56490 commit 975b68e
Show file tree
Hide file tree
Showing 2 changed files with 63 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Arrays;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
Expand All @@ -34,19 +35,24 @@
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class Buffer implements IBuffer {
protected final IFile file;
protected int flags;
protected char[] contents;
protected ListenerList<IBufferChangedListener> changeListeners;
protected final IOpenable owner;
protected int gapStart = -1;
protected int gapEnd = -1;
private final IFile file;
/** synchronized by this.lock **/
private int flags;
/** synchronized by this.lock **/
private char[] contents;
/** synchronized by Buffer.this **/
private ListenerList<IBufferChangedListener> changeListeners;
private final IOpenable owner;
/** synchronized by this.lock **/
private int gapStart = -1;
/** synchronized by this.lock **/
private int gapEnd = -1;

protected Object lock = new Object();
private final Object lock = new Object();

protected static final int F_HAS_UNSAVED_CHANGES = 1;
protected static final int F_IS_READ_ONLY = 2;
protected static final int F_IS_CLOSED = 4;
private static final int F_HAS_UNSAVED_CHANGES = 1;
private static final int F_IS_READ_ONLY = 2;
private static final int F_IS_CLOSED = 4;

/**
* Creates a new buffer on an underlying resource.
Expand Down Expand Up @@ -207,21 +213,27 @@ public IResource getUnderlyingResource() {
*/
@Override
public boolean hasUnsavedChanges() {
return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
synchronized(this.lock) {
return (this.flags & F_HAS_UNSAVED_CHANGES) != 0;
}
}
/**
* @see IBuffer
*/
@Override
public boolean isClosed() {
return (this.flags & F_IS_CLOSED) != 0;
synchronized(this.lock) {
return (this.flags & F_IS_CLOSED) != 0;
}
}
/**
* @see IBuffer
*/
@Override
public boolean isReadOnly() {
return (this.flags & F_IS_READ_ONLY) != 0;
synchronized(this.lock) {
return (this.flags & F_IS_READ_ONLY) != 0;
}
}
/**
* Moves the gap to location and adjust its size to the
Expand Down Expand Up @@ -270,20 +282,28 @@ protected void moveAndResizeGap(int position, int size) {
* To avoid deadlock, this should not be called in a synchronized block.
*/
protected void notifyChanged(final BufferChangedEvent event) {
ListenerList<IBufferChangedListener> listeners = this.changeListeners;
if (listeners != null) {
for (IBufferChangedListener listener : listeners) {
SafeRunner.run(new ISafeRunnable() {
@Override
public void handleException(Throwable exception) {
Util.log(exception, "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
}
@Override
public void run() throws Exception {
listener.bufferChanged(event);
}
});
IBufferChangedListener[] listeners;
synchronized(this) {
ListenerList<IBufferChangedListener> cListeners = this.changeListeners;
if (cListeners == null) {
return;
}
Object[] l = cListeners.getListeners();
// make a copy before leaving synchronized block to make sure there is no concurrent modification:
listeners = Arrays.copyOf(l, l.length, IBufferChangedListener[].class);
}

for (IBufferChangedListener listener : listeners) {
SafeRunner.run(new ISafeRunnable() {
@Override
public void handleException(Throwable exception) {
Util.log(exception, "Exception occurred in listener of buffer change notification"); //$NON-NLS-1$
}
@Override
public void run() throws Exception {
listener.bufferChanged(event);
}
});
}
}
/**
Expand Down Expand Up @@ -414,7 +434,9 @@ public void save(IProgressMonitor progress, boolean force) throws JavaModelExcep
}

// the resource no longer has unsaved changes
this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
synchronized(this.lock) {
this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
}
}
/**
* @see IBuffer
Expand All @@ -423,12 +445,12 @@ public void save(IProgressMonitor progress, boolean force) throws JavaModelExcep
public void setContents(char[] newContents) {
// allow special case for first initialization
// after creation by buffer factory
if (this.contents == null) {
synchronized (this.lock) {
this.contents = newContents;
this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
synchronized (this.lock) {
if (this.contents == null) {
this.contents = newContents;
this.flags &= ~ (F_HAS_UNSAVED_CHANGES);
return;
}
return;
}

if (!isReadOnly()) {
Expand Down Expand Up @@ -458,10 +480,12 @@ public void setContents(String newContents) {
* Sets this <code>Buffer</code> to be read only.
*/
protected void setReadOnly(boolean readOnly) {
if (readOnly) {
this.flags |= F_IS_READ_ONLY;
} else {
this.flags &= ~(F_IS_READ_ONLY);
synchronized(this.lock) {
if (readOnly) {
this.flags |= F_IS_READ_ONLY;
} else {
this.flags &= ~(F_IS_READ_ONLY);
}
}
}
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1624,8 +1624,8 @@ public boolean writeAndCacheClasspath(JavaProject javaProject, final IClasspathE

public static class PerWorkingCopyInfo implements IProblemRequestor {
int useCount = 0;
IProblemRequestor problemRequestor;
CompilationUnit workingCopy;
private final IProblemRequestor problemRequestor;
final CompilationUnit workingCopy;
public PerWorkingCopyInfo(CompilationUnit workingCopy, IProblemRequestor problemRequestor) {
this.workingCopy = workingCopy;
this.problemRequestor = problemRequestor;
Expand Down

0 comments on commit 975b68e

Please sign in to comment.