mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[java-analysis] IDEA-361307 Provide index to map getter to the field in compiled code
GitOrigin-RevId: 8cf9a9628e922aae75c1366cde4150cf820d023f
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8133c2d2cc
commit
e8a61b4845
+9
@@ -186,6 +186,12 @@ public final class BytecodeAnalysisIndex extends ScalarIndexExtension<HMember> {
|
||||
}
|
||||
writeDataValue(out, effects.returnValue);
|
||||
}
|
||||
else if (rhs instanceof FieldAccess fieldAccess) {
|
||||
out.writeUTF(fieldAccess.name());
|
||||
}
|
||||
else {
|
||||
throw new UnsupportedOperationException("Unsupported result: " + rhs + " in " + eqs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,6 +211,9 @@ public final class BytecodeAnalysisIndex extends ScalarIndexExtension<HMember> {
|
||||
DataValue returnValue = readDataValue(in);
|
||||
results.add(new DirectionResultPair(directionKey, new Effects(returnValue, Set.copyOf(effects))));
|
||||
}
|
||||
else if (direction == Direction.Access) {
|
||||
results.add(new DirectionResultPair(directionKey, new FieldAccess(in.readUTF())));
|
||||
}
|
||||
else {
|
||||
boolean isFinal = in.readBoolean(); // flag
|
||||
if (isFinal) {
|
||||
|
||||
+40
-3
@@ -22,7 +22,7 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.org.objectweb.asm.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
@@ -53,7 +53,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMem
|
||||
static final BinaryOperator<Equations> MERGER =
|
||||
(eq1, eq2) -> eq1.equals(eq2) ? eq1 : new Equations(Collections.emptyList(), false);
|
||||
|
||||
private static final int VERSION = 16; // change when inference algorithm changes
|
||||
private static final int VERSION = 17; // change when inference algorithm changes
|
||||
private static final int VERSION_MODIFIER = HardCodedPurity.AGGRESSIVE_HARDCODED_PURITY ? 1 : 0;
|
||||
private static final int FINAL_VERSION = VERSION * 2 + VERSION_MODIFIER;
|
||||
private static final VirtualFileGist<Map<HMember, Equations>> ourGist = GistManager.getInstance().newVirtualFileGist(
|
||||
@@ -199,7 +199,7 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMem
|
||||
return new Equations(compressedMethodEquations, methodKey.stable);
|
||||
}
|
||||
|
||||
public static Map<EKey, Equations> processClass(final ClassReader classReader, final String presentableUrl) {
|
||||
static Map<EKey, Equations> processClass(final ClassReader classReader, final String presentableUrl) {
|
||||
|
||||
// It is OK to share pending states, actions and results for analyses.
|
||||
// Analyses are designed in such a way that they first write to states/actions/results and then read only those portion
|
||||
@@ -641,6 +641,9 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMem
|
||||
boolean stable,
|
||||
List<? super Equation> result) throws AnalyzerException {
|
||||
Set<Member> fieldsToTrack = method.methodName.equals("<clinit>") ? myStaticFinalFields : Collections.emptySet();
|
||||
if (argumentTypes.length == 0 && !Type.VOID_TYPE.equals(returnType)) {
|
||||
ContainerUtil.addIfNotNull(result, getterEquation(method, graph, stable));
|
||||
}
|
||||
CombinedAnalysis analyzer = new CombinedAnalysis(method, graph, fieldsToTrack);
|
||||
analyzer.analyze();
|
||||
ContainerUtil.addIfNotNull(result, analyzer.outContractEquation(stable));
|
||||
@@ -667,6 +670,40 @@ public class ClassDataIndexer implements VirtualFileGist.GistCalculator<Map<HMem
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Equation getterEquation(@NotNull Member method, @NotNull ControlFlowGraph controlFlow, boolean stable) {
|
||||
MethodNode node = controlFlow.methodNode;
|
||||
boolean isStatic = (node.access & Opcodes.ACC_STATIC) != 0;
|
||||
InsnList instructions = node.instructions;
|
||||
int size = instructions.size();
|
||||
int shift = isStatic ? 0 : 1;
|
||||
if (size != 2 + shift) return null;
|
||||
if (!isReturn(instructions.get(1 + shift))) return null;
|
||||
// isStatic -> GETSTATIC + xRETURN
|
||||
// !isStatic -> ALOAD_0 + GETFIELD + xRETURN
|
||||
if (!isStatic) {
|
||||
if (!(instructions.get(0) instanceof VarInsnNode varAccess) ||
|
||||
varAccess.getOpcode() != Opcodes.ALOAD ||
|
||||
varAccess.var != 0) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (!(instructions.get(shift) instanceof FieldInsnNode fieldAccess) ||
|
||||
fieldAccess.getOpcode() != (isStatic ? Opcodes.GETSTATIC : Opcodes.GETFIELD) ||
|
||||
!fieldAccess.owner.equals(className)) {
|
||||
return null;
|
||||
}
|
||||
String name = fieldAccess.name;
|
||||
if (!isReturn(instructions.get(1 + shift))) return null;
|
||||
return new Equation(new EKey(method, Access, stable), new FieldAccess(name));
|
||||
}
|
||||
|
||||
private static boolean isReturn(AbstractInsnNode insn) {
|
||||
int opcode = insn.getOpcode();
|
||||
return opcode == Opcodes.ARETURN || opcode == Opcodes.DRETURN ||
|
||||
opcode == Opcodes.FRETURN || opcode == Opcodes.IRETURN ||
|
||||
opcode == Opcodes.LRETURN;
|
||||
}
|
||||
|
||||
private void storeStaticFieldEquations(CombinedAnalysis analyzer) {
|
||||
for (Equation equation : analyzer.staticFieldEquations()) {
|
||||
myEquations.put(equation.key,
|
||||
|
||||
@@ -129,7 +129,7 @@ class Equations {
|
||||
}
|
||||
}
|
||||
|
||||
class DirectionResultPair {
|
||||
final class DirectionResultPair {
|
||||
final int directionKey;
|
||||
@NotNull
|
||||
final Result result;
|
||||
@@ -159,7 +159,7 @@ class DirectionResultPair {
|
||||
}
|
||||
}
|
||||
|
||||
interface Result {
|
||||
sealed interface Result permits Effects, FieldAccess, Pending, Value {
|
||||
/**
|
||||
* @return a stream of keys which should be solved to make this result final
|
||||
*/
|
||||
@@ -171,6 +171,14 @@ interface Result {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A result for the {@link Direction#Access} direction:
|
||||
* for setter/constructor parameter: unconditional field set;
|
||||
* for method: unconditional field return
|
||||
* @param name name of the field
|
||||
*/
|
||||
record FieldAccess(String name) implements Result {}
|
||||
|
||||
final class Pending implements Result {
|
||||
final Component @NotNull [] delta; // sum
|
||||
|
||||
|
||||
+2
-1
@@ -10,10 +10,11 @@ public abstract class Direction {
|
||||
public static final Direction Out = explicitDirection("Out");
|
||||
public static final Direction NullableOut = explicitDirection("NullableOut");
|
||||
public static final Direction Pure = explicitDirection("Pure");
|
||||
public static final Direction Access = explicitDirection("Access");
|
||||
public static final Direction Throw = explicitDirection("Throw");
|
||||
public static final Direction Volatile = explicitDirection("Volatile");
|
||||
|
||||
private static final List<Direction> ourConcreteDirections = Arrays.asList(Out, NullableOut, Pure, Throw, Volatile);
|
||||
private static final List<Direction> ourConcreteDirections = Arrays.asList(Out, NullableOut, Pure, Access, Throw, Volatile);
|
||||
private static final int CONCRETE_DIRECTIONS_OFFSET = ourConcreteDirections.size();
|
||||
private static final int IN_OUT_OFFSET = 2; // nullity mask is 0/1
|
||||
private static final int IN_THROW_OFFSET = 2 + Value.values().length;
|
||||
|
||||
+17
@@ -69,6 +69,23 @@ public class ProjectBytecodeAnalysis {
|
||||
nullableMethodTransitivity = Registry.is(NULLABLE_METHOD_TRANSITIVITY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param getter getter method
|
||||
* @return field that this method reads and returns; null if the method is not identified as a getter
|
||||
*/
|
||||
public @Nullable PsiField findFieldForGetter(@NotNull PsiMethod getter) {
|
||||
EKey eKey = getKey(getter);
|
||||
if (eKey == null) return null;
|
||||
EKey accessKey = myEquationProvider.adaptKey(eKey.withDirection(Access));
|
||||
for (Equations equation : myEquationProvider.getEquations(accessKey.member)) {
|
||||
if (equation.find(Access).orElse(null) instanceof FieldAccess access) {
|
||||
PsiClass containingClass = getter.getContainingClass();
|
||||
return containingClass != null ? containingClass.findFieldByName(access.name(), false) : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PsiAnnotation findInferredAnnotation(@NotNull PsiModifierListOwner listOwner, @NotNull String annotationFQN) {
|
||||
if (!(listOwner instanceof PsiCompiledElement)) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.util;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
|
||||
import com.intellij.lang.java.beans.PropertyKind;
|
||||
import com.intellij.lang.jvm.JvmModifier;
|
||||
import com.intellij.psi.*;
|
||||
@@ -29,14 +30,26 @@ public final class PropertyUtil extends PropertyUtilBase {
|
||||
|
||||
@Nullable
|
||||
public static PsiField getFieldOfGetter(PsiMethod method, Supplier<? extends PsiExpression> returnExprSupplier, boolean useIndex) {
|
||||
PsiField field = useIndex && method instanceof PsiMethodImpl && method.isPhysical()
|
||||
? JavaSimplePropertyGistKt.getFieldOfGetter(method)
|
||||
: getSimplyReturnedField(returnExprSupplier.get());
|
||||
PsiField field = getFieldImpl(method, returnExprSupplier, useIndex);
|
||||
if (field == null || !checkFieldLocation(method, field)) return null;
|
||||
final PsiType returnType = method.getReturnType();
|
||||
return returnType != null && field.getType().equals(returnType) ? field : null;
|
||||
}
|
||||
|
||||
private static @Nullable PsiField getFieldImpl(@NotNull PsiMethod method,
|
||||
@NotNull Supplier<? extends PsiExpression> returnExprSupplier,
|
||||
boolean useIndex) {
|
||||
if (useIndex) {
|
||||
if (PsiUtil.preferCompiledElement(method) instanceof PsiMethod compiledMethod) {
|
||||
return ProjectBytecodeAnalysis.getInstance(method.getProject()).findFieldForGetter(compiledMethod);
|
||||
}
|
||||
if (method instanceof PsiMethodImpl && method.isPhysical()) {
|
||||
return JavaSimplePropertyGistKt.getFieldOfGetter(method);
|
||||
}
|
||||
}
|
||||
return getSimplyReturnedField(returnExprSupplier.get());
|
||||
}
|
||||
|
||||
public static boolean isSimpleGetter(@Nullable PsiMethod method) {
|
||||
//noinspection TestOnlyProblems
|
||||
return isSimpleGetter(method, true);
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import java.util.*;
|
||||
|
||||
class Test {
|
||||
void testVersion() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
Runtime.Version version = runtime.version();
|
||||
if (version.build().isPresent()) {
|
||||
unknown();
|
||||
if (<warning descr="Condition 'version.build().isPresent()' is always 'true'">version.build().isPresent()</warning>) {
|
||||
}
|
||||
if (<warning descr="Condition 'runtime == Runtime.getRuntime()' is always 'true'">runtime == Runtime.getRuntime()</warning>) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
native void unknown();
|
||||
}
|
||||
@@ -140,4 +140,8 @@ public class DataFlowInspection21Test extends DataFlowInspectionTestCase {
|
||||
addJetBrainsNotNullByDefault(myFixture);
|
||||
doTest();
|
||||
}
|
||||
|
||||
public void testClassFileGetter() {
|
||||
doTest();
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.java.codeInspection.bytecodeAnalysis;
|
||||
|
||||
import com.intellij.codeInspection.bytecodeAnalysis.ProjectBytecodeAnalysis;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.testFramework.LightProjectDescriptor;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public final class BytecodeAccessorTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
public void testAccessors() {
|
||||
PsiClass psiClass =
|
||||
JavaPsiFacade.getInstance(getProject()).findClass("java.util.OptionalInt", GlobalSearchScope.allScope(getProject()));
|
||||
assertNotNull(psiClass);
|
||||
assertInstanceOf(psiClass, PsiCompiledElement.class);
|
||||
PsiMethod isPresentMethod = psiClass.findMethodsByName("isPresent", false)[0];
|
||||
assertFalse(isPresentMethod.hasModifierProperty(PsiModifier.STATIC));
|
||||
PsiMethod emptyMethod = psiClass.findMethodsByName("empty", false)[0];
|
||||
assertTrue(emptyMethod.hasModifierProperty(PsiModifier.STATIC));
|
||||
ProjectBytecodeAnalysis analysis = ProjectBytecodeAnalysis.getInstance(getProject());
|
||||
PsiField isPresentField = analysis.findFieldForGetter(isPresentMethod);
|
||||
assertNotNull(isPresentField);
|
||||
PsiField emptyField = analysis.findFieldForGetter(emptyMethod);
|
||||
assertNotNull(emptyField);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NotNull LightProjectDescriptor getProjectDescriptor() {
|
||||
return JAVA_21;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -169,7 +169,7 @@ public class BytecodeAnalysisTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
fail(message + ": @NotNull inferred, but not expected");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void checkCompoundIds(String className) throws IOException {
|
||||
GlobalSearchScope scope = GlobalSearchScope.moduleWithLibrariesScope(getModule());
|
||||
PsiClass psiClass = JavaPsiFacade.getInstance(getProject()).findClass(PACKAGE_NAME + '.' + className, scope);
|
||||
|
||||
Reference in New Issue
Block a user