Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ void coroutineEscapingCommand() {
}

@Test
@SuppressWarnings("CoroutineMayNotBeInScope")
void usingParentCoroutineInChildThrows() {
var parent =
Command.noRequirements()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

package org.wpilib.javacplugin;

import com.sun.source.tree.BlockTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.EmptyStatementTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TreeScanner;
import com.sun.source.util.Trees;
import javax.lang.model.element.VariableElement;
import javax.tools.Diagnostic;

/**
* Detects any statements after a call to {@code coroutine.park()} and labels them as unreachable
* code, similar to a {@code while (true)} statement.
*/
public class CodeAfterCoroutineParkDetector extends CoroutineBasedDetector {
public static final String SUPPRESSION_KEY = "CodeAfterCoroutinePark";

public CodeAfterCoroutineParkDetector(JavacTask task) {
super(task);
}

@Override
protected TreeScanner<?, ?> createScanner(CompilationUnitTree compilationUnit) {
return new Scanner(compilationUnit);
}

private final class Scanner extends TreeScanner<Void, Void> {
private final CompilationUnitTree m_root;
private final Trees m_trees = Trees.instance(m_task);

Scanner(CompilationUnitTree compilationUnit) {
m_root = compilationUnit;
}

@Override
public Void visitBlock(BlockTree node, Void param) {
var path = m_trees.getPath(m_root, node);
if (Suppressions.hasSuppression(m_trees, path, SUPPRESSION_KEY)) {
// Error is suppressed for this block, don't bother checking
return super.visitBlock(node, param);
}

MethodInvocationTree parkInvocation = null;
for (StatementTree statement : node.getStatements()) {
if (statement instanceof EmptyStatementTree) {
// skip empty statements; someone could have just added an extra semicolon by accident
continue;
}

if (parkInvocation != null) {
m_trees.printMessage(
Diagnostic.Kind.ERROR,
"Unreachable statement: `" + parkInvocation + "` will never exit",
statement,
m_root);
break;
}

if (statement instanceof ExpressionStatementTree est
&& est.getExpression() instanceof MethodInvocationTree mit
&& mit.getMethodSelect() instanceof MemberSelectTree ms
&& ms.getIdentifier().contentEquals("park")
&& ms.getExpression() instanceof IdentifierTree id) {
var idPath = m_trees.getPath(m_root, id);
var identifierElement = m_trees.getElement(idPath);
if (identifierElement instanceof VariableElement ve
&& m_task.getTypes().isSameType(m_coroutineType, ve.asType())) {
parkInvocation = mit;
}
}
}

return super.visitBlock(node, param);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

package org.wpilib.javacplugin;

import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.util.JavacTask;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.TreeScanner;
import java.util.HashSet;
import java.util.Set;
import javax.lang.model.type.TypeMirror;

/**
* Base class for detectors that scan for code that uses coroutines. Subclasses should provide a
* {@link TreeScanner} class that implements the logic for the detector.
*/
public abstract class CoroutineBasedDetector implements TaskListener {
protected final JavacTask m_task;
private final Set<CompilationUnitTree> m_visitedCUs = new HashSet<>();

/**
* The type of the Coroutine class. This is null if the commands v3 library is not on the
* classpath, or if the field is read before the Java compiler finishes the analyze phase. Custom
* scanners are only used if this is non-null; scanner code can safely assume that it is present.
*/
protected TypeMirror m_coroutineType;

protected CoroutineBasedDetector(JavacTask task) {
m_task = task;
}

@Override
public void finished(TaskEvent taskEvent) {
var compilationUnit = taskEvent.getCompilationUnit();
if (taskEvent.getKind() == TaskEvent.Kind.ANALYZE && m_visitedCUs.add(compilationUnit)) {
m_coroutineType = getCoroutineType();

if (m_coroutineType == null) {
// Not using the commands library; nothing to scan for
return;
}

compilationUnit.accept(createScanner(compilationUnit), null);
}
}

protected abstract TreeScanner<?, ?> createScanner(CompilationUnitTree compilationUnit);

private TypeMirror getCoroutineType() {
var te = m_task.getElements().getTypeElement("org.wpilib.commands3.Coroutine");
return te == null ? null : te.asType();
}
}
Loading
Loading