Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.junit.jupiter.api.Test;
import org.sonar.java.checks.verifier.CheckVerifier;
import org.sonar.java.checks.verifier.TestUtils;
import org.sonar.java.model.SemanticValues;
import org.sonar.java.model.declaration.VariableTreeImpl;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.VariableTree;
Expand Down Expand Up @@ -64,7 +65,7 @@ private static class EraseSymbols extends BaseTreeVisitor {
@Override
public void visitVariable(VariableTree tree) {
if (tree instanceof VariableTreeImpl varImpl) {
varImpl.variableBinding = null;
varImpl.variableSymbol = SemanticValues.UNKNOWN_SYMBOL;
}
super.visitVariable(tree);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ public void simpleScan(InputFile inputFile, JParserConfig.Result result, Consume
modifyCompilationUnit.accept(ast);
visitor.visitFile(ast, sonarComponents != null && sonarComponents.fileCanBeSkipped(inputFile));
String path = inputFile.toString();
Set<JProblem> undefinedTypes = ast.sema.undefinedTypes();
Set<JProblem> undefinedTypes = ast.undefinedTypes();
collectUndefinedTypes(path, undefinedTypes);
cleanUp.accept(ast);
telemetryAnalysisKeys = javaAnalysisKeys.success();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,18 @@
*/
package org.sonar.java.model;

import org.eclipse.jdt.core.dom.ITypeBinding;
import java.util.function.Supplier;
import org.sonar.plugins.java.api.semantic.Type;

import javax.annotation.Nullable;

/**
* This class is intended for internal use during semantic analysis and should not be used in checks.
*/
public abstract class AbstractTypedTree extends JavaTree {

@Nullable
public ITypeBinding typeBinding;
public Supplier<Type> type = SemanticValues.UNKNOWN_TYPE;

public Type symbolType() {
return typeBinding != null
? root.sema.type(typeBinding)
: Type.UNKNOWN;
return type.get();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.model;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.CompilationUnit;

final class EcjDiagnosticProvider {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this onex


private static final Map<JProblem.Type, Integer> IDS = Map.of(
JProblem.Type.UNDEFINED_TYPE, IProblem.UndefinedType,
JProblem.Type.PREVIEW_FEATURE_USED, IProblem.PreviewFeatureUsed,
JProblem.Type.UNUSED_IMPORT, IProblem.UnusedImport,
JProblem.Type.REDUNDANT_CAST, IProblem.UnnecessaryCast,
JProblem.Type.ASSIGNMENT_HAS_NO_EFFECT, IProblem.AssignmentHasNoEffect,
JProblem.Type.MASKED_CATCH, IProblem.MaskedCatch);

private EcjDiagnosticProvider() {
}

static Set<String> compilerOptions() {
return Set.of(
JavaCore.COMPILER_PB_UNUSED_IMPORT,
JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK,
JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT,
JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK
);
}

static List<JWarning> warnings(CompilationUnit ast, LineColumnConverter positions) {
return Stream.of(ast.getProblems())
.map(problem -> convert(problem, positions))
.filter(Objects::nonNull)
.toList();
}

private static JWarning convert(IProblem problem, LineColumnConverter positions) {
for (JProblem.Type type : JProblem.Type.values()) {
if (problem.getID() == IDS.get(type)) {
LineColumnConverter.Pos start = positions.toPos(problem.getSourceStart());
LineColumnConverter.Pos end = positions.toPos(problem.getSourceEnd());
return new JWarning(problem.getMessage(), type, start.line(), start.columnOffset(), end.line(), end.columnOffset());
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.model;

import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.eclipse.jdt.core.dom.IBinding;

final class EcjSemanticAttachment<B extends IBinding, T> implements Supplier<T> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If i'm correct we can constrain T to Symbol and its subclasses only

Suggested change
final class EcjSemanticAttachment<B extends IBinding, T> implements Supplier<T> {
final class EcjSemanticAttachment<B extends IBinding, T extends Symbol> implements Supplier<T> {


final B binding;
private final Function<B, T> factory;
private T value;

EcjSemanticAttachment(B binding, Function<B, T> factory) {
this.binding = binding;
this.factory = factory;
}

@Override
public T get() {
if (value == null) {
value = Objects.requireNonNull(factory.apply(binding));
}
return value;
}

@Nullable
static IBinding getBinding(Supplier<?> attachment) {
return attachment instanceof EcjSemanticAttachment<?, ?> ecj ? ecj.binding : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.model;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance;
import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationValue;

final class EcjSymbolMetadata {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class has no superclass, as the goal of this effort is to wrap everything behind generic interfaces/abstract classes/api not ecj-aware, this class should extend a generic one, and this raw type should never be used except for construction (can be done in another PR)


private EcjSymbolMetadata() {
}

static JSymbolMetadata declaration(JSema sema, Symbol owner, IAnnotationBinding[] bindings) {
return metadata(sema, owner, bindings, new JSymbolMetadata[0], false);
}

static JSymbolMetadata of(JSema sema, Symbol owner, ITypeBinding type, IAnnotationBinding[] externalAnnotations) {
var symbolAnnotations = new IAnnotationBinding[externalAnnotations.length + type.getTypeAnnotations().length];
System.arraycopy(externalAnnotations, 0, symbolAnnotations, 0, externalAnnotations.length);
System.arraycopy(type.getTypeAnnotations(), 0, symbolAnnotations, externalAnnotations.length, type.getTypeAnnotations().length);

var parameterMetaDatas = Arrays.stream(type.getTypeArguments())
.map(param -> ofTypeArg(sema, owner, param))
.toArray(JSymbolMetadata[]::new);

return metadata(sema, owner, symbolAnnotations, parameterMetaDatas, false);
}

private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding param) {
var symbolAnnotations = param.getTypeAnnotations();
var parameterMetaDatas = Arrays.stream(param.getTypeArguments())
.map(p -> ofTypeArg(sema, owner, p))
.toArray(JSymbolMetadata[]::new);
return metadata(sema, owner, symbolAnnotations, parameterMetaDatas, true);
}

private static JSymbolMetadata metadata(JSema sema, Symbol owner, IAnnotationBinding[] bindings, JSymbolMetadata[] parameters, boolean typeArgument) {
return new JSymbolMetadata(owner, () -> Arrays.stream(bindings).map(sema::annotation).collect(Collectors.toList()), parameters, typeArgument);
}

static final class JAnnotationInstance implements AnnotationInstance {
private final JSema sema;
private final IAnnotationBinding annotationBinding;

/**
* Cache for {@link #values()}.
*/
private List<AnnotationValue> values;

JAnnotationInstance(JSema sema, IAnnotationBinding annotationBinding) {
this.sema = sema;
this.annotationBinding = annotationBinding;
}

@Override
public Symbol symbol() {
return sema.typeSymbol(annotationBinding.getAnnotationType());
}

@Override
public List<AnnotationValue> values() {
if (values == null) {
values = Arrays.stream(annotationBinding.getDeclaredMemberValuePairs())
.map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(p.getValue())))
.collect(Collectors.toList());
}
return values;
}

private Object convertAnnotationValue(Object value) {
return EcjSymbolMetadata.convertAnnotationValue(sema, value);
}
}

static Object convertAnnotationValue(JSema sema, Object value) {
return switch (value) {
case IVariableBinding iVariableBinding -> sema.variableSymbol(iVariableBinding);
case ITypeBinding iTypeBinding -> sema.typeSymbol(iTypeBinding);
case IAnnotationBinding iAnnotationBinding -> sema.annotation(iAnnotationBinding);
case Object[] a -> {
Object[] result = new Object[a.length];
for (int i = 0; i < a.length; i++) {
result[i] = convertAnnotationValue(sema, a[i]);
}
yield result;
}
case null, default -> value;
};
}
}
Comment on lines +91 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These 2 return types are too broad to me, the return type could be symbol to be bounded and easier to understand

101 changes: 101 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/model/EcjTreeBindings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program 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 Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.model;

import java.util.function.Supplier;
import javax.annotation.Nullable;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.sonar.java.model.declaration.ClassTreeImpl;
import org.sonar.java.model.declaration.MethodTreeImpl;
import org.sonar.java.model.declaration.VariableTreeImpl;
import org.sonar.java.model.expression.IdentifierTreeImpl;
import org.sonar.java.model.expression.LambdaExpressionTreeImpl;
import org.sonar.java.model.expression.MethodInvocationTreeImpl;
import org.sonar.java.model.pattern.AbstractPatternTree;

final class EcjTreeBindings {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for this one


private EcjTreeBindings() {
}

@Nullable
static ITypeBinding typeBinding(Object tree) {
return switch (tree) {
case AbstractTypedTree t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.type);
case ClassTreeImpl t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.typeSymbol);
case TypeParameterTreeImpl t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.typeSymbol);
case AbstractPatternTree t -> (ITypeBinding) EcjSemanticAttachment.getBinding(t.type);
default -> throw new IllegalArgumentException("No ECJ type attachment: " + tree.getClass());
};
}

@Nullable
static IMethodBinding methodBinding(Object tree) {
Supplier<?> attachment = switch (tree) {
case MethodTreeImpl t -> t.methodSymbol;
case MethodInvocationTreeImpl t -> t.methodSymbolSupplier;
case LambdaExpressionTreeImpl t -> t.methodSymbol;
default -> throw new IllegalArgumentException("No ECJ method attachment: " + tree.getClass());
};
return (IMethodBinding) EcjSemanticAttachment.getBinding(attachment);
}

@Nullable
static IVariableBinding variableBinding(VariableTreeImpl tree) {
return (IVariableBinding) EcjSemanticAttachment.getBinding(tree.variableSymbol);
}

@Nullable
static IBinding binding(Object tree) {
return switch (tree) {
case IdentifierTreeImpl t -> EcjSemanticAttachment.getBinding(t.symbolSupplier);
case JavaTree.ImportTreeImpl t -> EcjSemanticAttachment.getBinding(t.symbolSupplier);
default -> throw new IllegalArgumentException("No ECJ symbol attachment: " + tree.getClass());
};
}

static void setType(JSema sema, JavaTree tree, @Nullable ITypeBinding binding) {
switch (tree) {
case AbstractTypedTree t -> t.type = sema.typeSupplier(binding);
case ClassTreeImpl t -> t.typeSymbol = binding == null ? SemanticValues.UNKNOWN_TYPE_SYMBOL : new EcjSemanticAttachment<>(binding, sema::typeSymbol);
case TypeParameterTreeImpl t -> t.typeSymbol = sema.symbolSupplier(binding);
default -> throw new IllegalArgumentException("No type attachment: " + tree.getClass());
}
if (tree instanceof KeywordThis t) {
t.symbolSupplier = binding == null ? SemanticValues.UNKNOWN_SYMBOL : new EcjSemanticAttachment<>(binding, b -> sema.typeSymbol(b).thisSymbol);
} else if (tree instanceof KeywordSuper t) {
t.symbolSupplier = binding == null ? SemanticValues.UNKNOWN_SYMBOL : new EcjSemanticAttachment<>(binding, b -> sema.typeSymbol(b).superSymbol);
}
}

static void setSymbol(JSema sema, JavaTree tree, @Nullable IBinding binding) {
switch (tree) {
case MethodTreeImpl t -> t.methodSymbol = sema.methodSupplier((IMethodBinding) binding);
case MethodInvocationTreeImpl t -> t.methodSymbolSupplier = sema.methodSupplier((IMethodBinding) binding);
case LambdaExpressionTreeImpl t -> t.methodSymbol = sema.methodSupplier((IMethodBinding) binding);
case VariableTreeImpl t -> t.variableSymbol = sema.symbolSupplier(binding);
case IdentifierTreeImpl t -> t.symbolSupplier = sema.symbolSupplier(binding);
case JavaTree.ImportTreeImpl t -> t.symbolSupplier = (binding == null || binding.getKind() == IBinding.PACKAGE || binding.getKind() == IBinding.MODULE)
? () -> null
: sema.symbolSupplier(binding);
default -> throw new IllegalArgumentException("No symbol attachment: " + tree.getClass());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ IMethodBinding methodBinding() {
return (IMethodBinding) binding;
}

@Override
@Nullable
public Object defaultValue() {
return EcjSymbolMetadata.convertAnnotationValue(sema, methodBinding().getDefaultValue());
}
Comment on lines +73 to +77

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the return type could also be bounded to Symbol


public boolean isLambda() {
return methodBinding().getDeclaringMember() != null;
}
Expand Down
Loading
Loading