Skip to content

Parser.getDefault() is thread-unsafe and is used in multiple threads #2135

@trancexpress

Description

@trancexpress

Seen while debugging for: #666

E.g.:

	at org.eclipse.pde.internal.genericeditor.target.extension.model.UnitNode.setVersion(UnitNode.java:51)
	at org.eclipse.pde.internal.genericeditor.target.extension.model.xml.Parser.parse(Parser.java:74)
	at org.eclipse.pde.internal.genericeditor.target.extension.codemining.TargetDefinitionCodeMiningProvider.fillCodeMinings(TargetDefinitionCodeMiningProvider.java:52)
	at org.eclipse.pde.internal.genericeditor.target.extension.codemining.TargetDefinitionCodeMiningProvider.lambda$0(TargetDefinitionCodeMiningProvider.java:40)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
	at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1760)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)
	at org.eclipse.pde.internal.genericeditor.target.extension.model.UnitNode.setVersion(UnitNode.java:51)
	at org.eclipse.pde.internal.genericeditor.target.extension.model.xml.Parser.parse(Parser.java:74)
	at org.eclipse.pde.internal.genericeditor.target.extension.validator.SyntaxValidatorListener.lambda$1(SyntaxValidatorListener.java:60)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.run(CompletableFuture.java:1804)
	at java.base/java.util.concurrent.CompletableFuture$AsyncRun.exec(CompletableFuture.java:1796)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:387)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1312)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1843)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1808)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:188)

The code is respectively:

	@Override
	public CompletableFuture<List<? extends ICodeMining>> provideCodeMinings(ITextViewer viewer,
			IProgressMonitor monitor) {
		return CompletableFuture.supplyAsync(() -> {
			List<ICodeMining> minings = new ArrayList<>();
			IDocument document = viewer.getDocument();
			try {
				fillCodeMinings(document, minings);
			} catch (BadLocationException e) {
				// Caught with empty mining
			}
			return minings;
		});
	}

	void fillCodeMinings(IDocument document, List<ICodeMining> minings) throws BadLocationException {
		int line = 0;
		try {
			Parser parser = Parser.getDefault();
			parser.parse(document);
			Node target = parser.getRootNode();
		CompletableFuture.runAsync(() -> {
			try {
				if (fDocument.get().isEmpty()) {
					return;
				}
				Parser.getDefault().parse(fDocument);

Parser.default() does this:

	public static Parser getDefault() {
		if (instance == null) {
			instance = new Parser();
		}
		return instance;
	}
	public void parse(IDocument document) throws XMLStreamException {
		target = null;
		Node currentParent = null;
		Node currentNode = null;
	public Node getRootNode() {
		return target;
	}

Using getRootNode() is not safe in this case. UpdateUnitVersions also has similar to TargetDefinitionCodeMiningProvider code. Likely there is more such code.

In #666 we see that a task in parallel parsing potentially other documents can result in another task working with the wrong parsed contents (since it retrieves the wrong root note with Parser.getRootNode()).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions