Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
71a8486
Rework field assignability
maximilianruesch Oct 22, 2025
dfd7647
Disable simple string model test for now
maximilianruesch Oct 29, 2025
7024efe
Correctly refresh assignability on new TAC
maximilianruesch Oct 29, 2025
09d026e
Perform soundness check
maximilianruesch Oct 29, 2025
0890e33
Fix call to lazy initializer pattern
maximilianruesch Oct 29, 2025
30b8e01
Enable more lazy initialization tests
maximilianruesch Oct 29, 2025
b7ca155
Disable failing test
maximilianruesch Nov 12, 2025
de44137
Slightly more efficient lazy init check
maximilianruesch Nov 12, 2025
6f4f303
Extract lazy init and clone pattern functions
maximilianruesch Nov 12, 2025
676989d
Make analysis of non-initializer reads in clone pattern symmetric ins…
maximilianruesch Nov 12, 2025
f75f910
Allow initializer writes for lazy init pattern
maximilianruesch Nov 12, 2025
ec3d93a
Remove L1 from reference conf for now
maximilianruesch Nov 12, 2025
18d664b
Fix support for reading static fields in constructors
maximilianruesch Nov 12, 2025
2890d77
Merge branch 'develop' into rework/field-assignability
maximilianruesch Nov 12, 2025
4adf80c
Fix formatting
maximilianruesch Nov 12, 2025
45ebe3e
Fix immutability values
maximilianruesch Nov 13, 2025
67c58da
Extract read to write path analysis part
maximilianruesch Nov 13, 2025
2de90e2
Completely rely on part system
maximilianruesch Nov 13, 2025
7c226f3
Initialize L0 with simple read write path analysis
maximilianruesch Nov 13, 2025
b6755d2
Reenable L1 assignability analysis
maximilianruesch Nov 13, 2025
9ba7688
Fix formatting
maximilianruesch Nov 13, 2025
b8f72f9
Fix documentation of clone pattern analysis
maximilianruesch Nov 13, 2025
1a402dc
Add more elaborate documentation
maximilianruesch Nov 13, 2025
91b8289
Merge branch 'develop' into rework/field-assignability
maximilianruesch Nov 13, 2025
b8555c3
Refine support for interprocedural read -> write path analysis
maximilianruesch Nov 13, 2025
14ea7d2
Collate read write path analysis into single trait
maximilianruesch Nov 13, 2025
03099b3
Add class constant lazy init tests
maximilianruesch Nov 14, 2025
1a5755a
Add test for multiple writes
maximilianruesch Nov 14, 2025
d6ff13f
Clarify read write path analysis
maximilianruesch Nov 17, 2025
eeb2bec
Rework the test framework
maximilianruesch Nov 17, 2025
177feae
Check precision of tested values strictly increases
maximilianruesch Nov 17, 2025
f5dbfe4
Remove unused property
maximilianruesch Nov 17, 2025
0d78235
Remove L0 from lazy init tests
maximilianruesch Nov 17, 2025
8a83fa4
Refactor analysis part framework to be inheritance based
maximilianruesch Nov 17, 2025
1859d08
Fix mixed up field assignability schedulers
maximilianruesch Nov 17, 2025
214562a
Make L0 use the slightly more sophisticated read write path analysis …
maximilianruesch Nov 17, 2025
eaeb9e6
Rename test
maximilianruesch Nov 17, 2025
d46ca5b
Fix formatting
maximilianruesch Nov 18, 2025
35b1c89
Support side by side constructors via call graphs
maximilianruesch Nov 18, 2025
a1dc7d3
Recognize getClass calls as safe
maximilianruesch Nov 29, 2025
f96236d
Add advanced counter examples
maximilianruesch Nov 29, 2025
7c60006
Remove unsound callee based read write path analysis
maximilianruesch Nov 29, 2025
6705466
Remove unused simple read write analysis
maximilianruesch Nov 29, 2025
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
@@ -0,0 +1,29 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.assignability;

import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField;
import org.opalj.fpcf.properties.immutability.field_assignability.EffectivelyNonAssignableField;

