Merge branch '7.0.x' into pr/3271
This commit is contained in:
80 files changed
+2634
-670
No files matched your search
@@ -110,7 +110,7 @@
|
||||
<rule ref="category/java/codestyle.xml/UnnecessaryLocalBeforeReturn"/>
|
||||
<rule ref="category/java/codestyle.xml/UnnecessaryModifier"/>
|
||||
<rule ref="category/java/codestyle.xml/UnnecessaryReturn"/>
|
||||
<!-- <rule ref="category/java/codestyle.xml/UseDiamondOperator"/> -->
|
||||
<rule ref="category/java/codestyle.xml/UseDiamondOperator"/>
|
||||
<rule ref="category/java/codestyle.xml/UseShortArrayInitializer"/>
|
||||
<rule ref="category/java/codestyle.xml/UseUnderscoresInNumericLiterals"/>
|
||||
<rule ref="category/java/codestyle.xml/UselessParentheses"/>
|
||||
|
||||
@@ -80,15 +80,17 @@ The default version is always ES6.
|
||||
|
||||
##### Java
|
||||
|
||||
* {% rule "java/codestyle/UnnecessaryFullyQualifiedName" %} has two new properties, to selectively disable reporting on
|
||||
static field and method qualifiers. The rule also has been improved to be more precise.
|
||||
* The rule {% rule "java/codestyle/UselessParentheses" %} has two new properties which control how strict
|
||||
the rule should be applied. With `ignoreClarifying` (default: true) parentheses that are strictly speaking
|
||||
not necessary are allowed, if they separate expressions of different precedence.
|
||||
The other property `ignoreBalancing` (default: true) is similar, in that it allows parentheses that help
|
||||
reading and understanding the expressions.
|
||||
* The rule {% rule "java/bestpractices/LooseCoupling" %} has a new property to allow some types to be coupled to (`allowedTypes`).
|
||||
* {% rule "java/errorprone/EmptyCatchBlock" %}: `CloneNotSupportedException` and `InterruptedException` are not special-cased anymore. Rename the exception parameter to `ignored` to ignore them.
|
||||
* {% rule "java/codestyle/UnnecessaryFullyQualifiedName" %}: the rule has two new properties,
|
||||
to selectively disable reporting on static field and method qualifiers. The rule also has been improved to be more precise.
|
||||
* {% rule "java/codestyle/UselessParentheses" %}: the rule has two new properties which control how strict
|
||||
the rule should be applied. With `ignoreClarifying` (default: true) parentheses that are strictly speaking
|
||||
not necessary are allowed, if they separate expressions of different precedence.
|
||||
The other property `ignoreBalancing` (default: true) is similar, in that it allows parentheses that help
|
||||
reading and understanding the expressions.
|
||||
* {% rule "java/bestpractices/LooseCoupling" %}: the rule has a new property to allow some types to be coupled to (`allowedTypes`).
|
||||
* {% rule "java/errorprone/EmptyCatchBlock" %}: `CloneNotSupportedException` and `InterruptedException` are not special-cased anymore. Rename the exception parameter to `ignored` to ignore them.
|
||||
* {% rule "java/codestyle/UseDiamondOperator" %}: the property `java7Compatibility` is removed. The rule now handles Java 7
|
||||
properly without a property.
|
||||
|
||||
#### Removed Rules
|
||||
|
||||
@@ -151,6 +153,7 @@ The following previously deprecated rules have been finally removed:
|
||||
* [#1790](https://github.com/pmd/pmd/issues/1790): \[java] UnnecessaryFullyQualifiedName false positive with enum constant
|
||||
* [#1918](https://github.com/pmd/pmd/issues/1918): \[java] UselessParentheses false positive with boolean operators
|
||||
* [#2299](https://github.com/pmd/pmd/issues/2299): \[java] UnnecessaryFullyQualifiedName false positive with similar package name
|
||||
* [#2391](https://github.com/pmd/pmd/issues/2391): \[java] UseDiamondOperator FP when expected type and constructed type have a different parameterization
|
||||
* [#2528](https://github.com/pmd/pmd/issues/2528): \[java] MethodNamingConventions - JUnit 5 method naming not support ParameterizedTest
|
||||
* [#2739](https://github.com/pmd/pmd/issues/2739): \[java] UselessParentheses false positive for string concatenation
|
||||
* [#2748](https://github.com/pmd/pmd/issues/2748): \[java] UnnecessaryCast false positive with unchecked cast
|
||||
|
||||
@@ -7,6 +7,7 @@ package net.sourceforge.pmd.internal.util;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ContextedRuntimeException;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public final class AssertionUtil {
|
||||
@@ -54,6 +55,14 @@ public final class AssertionUtil {
|
||||
return "Invalid range [" + startInclusive + "," + endExclusive + "[ in [" + minIndex + "," + maxIndex + "[";
|
||||
}
|
||||
|
||||
|
||||
public static void validateState(boolean condition, String failed) {
|
||||
if (!condition) {
|
||||
throw new IllegalStateException(failed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @throws IllegalArgumentException if [startInclusive,endExclusive[ is
|
||||
* not a valid substring range for the given string
|
||||
@@ -128,4 +137,14 @@ public final class AssertionUtil {
|
||||
: prefix + ": " + message;
|
||||
return new AssertionError(message);
|
||||
}
|
||||
|
||||
public static @NonNull ContextedAssertionError contexted(AssertionError e) {
|
||||
return ContextedAssertionError.wrap(e);
|
||||
}
|
||||
|
||||
public static @NonNull ContextedRuntimeException contexted(RuntimeException e) {
|
||||
return e instanceof ContextedRuntimeException ? (ContextedRuntimeException) e
|
||||
: new ContextedRuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.internal.util;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang3.exception.DefaultExceptionContext;
|
||||
import org.apache.commons.lang3.exception.ExceptionContext;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
|
||||
/**
|
||||
* An {@link AssertionError} with nice messages.
|
||||
*/
|
||||
public final class ContextedAssertionError extends AssertionError implements ExceptionContext {
|
||||
|
||||
private final ExceptionContext exceptionContext = new DefaultExceptionContext();
|
||||
|
||||
private ContextedAssertionError(AssertionError e) {
|
||||
super(e.getMessage());
|
||||
setStackTrace(e.getStackTrace()); // pretend we're a regular assertion error
|
||||
}
|
||||
|
||||
|
||||
public static ContextedAssertionError wrap(AssertionError e) {
|
||||
return e instanceof ContextedAssertionError ? (ContextedAssertionError) e
|
||||
: new ContextedAssertionError(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
return getFormattedExceptionMessage(super.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextedAssertionError addContextValue(String label, Object value) {
|
||||
exceptionContext.addContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContextedAssertionError setContextValue(String label, Object value) {
|
||||
exceptionContext.addContextValue(label, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> getContextValues(String label) {
|
||||
return exceptionContext.getContextValues(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getFirstContextValue(String label) {
|
||||
return exceptionContext.getFirstContextValue(label);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getContextLabels() {
|
||||
return exceptionContext.getContextLabels();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Pair<String, Object>> getContextEntries() {
|
||||
return exceptionContext.getContextEntries();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFormattedExceptionMessage(String baseMessage) {
|
||||
return exceptionContext.getFormattedExceptionMessage(baseMessage);
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,24 @@ public class LanguageVersion implements Comparable<LanguageVersion> {
|
||||
return version.length() > 0 ? language.getTerseName() + ' ' + version : language.getTerseName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare this version to another version of the same language identified
|
||||
* by the given version string.
|
||||
*
|
||||
* @param versionString The version with which to compare
|
||||
*
|
||||
* @throws IllegalArgumentException If the argument is not a valid version
|
||||
* string for the parent language
|
||||
*/
|
||||
public int compareToVersion(String versionString) {
|
||||
LanguageVersion otherVersion = language.getVersion(versionString);
|
||||
if (otherVersion == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"No such version '" + versionString + "' for language " + language.getName());
|
||||
}
|
||||
return this.compareTo(otherVersion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(LanguageVersion o) {
|
||||
if (o == null) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.sourceforge.pmd.benchmark.TimeTracker;
|
||||
import net.sourceforge.pmd.benchmark.TimedOperation;
|
||||
import net.sourceforge.pmd.benchmark.TimedOperationCategory;
|
||||
import net.sourceforge.pmd.internal.SystemProps;
|
||||
import net.sourceforge.pmd.internal.util.AssertionUtil;
|
||||
import net.sourceforge.pmd.lang.ast.Node;
|
||||
|
||||
/** Applies a set of rules to a set of ASTs. */
|
||||
@@ -62,32 +63,38 @@ public class RuleApplicator {
|
||||
rule.apply(node, ctx);
|
||||
rcto.close(1);
|
||||
} catch (RuntimeException e) {
|
||||
if (ctx.isIgnoreExceptions()) {
|
||||
ctx.getReport().addError(new ProcessingError(e, String.valueOf(ctx.getSourceCodeFile())));
|
||||
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + " on file "
|
||||
+ ctx.getSourceCodeFile() + ", continuing with next rule", e);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (StackOverflowError | AssertionError e) {
|
||||
if (SystemProps.isErrorRecoveryMode()) {
|
||||
ctx.getReport().addError(new ProcessingError(e, String.valueOf(ctx.getSourceCodeFile())));
|
||||
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + " on file "
|
||||
+ ctx.getSourceCodeFile() + ", continuing with next rule", e);
|
||||
}
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
reportOrRethrow(ctx, rule, node, e, ctx.isIgnoreExceptions());
|
||||
} catch (AssertionError | StackOverflowError e) {
|
||||
reportOrRethrow(ctx, rule, node, e, SystemProps.isErrorRecoveryMode());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private <E extends Throwable> void reportOrRethrow(RuleContext ctx, Rule rule, Node node, E e, boolean reportAndDontThrow) throws E {
|
||||
if (reportAndDontThrow) {
|
||||
reportException(ctx, rule, node, e);
|
||||
} else {
|
||||
if (e instanceof RuntimeException) {
|
||||
throw AssertionUtil.contexted((RuntimeException) e).addContextValue("Rule applied on node", node);
|
||||
} else if (e instanceof AssertionError) {
|
||||
throw AssertionUtil.contexted((AssertionError) e).addContextValue("Rule applied on node", node);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reportException(RuleContext ctx, Rule rule, Node node, Throwable e) {
|
||||
ctx.getReport().addError(new ProcessingError(e, String.valueOf(ctx.getSourceCodeFile())));
|
||||
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.log(Level.WARNING, "Exception applying rule " + rule.getName() + " on file "
|
||||
+ ctx.getSourceCodeFile() + ", continuing with next rule", e);
|
||||
LOG.log(Level.WARNING, "Exception occurred on node " + node, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void indexTree(Node top, TreeIndex idx) {
|
||||
idx.indexNode(top);
|
||||
for (Node child : top.children()) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import net.sourceforge.pmd.lang.ast.impl.GenericNode;
|
||||
import net.sourceforge.pmd.lang.java.symbols.table.JSymbolTable;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.ClassTypeResolver;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeSystem;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
|
||||
|
||||
// FUTURE Change this class to extend from SimpleJavaNode, as TypeNode is not appropriate (unless I'm wrong)
|
||||
public final class ASTCompilationUnit extends AbstractJavaTypeNode implements JavaNode, GenericNode<JavaNode>, RootNode {
|
||||
|
||||
@@ -55,7 +55,15 @@ public final class ASTConditionalExpression extends AbstractJavaExpr {
|
||||
return visitor.visit(this, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: this method is not used at all in pre-Java 8 analysis,
|
||||
* because standalone/poly exprs weren't formalized before java 8.
|
||||
* Calling this method then is undefined.
|
||||
*/
|
||||
// very internal
|
||||
boolean isStandalone() {
|
||||
assert getAstInfo().getLanguageVersion().compareToVersion("8") >= 0
|
||||
: "This method's result is undefined in pre java 8 code";
|
||||
return this.isStandalone;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.annotation.Experimental;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
|
||||
|
||||
/**
|
||||
* Represents an expression, in the most general sense.
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.VariableIdOwner;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypingContext;
|
||||
|
||||
|
||||
/**
|
||||
@@ -68,8 +69,7 @@ public final class ASTFormalParameter extends AbstractJavaNode
|
||||
* Returns the declarator ID of this formal parameter.
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public ASTVariableDeclaratorId getVarId() {
|
||||
public @NonNull ASTVariableDeclaratorId getVarId() {
|
||||
return firstChild(ASTVariableDeclaratorId.class);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public final class ASTFormalParameter extends AbstractJavaNode
|
||||
// The node that represents the variable is the variable ID.
|
||||
|
||||
@Override
|
||||
public @NonNull JTypeMirror getTypeMirror() {
|
||||
return getVarId().getTypeMirror();
|
||||
public @NonNull JTypeMirror getTypeMirror(TypingContext ctx) {
|
||||
return getVarId().getTypeMirror(ctx);
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
*
|
||||
* </pre>
|
||||
*/
|
||||
public final class ASTLambdaExpression extends AbstractJavaExpr {
|
||||
public final class ASTLambdaExpression extends AbstractJavaExpr implements FunctionalExpression {
|
||||
|
||||
private JMethodSig functionalMethod;
|
||||
|
||||
@@ -50,6 +50,7 @@ public final class ASTLambdaExpression extends AbstractJavaExpr {
|
||||
*
|
||||
* @see #getTypeMirror()
|
||||
*/
|
||||
@Override
|
||||
public JMethodSig getFunctionalMethod() {
|
||||
forceTypeResolution();
|
||||
return assertNonNullAfterTypeRes(functionalMethod);
|
||||
|
||||
@@ -26,7 +26,8 @@ public final class ASTMethodReference extends AbstractJavaExpr
|
||||
implements ASTPrimaryExpression,
|
||||
QualifiableExpression,
|
||||
LeftRecursiveNode,
|
||||
MethodUsage {
|
||||
MethodUsage,
|
||||
FunctionalExpression {
|
||||
|
||||
private JMethodSig functionalMethod;
|
||||
private JMethodSig compileTimeDecl;
|
||||
@@ -131,6 +132,7 @@ public final class ASTMethodReference extends AbstractJavaExpr
|
||||
* @see #getReferencedMethod()
|
||||
* @see #getTypeMirror()
|
||||
*/
|
||||
@Override
|
||||
public JMethodSig getFunctionalMethod() {
|
||||
forceTypeResolution();
|
||||
return assertNonNullAfterTypeRes(functionalMethod);
|
||||
|
||||
@@ -8,6 +8,7 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.InternalInterfaces.AtLeastOneChild;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypingContext;
|
||||
|
||||
/**
|
||||
* Wraps a type node but presents the interface of {@link ASTExpression}.
|
||||
@@ -63,8 +64,8 @@ public final class ASTTypeExpression extends AbstractJavaNode implements ASTPrim
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull JTypeMirror getTypeMirror() {
|
||||
return getTypeNode().getTypeMirror();
|
||||
public @NonNull JTypeMirror getTypeMirror(TypingContext ctx) {
|
||||
return getTypeNode().getTypeMirror(ctx);
|
||||
}
|
||||
|
||||
}
|
||||
+3
-6
@@ -48,16 +48,14 @@ abstract class AbstractAnyTypeDeclaration extends AbstractTypedSymbolDeclarator<
|
||||
return super.getImage();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String getBinaryName() {
|
||||
public @NonNull String getBinaryName() {
|
||||
assert binaryName != null : "Null binary name";
|
||||
return binaryName;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getCanonicalName() {
|
||||
public @Nullable String getCanonicalName() {
|
||||
assert binaryName != null : "Canonical name wasn't set";
|
||||
return canonicalName;
|
||||
}
|
||||
@@ -73,9 +71,8 @@ abstract class AbstractAnyTypeDeclaration extends AbstractTypedSymbolDeclarator<
|
||||
this.canonicalName = canon;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public JClassType getTypeMirror() {
|
||||
public @NonNull JClassType getTypeMirror() {
|
||||
return (JClassType) super.getTypeMirror();
|
||||
}
|
||||
}
|
||||
|
||||
+25
-15
@@ -4,10 +4,12 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import org.apache.commons.lang3.exception.ContextedRuntimeException;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.internal.util.AssertionUtil;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypingContext;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
|
||||
|
||||
/**
|
||||
* An extension of the SimpleJavaNode which implements the TypeNode interface.
|
||||
@@ -34,22 +36,30 @@ abstract class AbstractJavaTypeNode extends AbstractJavaNode implements TypeNode
|
||||
|
||||
@Override
|
||||
public @NonNull JTypeMirror getTypeMirror() {
|
||||
if (typeMirror == null) {
|
||||
try {
|
||||
LazyTypeResolver resolver = getRoot().getLazyTypeResolver();
|
||||
typeMirror = this.acceptVisitor(resolver, null);
|
||||
assert typeMirror != null : "LazyTypeResolver returned null";
|
||||
} catch (Exception | AssertionError e) {
|
||||
// this will add every type in the chain
|
||||
throw addContextValue(e, "Resolving type of", this);
|
||||
}
|
||||
}
|
||||
return typeMirror;
|
||||
return getTypeMirror(TypingContext.DEFAULT);
|
||||
}
|
||||
|
||||
private static ContextedRuntimeException addContextValue(Throwable e, String label, Object value) {
|
||||
return e instanceof ContextedRuntimeException ? ((ContextedRuntimeException) e).addContextValue(label, value)
|
||||
: new ContextedRuntimeException(e).addContextValue(label, value);
|
||||
@Override
|
||||
public @NonNull JTypeMirror getTypeMirror(TypingContext context) {
|
||||
if (context.isEmpty() && typeMirror != null) {
|
||||
return typeMirror;
|
||||
}
|
||||
|
||||
LazyTypeResolver resolver = getRoot().getLazyTypeResolver();
|
||||
JTypeMirror result;
|
||||
try {
|
||||
result = this.acceptVisitor(resolver, context);
|
||||
assert result != null : "LazyTypeResolver returned null";
|
||||
} catch (RuntimeException e) {
|
||||
throw AssertionUtil.contexted(e).addContextValue("Resolving type of", this);
|
||||
} catch (AssertionError e) {
|
||||
throw AssertionUtil.contexted(e).addContextValue("Resolving type of", this);
|
||||
}
|
||||
|
||||
if (context.isEmpty() && typeMirror == null) {
|
||||
typeMirror = result; // cache it
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
JTypeMirror getTypeMirrorInternal() {
|
||||
|
||||
@@ -27,6 +27,7 @@ import net.sourceforge.pmd.lang.java.types.JClassType;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.JVariableSig;
|
||||
import net.sourceforge.pmd.lang.java.types.JVariableSig.FieldSig;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
|
||||
|
||||
/**
|
||||
* This implements name disambiguation following <a href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-6.html#jls-6.5.2">JLS§6.5.2</a>.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.types.JMethodSig;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
|
||||
/**
|
||||
* A method reference or lambda expression.
|
||||
*/
|
||||
public interface FunctionalExpression extends ASTExpression {
|
||||
|
||||
|
||||
/**
|
||||
* Returns the type of the functional interface.
|
||||
* E.g. in {@code stringStream.map(s -> s.isEmpty())}, this is
|
||||
* {@code java.util.function.Function<java.lang.String, java.lang.Boolean>}.
|
||||
*
|
||||
* @see #getFunctionalMethod()
|
||||
*/
|
||||
@Override
|
||||
@NonNull JTypeMirror getTypeMirror();
|
||||
|
||||
/**
|
||||
* Returns the method that is overridden in the functional interface.
|
||||
* E.g. in {@code stringStream.map(s -> s.isEmpty())}, this is
|
||||
* {@code java.util.function.Function#apply(java.lang.String) ->
|
||||
* java.lang.Boolean}
|
||||
*
|
||||
* @see #getTypeMirror()
|
||||
*/
|
||||
JMethodSig getFunctionalMethod();
|
||||
|
||||
}
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.ast;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.annotation.InternalApi;
|
||||
import net.sourceforge.pmd.internal.util.AssertionUtil;
|
||||
import net.sourceforge.pmd.lang.ast.NodeStream;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccToken;
|
||||
import net.sourceforge.pmd.lang.ast.impl.javacc.JavaccTokenDocument;
|
||||
@@ -26,6 +28,11 @@ import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.JVariableSig;
|
||||
import net.sourceforge.pmd.lang.java.types.JVariableSig.FieldSig;
|
||||
import net.sourceforge.pmd.lang.java.types.OverloadSelectionResult;
|
||||
import net.sourceforge.pmd.lang.java.types.Substitution;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeSystem;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.LazyTypeResolver;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.Infer;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.TypeInferenceLogger;
|
||||
import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
|
||||
@@ -153,12 +160,14 @@ public final class InternalApiBridge {
|
||||
node.setTypedSym(sig);
|
||||
}
|
||||
|
||||
public static void setFunctionalMethod(ASTMethodReference methodReference, JMethodSig methodType) {
|
||||
methodReference.setFunctionalMethod(methodType);
|
||||
}
|
||||
|
||||
public static void setFunctionalMethod(ASTLambdaExpression lambda, @Nullable JMethodSig methodType) {
|
||||
lambda.setFunctionalMethod(methodType);
|
||||
public static void setFunctionalMethod(FunctionalExpression node, JMethodSig methodType) {
|
||||
if (node instanceof ASTMethodReference) {
|
||||
((ASTMethodReference) node).setFunctionalMethod(methodType);
|
||||
} else if (node instanceof ASTLambdaExpression) {
|
||||
((ASTLambdaExpression) node).setFunctionalMethod(methodType);
|
||||
} else {
|
||||
throw AssertionUtil.shouldNotReachHere("" + node);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setCompileTimeDecl(ASTMethodReference methodReference, JMethodSig methodType) {
|
||||
@@ -185,6 +194,18 @@ public final class InternalApiBridge {
|
||||
return n.getRoot().getLazyTypeResolver().getProcessor();
|
||||
}
|
||||
|
||||
public static Infer getInferenceEntryPoint(JavaNode n) {
|
||||
return n.getRoot().getLazyTypeResolver().getInfer();
|
||||
}
|
||||
|
||||
public static @NonNull LazyTypeResolver getLazyTypeResolver(JavaNode n) {
|
||||
return n.getRoot().getLazyTypeResolver();
|
||||
}
|
||||
|
||||
public static @NonNull ExprContext getTopLevelExprContext(TypeNode n) {
|
||||
return n.getRoot().getLazyTypeResolver().getTopLevelContextIncludingInvocation(n);
|
||||
}
|
||||
|
||||
public static void setSymbolTable(JavaNode node, JSymbolTable table) {
|
||||
((AbstractJavaNode) node).setSymbolTable(table);
|
||||
}
|
||||
@@ -208,4 +229,20 @@ public final class InternalApiBridge {
|
||||
public static void setStandaloneTernary(ASTConditionalExpression node) {
|
||||
node.setStandaloneTernary();
|
||||
}
|
||||
|
||||
public static boolean isStandaloneInternal(ASTConditionalExpression node) {
|
||||
return node.isStandalone();
|
||||
}
|
||||
|
||||
public static JTypeMirror buildTypeFromAstInternal(TypeSystem ts, Substitution lexicalSubst, ASTType node) {
|
||||
return TypesFromAst.fromAst(ts, lexicalSubst, node);
|
||||
}
|
||||
|
||||
public static void setTypedSym(ASTFieldAccess expr, JVariableSig.FieldSig sym) {
|
||||
expr.setTypedSym(sym);
|
||||
}
|
||||
|
||||
public static void setTypedSym(ASTVariableAccess expr, JVariableSig sym) {
|
||||
expr.setTypedSym(sym);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import net.sourceforge.pmd.annotation.DeprecatedUntil700;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeSystem;
|
||||
import net.sourceforge.pmd.lang.java.types.TypingContext;
|
||||
|
||||
/**
|
||||
* A node that has a statically known type. This includes e.g.
|
||||
@@ -34,10 +35,14 @@ public interface TypeNode extends JavaNode {
|
||||
* API will be added to expose this information.
|
||||
*
|
||||
* @return The type mirror. Never returns null; if the type is unresolved, returns
|
||||
* {@link TypeSystem#UNKNOWN}.
|
||||
* {@link TypeSystem#UNKNOWN}.
|
||||
*/
|
||||
@NonNull
|
||||
JTypeMirror getTypeMirror();
|
||||
default JTypeMirror getTypeMirror() {
|
||||
return getTypeMirror(TypingContext.DEFAULT);
|
||||
}
|
||||
|
||||
JTypeMirror getTypeMirror(TypingContext typing);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ final class TypesFromAst {
|
||||
return fromAstImpl(ts, lexicalSubst, node);
|
||||
}
|
||||
|
||||
public static JTypeMirror fromAstImpl(TypeSystem ts, Substitution lexicalSubst, ASTType node) {
|
||||
private static JTypeMirror fromAstImpl(TypeSystem ts, Substitution lexicalSubst, ASTType node) {
|
||||
|
||||
if (node instanceof ASTClassOrInterfaceType) {
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@ public final class JavaAstProcessor {
|
||||
SemanticErrorReporter logger,
|
||||
TypeInferenceLogger typeInfLogger) {
|
||||
|
||||
TypeSystem typeSystem = TYPE_SYSTEMS.computeIfAbsent(classLoader, TypeSystem::new);
|
||||
TypeSystem typeSystem = TYPE_SYSTEMS.computeIfAbsent(classLoader, TypeSystem::usingClassLoaderClasspath);
|
||||
return new JavaAstProcessor(
|
||||
typeSystem,
|
||||
typeSystem.bootstrapResolver(),
|
||||
|
||||
+6
-5
@@ -28,7 +28,6 @@ import net.sourceforge.pmd.lang.java.ast.ASTInfixExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTLambdaExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTMethodReference;
|
||||
import net.sourceforge.pmd.lang.java.ast.BinaryOp;
|
||||
import net.sourceforge.pmd.lang.java.ast.ExprContext;
|
||||
import net.sourceforge.pmd.lang.java.ast.internal.PrettyPrintingUtil;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.lang.java.rule.internal.JavaRuleUtil;
|
||||
@@ -36,6 +35,8 @@ import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeConversion;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeOps;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeTestUtil;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.ExprContext.ExprContextKind;
|
||||
|
||||
/**
|
||||
* Detects casts where the operand is already a subtype of the context
|
||||
@@ -72,7 +73,7 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
|
||||
if (operand instanceof ASTLambdaExpression || operand instanceof ASTMethodReference) {
|
||||
// Then the cast provides a target type for the expression (always).
|
||||
// We need to check the enclosing context, as if it's invocation we give up for now
|
||||
if (context.isMissing() || context.isInvocationContext()) {
|
||||
if (context.isMissing() || context.hasKind(ExprContextKind.INVOCATION)) {
|
||||
// Then the cast may be used to determine the overload.
|
||||
// We need to treat the casted lambda as a whole unit.
|
||||
// todo see below
|
||||
@@ -119,7 +120,7 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
|
||||
private static boolean castIsUnnecessaryToMatchContext(ExprContext context,
|
||||
JTypeMirror coercionType,
|
||||
JTypeMirror operandType) {
|
||||
if (context.isInvocationContext()) {
|
||||
if (context.hasKind(ExprContextKind.INVOCATION)) {
|
||||
// todo unsupported for now, the cast may be disambiguating overloads
|
||||
return false;
|
||||
}
|
||||
@@ -152,13 +153,13 @@ public class UnnecessaryCastRule extends AbstractJavaRulechainRule {
|
||||
// a branch of a ternary
|
||||
return true;
|
||||
|
||||
} else if (context.isString() && isInfixExprWithOperator(castExpr.getParent(), ADD)) {
|
||||
} else if (context.hasKind(ExprContextKind.STRING) && isInfixExprWithOperator(castExpr.getParent(), ADD)) {
|
||||
|
||||
// inside string concatenation
|
||||
return !TypeTestUtil.isA(String.class, JavaRuleUtil.getOtherOperandIfInInfixExpr(castExpr))
|
||||
&& !TypeTestUtil.isA(String.class, operandType);
|
||||
|
||||
} else if (context.isNumeric() && castExpr.getParent() instanceof ASTInfixExpression) {
|
||||
} else if (context.hasKind(ExprContextKind.NUMERIC) && castExpr.getParent() instanceof ASTInfixExpression) {
|
||||
// numeric expr
|
||||
ASTInfixExpression parent = (ASTInfixExpression) castExpr.getParent();
|
||||
|
||||
|
||||
+286
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.rule.codestyle;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTArgumentList;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceType;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTConstructorCall;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTExpression;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTTypeArguments;
|
||||
import net.sourceforge.pmd.lang.java.ast.InternalApiBridge;
|
||||
import net.sourceforge.pmd.lang.java.ast.InvocationNode;
|
||||
import net.sourceforge.pmd.lang.java.ast.JavaNode;
|
||||
import net.sourceforge.pmd.lang.java.rule.AbstractJavaRulechainRule;
|
||||
import net.sourceforge.pmd.lang.java.symbols.JTypeDeclSymbol;
|
||||
import net.sourceforge.pmd.lang.java.types.JClassType;
|
||||
import net.sourceforge.pmd.lang.java.types.JMethodSig;
|
||||
import net.sourceforge.pmd.lang.java.types.JTypeMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.TypeOps;
|
||||
import net.sourceforge.pmd.lang.java.types.TypingContext;
|
||||
import net.sourceforge.pmd.lang.java.types.ast.ExprContext;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.CtorInvocationMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.ExprMirror.InvocationMirror;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.Infer;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.MethodCallSite;
|
||||
import net.sourceforge.pmd.lang.java.types.internal.infer.ast.JavaExprMirrors;
|
||||
|
||||
/**
|
||||
* Checks usages of explicity type arguments in a constructor call that
|
||||
* may be replaced by a diamond ({@code <>}). In order to determine this,
|
||||
* we mock a type resolution call site, which is equivalent to the expression
|
||||
* as if it had a diamond instead of explicit type arguments. We then perform
|
||||
* overload resolution for this fake call site. If overload resolution fails,
|
||||
* resolves to another overload, or if the inferred type is not compatible
|
||||
* with the expected context type, then the type arguments are unnecessary,
|
||||
* and removing them will not break the program.
|
||||
*
|
||||
* <p>Note that type inference in Java 8+ works differently from Java 7.
|
||||
* In Java 7, type arguments may be necessary in more places. The specifics
|
||||
* are however implemented within the type resolution code, and this rule does
|
||||
* not need to know about it.
|
||||
*/
|
||||
public class UseDiamondOperatorRule extends AbstractJavaRulechainRule {
|
||||
|
||||
private static final String REPLACE_TYPE_ARGS_MESSAGE = "Explicit type arguments can be replaced by a diamond: `{0}`";
|
||||
private static final String RAW_TYPE_MESSAGE = "Raw type use may be avoided by using a diamond: `{0}`";
|
||||
/**
|
||||
* Maximum length of the argument list (including parentheses) for
|
||||
* it to be included in the violation message instead of an ellipsis {@code (...)}.
|
||||
*/
|
||||
private static final int MAX_ARGS_LENGTH = 25;
|
||||
|
||||
public UseDiamondOperatorRule() {
|
||||
super(ASTConstructorCall.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visit(ASTConstructorCall ctorCall, Object data) {
|
||||
ASTClassOrInterfaceType newTypeNode = ctorCall.getTypeNode();
|
||||
JTypeMirror newType = newTypeNode.getTypeMirror();
|
||||
|
||||
ASTTypeArguments targs = newTypeNode.getTypeArguments();
|
||||
if (targs != null && targs.isDiamond()
|
||||
// if unresolved we can't know whether the class is generic or not
|
||||
|| TypeOps.isUnresolved(newType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!newType.isGeneric() // targs may be null, in which case this would be a raw type
|
||||
|| ctorCall.isAnonymousClass() && !supportsDiamondOnAnonymousClass(ctorCall)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inferenceSucceedsWithoutTypeArgs(ctorCall)) {
|
||||
// report it
|
||||
JavaNode reportNode = targs == null ? newTypeNode : targs;
|
||||
String message = targs == null ? RAW_TYPE_MESSAGE : REPLACE_TYPE_ARGS_MESSAGE;
|
||||
String replaceWith = produceSuggestedExprImage(ctorCall);
|
||||
addViolationWithMessage(data, reportNode, message, new String[] { replaceWith });
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean supportsDiamondOnAnonymousClass(ASTConstructorCall ctorCall) {
|
||||
return ctorCall.getAstInfo().getLanguageVersion().compareToVersion("9") >= 0;
|
||||
}
|
||||
|
||||
|
||||
/** Redo inference as described in the javadoc of this class. */
|
||||
private static boolean inferenceSucceedsWithoutTypeArgs(ASTConstructorCall call) {
|
||||
ExprContext context = call.getConversionContext();
|
||||
if (context.isMissing()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Infer infer = InternalApiBridge.getInferenceEntryPoint(call);
|
||||
// this may not mutate the AST
|
||||
JavaExprMirrors factory = JavaExprMirrors.forObservation(infer);
|
||||
|
||||
InvocationNode invocContext = InternalApiBridge.getTopLevelExprContext(call).getInvocNodeIfInvocContext();
|
||||
ExprContext topmostContext;
|
||||
InvocationMirror mirror;
|
||||
if (invocContext == null) {
|
||||
CtorInvocationMirror defaultMirror = (CtorInvocationMirror) factory.getTopLevelInvocationMirror(call);
|
||||
mirror = new SpyInvocMirror(defaultMirror);
|
||||
topmostContext = call.getConversionContext();
|
||||
} else {
|
||||
mirror = factory.getInvocationMirror(invocContext, (e, parent, self) -> {
|
||||
ExprMirror defaultImpl = factory.defaultMirrorMaker().createMirrorForSubexpression(e, parent, self);
|
||||
if (e == call) {
|
||||
return new SpyInvocMirror((CtorInvocationMirror) defaultImpl);
|
||||
} else {
|
||||
return defaultImpl;
|
||||
}
|
||||
});
|
||||
|
||||
if (invocContext instanceof ASTExpression) {
|
||||
topmostContext = ((ASTExpression) invocContext).getConversionContext();
|
||||
} else {
|
||||
topmostContext = ExprContext.getMissingInstance();
|
||||
}
|
||||
}
|
||||
JTypeMirror targetType = topmostContext.getPolyTargetType(false);
|
||||
MethodCallSite fakeCallSite = infer.newCallSite(mirror, targetType);
|
||||
infer.inferInvocationRecursively(fakeCallSite);
|
||||
|
||||
return mirror.isEquivalentToUnderlyingAst()
|
||||
&& topmostContext.acceptsType(mirror.getInferredType());
|
||||
}
|
||||
|
||||
|
||||
private static String produceSuggestedExprImage(ASTConstructorCall ctor) {
|
||||
StringBuilder sb = new StringBuilder(30);
|
||||
sb.append("new ");
|
||||
produceSameTypeWithDiamond(ctor.getTypeNode(), sb, true);
|
||||
ASTArgumentList arguments = ctor.getArguments();
|
||||
String argsString;
|
||||
if (arguments.size() == 0) {
|
||||
argsString = "()";
|
||||
} else {
|
||||
CharSequence text = arguments.getText();
|
||||
if (text.length() <= MAX_ARGS_LENGTH && !StringUtils.contains(text, '\n')) {
|
||||
argsString = text.toString();
|
||||
} else {
|
||||
argsString = "(...)";
|
||||
}
|
||||
}
|
||||
return sb.append(argsString).toString();
|
||||
}
|
||||
|
||||
private static StringBuilder produceSameTypeWithDiamond(ASTClassOrInterfaceType type, StringBuilder sb, boolean topLevel) {
|
||||
if (type.isFullyQualified()) {
|
||||
JTypeDeclSymbol sym = type.getTypeMirror().getSymbol();
|
||||
Objects.requireNonNull(sym);
|
||||
sb.append(sym.getPackageName()).append('.');
|
||||
} else {
|
||||
ASTClassOrInterfaceType qualifier = type.getQualifier();
|
||||
if (qualifier != null) {
|
||||
produceSameTypeWithDiamond(qualifier, sb, false).append('.');
|
||||
}
|
||||
}
|
||||
sb.append(type.getSimpleName());
|
||||
return topLevel ? sb.append("<>") : sb;
|
||||
}
|
||||
|
||||
|
||||
/** Proxy that pretends it has diamond type args. */
|
||||
private static final class SpyInvocMirror implements CtorInvocationMirror {
|
||||
|
||||
private final CtorInvocationMirror base;
|
||||
|
||||
SpyInvocMirror(CtorInvocationMirror base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
// overridden methods
|
||||
|
||||
@Override
|
||||
public @NonNull JTypeMirror getNewType() {
|
||||
// see doc of CtorInvocationMirror#getNewType
|
||||
return ((JClassType) base.getNewType()).getGenericTypeDeclaration();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDiamond() {
|
||||
return true; // pretend it is
|
||||
}
|
||||
|
||||
// delegated methods
|
||||
|
||||
@Override
|
||||
public List<JTypeMirror> getExplicitTypeArguments() {
|
||||
return base.getExplicitTypeArguments();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaNode getExplicitTargLoc(int i) {
|
||||
return base.getExplicitTargLoc(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setInferredType(JTypeMirror mirror) {
|
||||
base.setInferredType(mirror);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JTypeMirror getInferredType() {
|
||||
return base.getInferredType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCtDecl(MethodCtDecl methodType) {
|
||||
base.setCtDecl(methodType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable MethodCtDecl getCtDecl() {
|
||||
return base.getCtDecl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaNode getLocation() {
|
||||
return base.getLocation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull JClassType getEnclosingType() {
|
||||
return base.getEnclosingType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnonymous() {
|
||||
return base.isAnonymous();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<JMethodSig> getAccessibleCandidates(JTypeMirror newType) {
|
||||
return base.getAccessibleCandidates(newType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable JTypeMirror getReceiverType() {
|
||||
return base.getReceiverType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return base.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ExprMirror> getArgumentExpressions() {
|
||||
return base.getArgumentExpressions();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getArgumentCount() {
|
||||
return base.getArgumentCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return base.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypingContext getTypingContext() {
|
||||
return base.getTypingContext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEquivalentToUnderlyingAst() {
|
||||
return base.isEquivalentToUnderlyingAst();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+3
-3
@@ -27,7 +27,7 @@ public class AsmSymbolResolver implements SymbolResolver {
|
||||
static final int ASM_API_V = Opcodes.ASM9;
|
||||
|
||||
private final TypeSystem ts;
|
||||
private final ClassLoader classLoader;
|
||||
private final Classpath classLoader;
|
||||
private final SignatureParser typeLoader;
|
||||
|
||||
private final ConcurrentHashMap<String, SoftClassReference> knownStubs = new ConcurrentHashMap<>();
|
||||
@@ -38,7 +38,7 @@ public class AsmSymbolResolver implements SymbolResolver {
|
||||
*/
|
||||
private final SoftClassReference failed;
|
||||
|
||||
public AsmSymbolResolver(TypeSystem ts, ClassLoader classLoader) {
|
||||
public AsmSymbolResolver(TypeSystem ts, Classpath classLoader) {
|
||||
this.ts = ts;
|
||||
this.classLoader = classLoader;
|
||||
this.typeLoader = new SignatureParser(this);
|
||||
@@ -104,7 +104,7 @@ public class AsmSymbolResolver implements SymbolResolver {
|
||||
|
||||
@Nullable
|
||||
URL getUrlOfInternalName(String internalName) {
|
||||
return classLoader.getResource(internalName + ".class");
|
||||
return classLoader.findResource(internalName + ".class");
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.symbols.internal.asm;
|
||||
|
||||
import java.net.URL;
|
||||
import java.util.Set;
|
||||
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
/**
|
||||
* Classpath abstraction. PMD's symbol resolver uses the classpath to
|
||||
* find class files.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface Classpath {
|
||||
|
||||
/**
|
||||
* Returns a URL to load the given resource if it exists in this classpath.
|
||||
* Otherwise returns null. This will typically be used to find Java class files.
|
||||
* A typical input would be {@code java/lang/String.class}.
|
||||
*
|
||||
* @param resourcePath Resource path, as described in {@link ClassLoader#getResource(String)}
|
||||
*
|
||||
* @return A URL if the resource exists, otherwise null
|
||||
*/
|
||||
@Nullable URL findResource(String resourcePath);
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Transformation methods (defaults)">
|
||||
|
||||
/**
|
||||
* Return a classpath that will ignore the given classpath entries,
|
||||
* even if they are present in this classpath. Every call to {@link #findResource(String)}
|
||||
* is otherwise delegated to this one.
|
||||
*
|
||||
* @param deletedEntries Set of resource paths to exclude
|
||||
*/
|
||||
default Classpath exclude(Set<String> deletedEntries) {
|
||||
return resourcePath -> deletedEntries.contains(resourcePath) ? null : findResource(resourcePath);
|
||||
}
|
||||
|
||||
default Classpath delegateTo(Classpath c) {
|
||||
return path -> {
|
||||
URL p = findResource(path);
|
||||
if (p != null) {
|
||||
return p;
|
||||
}
|
||||
return c.findResource(path);
|
||||
};
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
// <editor-fold defaultstate="collapsed" desc="Creator methods">
|
||||
|
||||
|
||||
/**
|
||||
* Returns a classpath instance that uses {@link ClassLoader#getResource(String)}
|
||||
* to find resources.
|
||||
*/
|
||||
static Classpath forClassLoader(ClassLoader classLoader) {
|
||||
return classLoader::getResource;
|
||||
}
|
||||
|
||||
static Classpath contextClasspath() {
|
||||
return forClassLoader(Thread.currentThread().getContextClassLoader());
|
||||
}
|
||||
|
||||
// </editor-fold>
|
||||
|
||||
}
|
||||
@@ -168,7 +168,7 @@ class ClassTypeImpl implements JClassType {
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public List<JTypeMirror> getTypeArgs() {
|
||||
return isGenericTypeDeclaration() ? (List) getFormalTypeParams() : typeArgs;
|
||||
}
|
||||
@@ -199,9 +199,7 @@ class ClassTypeImpl implements JClassType {
|
||||
}
|
||||
|
||||
int expected = symbol.getTypeParameterCount();
|
||||
if (typeArgs.size() != expected && !typeArgs.isEmpty()) {
|
||||
throw invalidTypeArgs(symbol, typeArgs);
|
||||
} else if (expected == 0) {
|
||||
if (expected == 0 && typeArgs.isEmpty() && this.typeArgs.isEmpty()) {
|
||||
return this; // non-generic
|
||||
}
|
||||
return new ClassTypeImpl(ts, symbol, CollectionUtil.defensiveUnmodifiableCopy(typeArgs), false);
|
||||
@@ -344,19 +342,6 @@ class ClassTypeImpl implements JClassType {
|
||||
checkUserEnclosingTypeIsOk(enclosing, symbol);
|
||||
|
||||
if (!typeArgsAreOk(symbol, typeArgs)) {
|
||||
// fixme relax this
|
||||
// This will throw if the symbol is unresolved and was
|
||||
// resolved through AsmSymbolResolver (ie, a missing dependency
|
||||
// in classpath, found in a signature of some ASM class symbol member).
|
||||
// Currently the AST symbol impl tries to patch unresolved symbols by
|
||||
// making the number of type params flexible. But this does not help
|
||||
// the ASM implementation, and these errors are frequent if your classpath
|
||||
// is missing something. We still want pmd to continue processing in this case.
|
||||
// The best fix IMO is to admit malformed types provided they're unresolved.
|
||||
// We'll have to abandon the assumption that every parameterized type for
|
||||
// the same symbol has the same number of type params. And also, that the
|
||||
// formal type parameter list always matches the type argument lists in length.
|
||||
// Many places rely on this... For instance: TypeConversion#capture, TypeOps#isSameType, etc
|
||||
throw invalidTypeArgs(symbol, typeArgs);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.symbols.JClassSymbol;
|
||||
import net.sourceforge.pmd.lang.java.symbols.JExecutableSymbol;
|
||||
import net.sourceforge.pmd.lang.java.symbols.JMethodSymbol;
|
||||
|
||||
/**
|
||||
* Represents class and interface types, including functional interface
|
||||
@@ -128,31 +129,44 @@ public interface JClassType extends JTypeMirror {
|
||||
|
||||
/**
|
||||
* A specific instantiation of the type variables in {@link #getFormalTypeParams()}.
|
||||
* Note that the type arguments and formal type parameters may be mismatched in size,
|
||||
* (only if the symbol is unresolved). In any case, no attempt is made to check that
|
||||
* the type arguments conform to the bound on type parameters in methods like
|
||||
* {@link #withTypeArguments(List)}, although this is taken into account during type
|
||||
* inference.
|
||||
*
|
||||
* <p>If this type is not generic, or a raw type, returns an empty list.
|
||||
* <p>If this is a {@linkplain #isGenericTypeDeclaration() generic type declaration},
|
||||
* returns exactly the same list as {@link #getFormalTypeParams()}.
|
||||
*
|
||||
* @see #getFormalTypeParams()
|
||||
*/
|
||||
List<JTypeMirror> getTypeArgs();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the list of type variables declared by the generic type declaration.
|
||||
* These match {@link #getTypeArgs()} if this is a {@linkplain #isGenericTypeDeclaration() generic type
|
||||
* declaration},
|
||||
* which is distinct from a {@linkplain #isRaw() raw type}.
|
||||
*
|
||||
* <p>If this type is not generic, returns an empty list.
|
||||
* <p>If this type is not generic, returns an empty list. Note that if the symbol
|
||||
* is unresolved, it is considered non-generic. But it still may have type arguments.
|
||||
*
|
||||
* @see #getTypeArgs()
|
||||
*/
|
||||
List<JTypeVar> getFormalTypeParams();
|
||||
|
||||
|
||||
/**
|
||||
* Returns the substitution mapping the formal type parameters of all
|
||||
* enclosing types to type arguments. If a type is raw, then its type
|
||||
* enclosing types to their type arguments. If a type is raw, then its type
|
||||
* parameters are not part of the returned mapping. Note, that this
|
||||
* does not include type parameters of the supertypes.
|
||||
*
|
||||
* <p>If this type is erased, returns a substitution erasing all type
|
||||
* parameters.
|
||||
*
|
||||
* <p>For instance, in the type {@code List<String>}, this is the substitution mapping
|
||||
* the type parameter {@code T} of {@code interface List<T>} to {@code String}.
|
||||
* It is suitable for use in e.g. {@link JMethodSymbol#getReturnType(Substitution)}.
|
||||
*/
|
||||
Substitution getTypeParamSubst();
|
||||
|
||||
@@ -174,8 +188,7 @@ public interface JClassType extends JTypeMirror {
|
||||
* @throws IllegalArgumentException If the symbol is not a member type
|
||||
* of this type (local/anon classes don't work)
|
||||
* @throws IllegalArgumentException If the type arguments don't match the
|
||||
* type parameters of the symbol (unless they're empty,
|
||||
* in which case the selected type is raw)
|
||||
* type parameters of the symbol (see {@link #withTypeArguments(List)})
|
||||
* @throws IllegalArgumentException If this type is raw and the inner type is not,
|
||||
* or this type is parameterized and the inner type is not
|
||||
*/
|
||||
@@ -248,7 +261,8 @@ public interface JClassType extends JTypeMirror {
|
||||
*
|
||||
* @throws IllegalArgumentException If the type argument list doesn't
|
||||
* match the type parameters of this
|
||||
* type in length
|
||||
* type in length. If the symbol is unresolved,
|
||||
* any number of type arguments is accepted.
|
||||
* @throws IllegalArgumentException If any type of the list is null, or
|
||||
* a primitive type
|
||||
*/
|
||||
|
||||
@@ -186,7 +186,7 @@ public final class JIntersectionType implements JTypeMirror {
|
||||
}
|
||||
} else if (ci instanceof JClassType) {
|
||||
// must be an interface, as per isExclusiveBlabla
|
||||
assert ci.isInterface();
|
||||
assert ci.isInterface() || TypeOps.hasUnresolvedSymbol(ci);
|
||||
} else {
|
||||
throw malformedIntersection(primary, flattened);
|
||||
}
|
||||
|
||||
@@ -91,12 +91,16 @@ public final class TypeConversion {
|
||||
|
||||
/**
|
||||
* Is t convertible to s by boxing/unboxing/widening conversion?
|
||||
* Only t can be undergo conversion.
|
||||
* Only t can undergo conversion.
|
||||
*/
|
||||
public static boolean isConvertibleUsingBoxing(JTypeMirror t, JTypeMirror s) {
|
||||
return isConvertibleCommon(t, s, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is t convertible to s by boxing/unboxing conversion?
|
||||
* Only t can undergo conversion.
|
||||
*/
|
||||
public static boolean isConvertibleInCastContext(JTypeMirror t, JTypeMirror s) {
|
||||
return isConvertibleCommon(t, s, true);
|
||||
}
|
||||
@@ -159,8 +163,6 @@ public final class TypeConversion {
|
||||
List<JTypeMirror> typeArgs = type.getTypeArgs();
|
||||
List<JTypeVar> typeParams = type.getFormalTypeParams();
|
||||
|
||||
assert typeParams.size() == typeArgs.size() : "Type is not well formed " + type + " (expects " + typeParams.size() + " params)";
|
||||
|
||||
// This is the algorithm described at https://docs.oracle.com/javase/specs/jls/se10/html/jls-5.html#jls-5.1.10
|
||||
|
||||
// Let G name a generic type declaration (§8.1.2, §9.1.2)
|
||||
@@ -176,11 +178,14 @@ public final class TypeConversion {
|
||||
|
||||
List<JTypeMirror> freshVars = makeFreshVars(type);
|
||||
|
||||
// types may be non-well formed if the symbol is unresolved
|
||||
// in this case the typeParams list is most likely empty
|
||||
boolean wellFormed = typeParams.size() == freshVars.size();
|
||||
|
||||
// Map of Ai to Si, for the substitution
|
||||
Substitution subst = Substitution.mapping(typeParams, freshVars);
|
||||
Substitution subst = wellFormed ? Substitution.mapping(typeParams, freshVars) : Substitution.EMPTY;
|
||||
|
||||
for (int i = 0; i < typeArgs.size(); i++) {
|
||||
JTypeVar param = typeParams.get(i); // Ai
|
||||
JTypeMirror fresh = freshVars.get(i); // Si
|
||||
JTypeMirror arg = typeArgs.get(i); // Ti
|
||||
|
||||
@@ -191,7 +196,7 @@ public final class TypeConversion {
|
||||
JWildcardType w = (JWildcardType) arg; // Ti alias
|
||||
TypeVarImpl.CapturedTypeVar freshVar = (TypeVarImpl.CapturedTypeVar) fresh; // Si alias
|
||||
|
||||
JTypeMirror prevUpper = param.getUpperBound(); // Ui
|
||||
JTypeMirror prevUpper = wellFormed ? typeParams.get(i).getUpperBound() : ts.OBJECT; // Ui
|
||||
JTypeMirror substituted = TypeOps.subst(prevUpper, subst);
|
||||
|
||||
if (w.isUnbounded()) {
|
||||
|
||||
@@ -387,11 +387,15 @@ public final class TypeOps {
|
||||
} else if (isSpecialUnresolved(t)) {
|
||||
// error type or unresolved type
|
||||
return Convertibility.SUBTYPING;
|
||||
} else if (hasUnresolvedSymbol(t)) {
|
||||
} else if (hasUnresolvedSymbol(t) && t instanceof JClassType) {
|
||||
// This also considers types with an unresolved symbol
|
||||
// subtypes of (nearly) anything. This allows them to
|
||||
// pass bound checks on type variables.
|
||||
return Convertibility.subtypeIf(s instanceof JClassType); // excludes array or so
|
||||
if (Objects.equals(t.getSymbol(), s.getSymbol())) {
|
||||
return typeArgsAreContained((JClassType) t, (JClassType) s);
|
||||
} else {
|
||||
return Convertibility.subtypeIf(s instanceof JClassType); // excludes array or so
|
||||
}
|
||||
} else if (s instanceof JIntersectionType) { // TODO test intersection with tvars & arrays
|
||||
// If S is an intersection, then T must conform to *all* bounds of S
|
||||
// Symmetrically, if T is an intersection, T <: S requires only that
|
||||
@@ -692,6 +696,11 @@ public final class TypeOps {
|
||||
: Convertibility.UNCHECKED_WARNING;
|
||||
}
|
||||
|
||||
if (targs.size() != sargs.size()) {
|
||||
// types are not well-formed
|
||||
return Convertibility.NEVER;
|
||||
}
|
||||
|
||||
Convertibility result = Convertibility.SUBTYPING;
|
||||
for (int i = 0; i < targs.size(); i++) {
|
||||
Convertibility sub = typeArgContains(sargs.get(i), targs.get(i));
|
||||
@@ -1360,8 +1369,8 @@ public final class TypeOps {
|
||||
* this does not check the static modifier, and tests for hiding
|
||||
* if the method is static.
|
||||
*
|
||||
* @param m Method to test
|
||||
* @param origin Site of the potential override
|
||||
* @param m Method to test
|
||||
* @param origin Site of the potential override
|
||||
*/
|
||||
public static boolean isOverridableIn(JExecutableSymbol m, JTypeDeclSymbol origin) {
|
||||
if (m instanceof JConstructorSymbol) {
|
||||
@@ -1664,11 +1673,12 @@ public final class TypeOps {
|
||||
|
||||
// Notice that this loop needs a well-behaved subtyping relation,
|
||||
// i.e. antisymmetric: A <: B && A != B implies not(B <: A)
|
||||
// This is not the case if we include unchecked conversion in there.
|
||||
// This is not the case if we include unchecked conversion in there,
|
||||
// or special provisions for unresolved types.
|
||||
vLoop:
|
||||
for (JTypeMirror v : set) {
|
||||
for (JTypeMirror w : set) {
|
||||
if (!w.equals(v) && isSubtypePure(w, v).bySubtyping()) {
|
||||
if (!w.equals(v) && !hasUnresolvedSymbol(w) && isSubtypePure(w, v).bySubtyping()) {
|
||||
continue vLoop;
|
||||
}
|
||||
}
|
||||
@@ -1936,9 +1946,14 @@ public final class TypeOps {
|
||||
return t == ts.UNKNOWN || t == ts.ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true if the argument is a {@link JClassType} with
|
||||
* {@linkplain JClassSymbol#isUnresolved() an unresolved symbol} or
|
||||
* a {@link JArrayType} whose element type matches the first criterion.
|
||||
*/
|
||||
public static boolean hasUnresolvedSymbol(@Nullable JTypeMirror t) {
|
||||
if (!(t instanceof JClassType)) {
|
||||
return false;
|
||||
return t instanceof JArrayType && hasUnresolvedSymbol(((JArrayType) t).getElementType());
|
||||
}
|
||||
return t.getSymbol() != null && t.getSymbol().isUnresolved();
|
||||
}
|
||||
|
||||
Loaded 30 of 80 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user