class ArbitraryCallInConstructor {

@AssignableField("The presence of arbitrary calls prevents guaranteeing that no read-write paths exist.")
private boolean value;

public ArbitraryCallInConstructor(boolean v) {
this.arbitraryCallee();
this.value = v;
}

public void arbitraryCallee() {}
}

class GetClassInConstructor {

@EffectivelyNonAssignableField("The field is only assigned once in its own constructor.")
private boolean value;

public GetClassInConstructor(boolean v) {
System.out.println(this.getClass());
this.value = v;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.assignability;

import org.opalj.fpcf.properties.immutability.field_assignability.NonAssignableField;

/**
* A case of a "desugared enum" usage. The Java 8 (bytecode 52.0) compiler generates a switch table in a synthetic inner
* class that is populated in a static initializer. As such, the enum values are static fields that are written in their
* own static initializer and read in another static initializer.
*/
public class DesugaredEnumUsage {

enum MyDesugaredEnum {
@NonAssignableField("Static fields generated to hold enum value singletons are final")
SOME_VALUE,

@NonAssignableField("Static fields generated to hold enum value singletons are final")
SOME_OTHER_VALUE
}

void iDesugarYourEnum(MyDesugaredEnum e) {
switch (e) {
case SOME_VALUE:
System.out.println("Some value found!");
case SOME_OTHER_VALUE:
System.out.println("Some other value found!");
default:
System.err.println("No value found!");
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,21 @@ public void callNopOfClassWithMutableFields(){
@EffectivelyNonAssignableField("The field is not written after initialization")
private HashMap<Object, Object> effectivelyNonAssignableHashMap = new HashMap<Object, Object>();
}

class MultiWritesInConstructor {
@EffectivelyNonAssignableField("The field is only observed with one value")
private boolean ignoreAutoboxing;

public MultiWritesInConstructor() {
this(false);
}

public MultiWritesInConstructor(boolean ignoreAutoboxing) {
this.ignoreAutoboxing = false;
this.ignoreAutoboxing = ignoreAutoboxing;
}

public boolean isIgnoringAutoboxing() {
return this.ignoreAutoboxing;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ class InitializationInConstructorAssignable {

@AssignableField("The field is written everytime it is passed to the constructor of another instance.")
private InitializationInConstructorAssignable child;

public InitializationInConstructorAssignable(InitializationInConstructorAssignable parent) {
parent.child = this;
}
}
}

class InitializationInConstructorNonAssignable {

@EffectivelyNonAssignableField("The field is only assigned once in its own constructor.")
private InitializationInConstructorNonAssignable parent;

public InitializationInConstructorNonAssignable(InitializationInConstructorNonAssignable parent) {
this.parent = parent.parent;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples;

import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField;

/**
* The default value of the field x is assigned to another field n during construction and as
* a result seen with two different values.
*/
public class PrematurelyReadFinalField {

@AssignableField("Field n is assigned with different values.")
static int n = 5;

public static void main(String[] args) {
C c = new C();
}
}

class B {
B() {
PrematurelyReadFinalField.n = ((C) this).x;
}
}

class C extends B {

@AssignableField("Is seen with two different values during construction.")
public final int x;

C() {
super();
x = 3;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples;

import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField;

/**
* This test case simulates the fact that the `this` object escapes in the constructor before (final) fields
* are assigned.
*/
public class ThisEscapesDuringConstruction {

@AssignableField("The this object escapes in the constructor before the field is assigned.")
final int n;

public ThisEscapesDuringConstruction() {
C2.m(this);
n = 7;
}
}

class C2 {
public static void m(ThisEscapesDuringConstruction c) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.assignability.advanced_counter_examples;

import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField;

/**
* The value of the field x is read with its default value (0)
* in the constructor before assignment and assigned to a public field.
* Thus, the value can be accessed from everywhere.
*/
public class ValueReadBeforeAssignment {
@AssignableField("Field value is read before assignment.")
private int x;
@AssignableField("Field y is public and not final.")
public int y;

public ValueReadBeforeAssignment() {
y = x;
x = 42;
}

public ValueReadBeforeAssignment foo() {
return new ValueReadBeforeAssignment();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.opalj.fpcf.properties.immutability.fields.TransitivelyImmutableField;
import org.opalj.fpcf.properties.immutability.field_assignability.EffectivelyNonAssignableField;
import org.opalj.fpcf.properties.immutability.types.TransitivelyImmutableType;
import org.opalj.tac.fpcf.analyses.fieldassignability.L2FieldAssignabilityAnalysis;

/**
* This class encompasses different possible cases of the clone pattern.
Expand All @@ -15,7 +16,8 @@
public final class SimpleClonePattern {

@TransitivelyImmutableField("Field is effectively non assignable and has a primitive type")
@EffectivelyNonAssignableField("Field is only assigned ones due to the clone function pattern")
@EffectivelyNonAssignableField(value = "Field is only assigned once due to the clone function pattern",
analyses = { L2FieldAssignabilityAnalysis.class })
private int i;

public SimpleClonePattern clone(){
Expand All @@ -28,7 +30,8 @@ public SimpleClonePattern clone(){
class CloneNonAssignableWithNewObject {

@TransitivelyImmutableField("field is effectively non assignable and assigned with a transitively immutable object")
@EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern")
@EffectivelyNonAssignableField(value = "field is only assigned once due to the clone function pattern",
analyses = { L2FieldAssignabilityAnalysis.class })
private Integer integer;

public CloneNonAssignableWithNewObject clone(){
Expand All @@ -41,7 +44,8 @@ public CloneNonAssignableWithNewObject clone(){
class EscapesAfterAssignment {

@TransitivelyImmutableField("field is effectively non assignable and assigned with a transitively immutable object")
@EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern")
@EffectivelyNonAssignableField(value = "field is only assigned once due to the clone function pattern",
analyses = { L2FieldAssignabilityAnalysis.class })
private Integer integer;

private Integer integerCopy;
Expand All @@ -59,11 +63,13 @@ public EscapesAfterAssignment clone(){
final class MultipleFieldsAssignedInCloneFunction {

@TransitivelyImmutableField("The field is effectively non assignable and has a transitively immutable type")
@EffectivelyNonAssignableField("The field is only assigned once in the clone function")
@EffectivelyNonAssignableField(value = "The field is only assigned once in the clone function",
analyses = { L2FieldAssignabilityAnalysis.class })
private Integer firstInteger;

@TransitivelyImmutableField("The field is effectively non assignable and has a transitively immutable type")
@EffectivelyNonAssignableField("The field is only assigned once in the clone function")
@EffectivelyNonAssignableField(value = "The field is only assigned once in the clone function",
analyses = { L2FieldAssignabilityAnalysis.class })
private Integer secondInteger;

public MultipleFieldsAssignedInCloneFunction clone(){
Expand All @@ -77,7 +83,8 @@ public MultipleFieldsAssignedInCloneFunction clone(){
class ConstructorWithParameter {

@TransitivelyImmutableField("field is effectively non assignable and has a transitively immutable type")
@EffectivelyNonAssignableField("field is only assigned ones due to the clone function pattern")
@EffectivelyNonAssignableField(value = "field is only assigned once due to the clone function pattern",
analyses = { L2FieldAssignabilityAnalysis.class })
private Integer integer;

public ConstructorWithParameter(Integer integer){
Expand All @@ -94,7 +101,8 @@ public ConstructorWithParameter clone(Integer integer){

class CloneNonAssignableArrayWithRead {

@EffectivelyNonAssignableField("field is only assigned once due to the clone function pattern")
@EffectivelyNonAssignableField(value = "field is only assigned once due to the clone function pattern",
analyses = { L2FieldAssignabilityAnalysis.class })
private boolean[] booleans;

public CloneNonAssignableArrayWithRead clone(){
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.general;

import org.opalj.fpcf.properties.immutability.classes.TransitivelyImmutableClass;
import org.opalj.fpcf.properties.immutability.field_assignability.NonAssignableField;
import org.opalj.fpcf.properties.immutability.fields.NonTransitivelyImmutableField;
import org.opalj.fpcf.properties.immutability.types.MutableType;

@MutableType("The type is extensible")
@TransitivelyImmutableClass("Class has no instance fields")
public class StaticFieldWithDefaultValue {

@NonTransitivelyImmutableField("Field is not assignable")
@NonAssignableField("The field is public, final, and its value is only set once in the static initializer")
public static final Object DEFAULT = new Object();

public StaticFieldWithDefaultValue() {
System.out.println(StaticFieldWithDefaultValue.DEFAULT);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
*/
public class DifferentLazyInitializedFieldTypes {

@TransitivelyImmutableField("Lazy initialized field with primitive type.")
@LazilyInitializedField("field is thread safely lazy initialized")
@TransitivelyImmutableField("Lazy initialized field with primitive type.")
@LazilyInitializedField("field is thread safely lazy initialized")
private int inTheGetterLazyInitializedIntField;

public synchronized int getInTheGetterLazyInitializedIntField(){
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* BSD 2-Clause License - see OPAL/LICENSE for details. */
package org.opalj.fpcf.fixtures.immutability.openworld.lazyinitialization.pre_bc_52_class_constant;

import org.opalj.fpcf.properties.immutability.field_assignability.AssignableField;
import org.opalj.fpcf.properties.immutability.field_assignability.LazilyInitializedField;
import org.opalj.fpcf.properties.immutability.field_assignability.UnsafelyLazilyInitializedField;
import org.opalj.tac.fpcf.analyses.fieldassignability.L2FieldAssignabilityAnalysis;

/**
* Contains lazy initializations of class constants as produced by compilers before class literal support was officially
* introduced, i.e. before Java 5 (Bytecode version 49.0). Before class literals became part of the constant pool, they
* used a synthetic singleton pattern, with inlined lazy initialization at the usage site.
*/
public class GeneratedClassConstantLazyInit {

private class ClassA {}

static Class<ClassA> class$(String className) {
try {
return (Class<ClassA>) Class.forName(className);
} catch (ClassNotFoundException e) {
return null;
}
}

@LazilyInitializedField(value = "A well behaved class loader returns singleton class instances (see JVM spec 5.3), and static initializers are inherently thread safe",
analyses = {})
@AssignableField(value = "The analysis only recognizes lazy init patterns in instance methods",
analyses = { L2FieldAssignabilityAnalysis.class })
private static Class<ClassA> class$lazyInitInStaticInitializer;

static {
Class<ClassA> instance = class$lazyInitInStaticInitializer == null
? (class$lazyInitInStaticInitializer = class$("string1"))
: class$lazyInitInStaticInitializer;
}

@LazilyInitializedField(value = "A well behaved class loader returns singleton class instances (see JVM spec 5.3)",
analyses = {})
@UnsafelyLazilyInitializedField(value = "The analysis does currently not incorporate singleton information",
analyses = { L2FieldAssignabilityAnalysis.class })
private static Class<ClassA> class$lazyInitInInstanceMethod;

void iUseClassConstantsOnce() {
Class<ClassA> instance = class$lazyInitInInstanceMethod == null
? (class$lazyInitInInstanceMethod = class$("string2"))
: class$lazyInitInInstanceMethod;
}

@LazilyInitializedField(value = "A well behaved class loader returns singleton class instances (see JVM spec 5.3)",
analyses = {})
@AssignableField(value = "Multiple lazy initialization patterns for the same field are not supported",
analyses = { L2FieldAssignabilityAnalysis.class })
private static Class<ClassA> class$multiPlaceLazyInit;

void iUseClassConstantsTwice() {
Class<ClassA> instance = class$multiPlaceLazyInit == null
? (class$multiPlaceLazyInit = class$("string3"))
: class$multiPlaceLazyInit;

Class<ClassA> other = class$multiPlaceLazyInit == null
? (class$multiPlaceLazyInit = class$("string4"))
: class$multiPlaceLazyInit;
}
}
Loading