Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
1ed9f3d
Simplify names
paganma Feb 23, 2026
efcd263
Implement basic facility for dataflow analysis
paganma Feb 23, 2026
0d9023a
Decouple UniquenessResolver from UniquenessChecker
paganma Feb 23, 2026
bc0b5e5
Remove visitor
paganma Feb 23, 2026
53c10b0
fixup! Decouple UniquenessResolver from UniquenessChecker
paganma Feb 23, 2026
bf1f192
Rename UniqueDeclarationChecker.kt to UniquenessDeclarationChecker.kt
paganma Feb 23, 2026
8cb9195
Implement precise UniquenessType lattice
paganma Feb 24, 2026
009d7e9
Extract enums from UniquenessType
paganma Feb 24, 2026
55a3608
Declare initial and bottom as values
paganma Feb 24, 2026
f211f03
Skeleton for UniquenessGraphChecker and UniquenessAnalyzer implementa…
paganma Feb 24, 2026
d97cbc5
fixup! Skeleton for UniquenessGraphChecker and UniquenessAnalyzer imp…
paganma Feb 24, 2026
89673c5
fixup! Skeleton for UniquenessGraphChecker and UniquenessAnalyzer imp…
paganma Feb 24, 2026
56e7f18
fixup! Skeleton for UniquenessGraphChecker and UniquenessAnalyzer imp…
paganma Feb 24, 2026
017b7a9
Implement local_assignments test
paganma Feb 24, 2026
0a2b365
Evaluate uniqueness types at local assignments
paganma Feb 24, 2026
ce89876
Handle method calls in analysis
paganma Feb 25, 2026
b316636
Extract TypeAssigner
paganma Feb 25, 2026
d754de9
Extract Path from FirProperty
paganma Feb 25, 2026
c9a1592
Branching example
paganma Feb 25, 2026
34fb4df
Looping example
paganma Feb 25, 2026
211036d
Fix format
paganma Feb 25, 2026
ca89175
Check simple locals
paganma Feb 25, 2026
ccc3d4f
Reimplement basic UniquenessTrie structure and methods
paganma Feb 26, 2026
d0f938e
Switch from Map to UniquenessTrie for the flow object
paganma Feb 26, 2026
f1b8165
Add more local_assignments tests
paganma Feb 26, 2026
af69ad9
Integrate UniquenessTrie with UniquenessResolver
paganma Feb 26, 2026
4e042e7
Implement hashCode() function for UniquenessTrie
paganma Feb 26, 2026
aa35df1
Add support for composed paths
paganma Feb 27, 2026
bb56809
Move the right operand of a unique assignment
paganma Feb 27, 2026
ef1ba2e
Rename Free to Captured
paganma Feb 27, 2026
6029629
Rename Captured to Consumed
paganma Feb 27, 2026
713843a
Reimplement original uniqueness-checking semantics
paganma Mar 2, 2026
7ba24c1
Remove unused abstract type
paganma Mar 2, 2026
12d662e
Simplify names, add documentation
paganma Mar 2, 2026
b4f1d2a
Disallow leaking of borrowed values
paganma Mar 2, 2026
a06131b
Test field support
paganma Mar 2, 2026
c5b4e2d
Check all partially-moved and partially-shared arguments
paganma Mar 3, 2026
e144824
Reorganize and add tests
paganma Mar 3, 2026
819f7be
Reorganize and add tests
paganma Mar 3, 2026
857d8c3
Fix test assertions
paganma Mar 3, 2026
67fe2ac
Add tests for borrowing locals
paganma Mar 3, 2026
698c13f
Add tests for borrowing locals
paganma Mar 3, 2026
b7910f6
Merge remote-tracking branch 'origin/marco/uniqueness-dataflow-analys…
paganma Mar 3, 2026
3570a70
Fix formatting
paganma Mar 3, 2026
917dee7
Rename graph local
paganma Mar 3, 2026
ec61489
Test local argument passing
paganma Mar 3, 2026
03a3668
Reorder imports
paganma Mar 3, 2026
3ab86a2
Improve handling of interdependent arguments
paganma Mar 3, 2026
29e93bc
Remove unused import
paganma Mar 3, 2026
8e32760
Correctly check return and throw expressions
paganma Mar 3, 2026
fe5ad43
Remove unused import
paganma Mar 3, 2026
273165d
fixup! Correctly check return and throw expressions
paganma Mar 3, 2026
e06666e
fixup! Correctly check return and throw expressions
paganma Mar 3, 2026
f4bb698
Use different path extraction strategies for receivers and values.
paganma Mar 4, 2026
28167e6
Fix formatting, add documentation
paganma Mar 4, 2026
aac41e9
Only move unique value paths on the right-hand side of the definition
paganma Mar 4, 2026
ba4a281
Improve local names
paganma Mar 4, 2026
166e0c4
Add comments
paganma Mar 4, 2026
bef5c19
Correct tests names
paganma Mar 4, 2026
52de67b
Clarify documentation
paganma Mar 5, 2026
0140083
Use `valuePaths` property instead of directly using the visitor
paganma Mar 5, 2026
1a2aff3
Improve locals names
paganma Mar 5, 2026
f660bd8
Rename local `out` to `errorCollector`
paganma Mar 5, 2026
9fb9935
Update formver.compiler-plugin/plugin/src/org/jetbrains/kotlin/formve…
paganma Mar 5, 2026
e175d7e
Put `resolver` and `initial` local declaration inside try-catch
paganma Mar 5, 2026
62cb786
Split declaration of `callableDeclaration` from `callableSymbol`
paganma Mar 5, 2026
fe29663
Update formver.compiler-plugin/uniqueness/src/org/jetbrains/kotlin/fo…
paganma Mar 5, 2026
b9ec09c
Check casts
paganma Mar 5, 2026
d254b58
Merge remote-tracking branch 'origin/marco/uniqueness-dataflow-analys…
paganma Mar 5, 2026
2dbb020
Use when expressions to simplify checks
paganma Mar 5, 2026
c2b9caf
Use `computeIfAbsent` to fetch `child`
paganma Mar 5, 2026
0822c03
Rename `BorrowLevel`s to `Global` and `Local`
paganma Mar 5, 2026
0856eed
fixup! Rename `BorrowLevel`s to `Global` and `Local`
paganma Mar 5, 2026
bd8f6fe
Avoid using abstract classes in tests
paganma Mar 5, 2026
aeb85b8
Move the individual `valuePaths` based on their individual types.
paganma Mar 5, 2026
1c7657e
Update formver.compiler-plugin/uniqueness/src/org/jetbrains/kotlin/fo…
paganma Mar 5, 2026
9a02a0b
Use smart casts through `when`
paganma Mar 5, 2026
d6c3328
Merge remote-tracking branch 'origin/marco/uniqueness-dataflow-analys…
paganma Mar 5, 2026
664238a
Refactor out logic for checking leaked values
paganma Mar 6, 2026
b450b19
Use helper function for checking leaked values; improve diagnostics p…
paganma Mar 6, 2026
adbc3fd
Refactor out value usage checking
paganma Mar 6, 2026
339b02b
Move `graph` and `errorCollector` declarations inside try-catch
paganma Mar 6, 2026
ac48ffc
Remove all the extra newlines around the body of class declarations
paganma Mar 6, 2026
0c48c5a
Rename `handleDefinition` to `evaluateDefinition`
paganma Mar 6, 2026
036180a
Rename `handleDefinition` to `evaluateDefinition`
paganma Mar 6, 2026
6f3d4c0
Enable `@Unique` and `@Borrowed` to annotate local variables
paganma Mar 6, 2026
c157a4d
Pretty-print uniqueness types
paganma Mar 8, 2026
f6d7ec1
Refactor out common checking logic, use callbacks for reporting errors
paganma Mar 8, 2026
8404956
Set uniqueness after the assignment based on receiver's default
paganma Mar 9, 2026
83dc416
Update tests for new checking logic and diagnostics reporting
paganma Mar 9, 2026
6e477cc
Reorder `visit` methods
paganma Mar 9, 2026
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 @@ -11,10 +11,10 @@ annotation class AlwaysVerify
annotation class DumpExpEmbeddings

// We annotate the function to indicate that the return value is unique
@Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
@Target(AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class Unique

@Target(AnnotationTarget.VALUE_PARAMETER)
@Target(AnnotationTarget.LOCAL_VARIABLE, AnnotationTarget.VALUE_PARAMETER)
annotation class Borrowed

@Target(AnnotationTarget.FUNCTION)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class PluginAdditionalCheckers(session: FirSession, config: PluginConfiguration)

override val declarationCheckers: DeclarationCheckers = object : DeclarationCheckers() {
override val simpleFunctionCheckers: Set<FirSimpleFunctionChecker>
get() = setOf(ViperPoweredDeclarationChecker(session, config), UniqueDeclarationChecker(session, config))
get() = setOf(ViperPoweredDeclarationChecker(session, config), UniquenessDeclarationChecker(session, config))
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,36 @@ import org.jetbrains.kotlin.fir.analysis.checkers.MppCheckerKind
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirSimpleFunctionChecker
import org.jetbrains.kotlin.fir.declarations.FirSimpleFunction
import org.jetbrains.kotlin.fir.resolve.dfa.controlFlowGraph
import org.jetbrains.kotlin.formver.common.ErrorCollector
import org.jetbrains.kotlin.formver.common.PluginConfiguration
import org.jetbrains.kotlin.formver.uniqueness.UniqueCheckVisitor
import org.jetbrains.kotlin.formver.uniqueness.UniqueChecker
import org.jetbrains.kotlin.formver.uniqueness.UniquenessCheckException
import org.jetbrains.kotlin.formver.uniqueness.UniquenessGraphChecker
import org.jetbrains.kotlin.formver.uniqueness.UniquenessResolver
import org.jetbrains.kotlin.formver.uniqueness.UniquenessTrie

class UniquenessDeclarationChecker(private val session: FirSession, private val config: PluginConfiguration) :

class UniqueDeclarationChecker(private val session: FirSession, private val config: PluginConfiguration) :
FirSimpleFunctionChecker(MppCheckerKind.Common) {

context(context: CheckerContext, reporter: DiagnosticReporter)
override fun check(declaration: FirSimpleFunction) {
if (!config.checkUniqueness) return
val errorCollector = ErrorCollector()

try {
val uniqueCheckerContext = UniqueChecker(session, config, errorCollector)
declaration.accept(UniqueCheckVisitor, uniqueCheckerContext)
}
catch (e: UniquenessCheckException) {
val errorCollector = ErrorCollector()
val graph = declaration.controlFlowGraphReference?.controlFlowGraph
?: error("Control flow graph is null for declaration: ${declaration.name}")
val resolver = UniquenessResolver(session)
val initial = UniquenessTrie(resolver)
val graphChecker = UniquenessGraphChecker(session, initial, errorCollector)
graphChecker.check(graph)
} catch (e: UniquenessCheckException) {
reporter.reportOn(e.source, PluginErrors.UNIQUENESS_VIOLATION, e.message)
}
catch (e: Exception) {
} catch (e: Exception) {
val error = e.message ?: "No message provided"
reporter.reportOn(declaration.source, PluginErrors.INTERNAL_ERROR, error)
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -425,51 +425,87 @@ public void testAnnotations() {
}

@Test
@TestMetadata("borrowing.kt")
public void testBorrowing() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/borrowing.kt");
@TestMetadata("assign_local.kt")
public void testAssign_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/assign_local.kt");
}

@Test
@TestMetadata("consume_properties.kt")
public void testConsume_properties() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/consume_properties.kt");
@TestMetadata("assign_property.kt")
public void testAssign_property() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/assign_property.kt");
}

@Test
@TestMetadata("direct_pass_shared_to_unique.kt")
public void testDirect_pass_shared_to_unique() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/direct_pass_shared_to_unique.kt");
@TestMetadata("borrow_local.kt")
public void testBorrow_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/borrow_local.kt");
}

@Test
@TestMetadata("local_same_type.kt")
public void testLocal_same_type() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/local_same_type.kt");
@TestMetadata("borrow_property.kt")
public void testBorrow_property() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/borrow_property.kt");
}

@Test
@TestMetadata("multi_level.kt")
public void testMulti_level() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/multi_level.kt");
@TestMetadata("consume_local.kt")
public void testConsume_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/consume_local.kt");
}

@Test
@TestMetadata("partial_move.kt")
public void testPartial_move() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/partial_move.kt");
@TestMetadata("consume_property.kt")
public void testConsume_property() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/consume_property.kt");
}

@Test
@TestMetadata("partially_shared.kt")
public void testPartially_shared() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/partially_shared.kt");
@TestMetadata("pass_local_argument.kt")
public void testPass_local_argument() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/pass_local_argument.kt");
}

@Test
@TestMetadata("shared_to_shared.kt")
public void testShared_to_shared() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/shared_to_shared.kt");
@TestMetadata("pass_property_argument.kt")
public void testPass_property_argument() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/pass_property_argument.kt");
}

@Test
@TestMetadata("return_local.kt")
public void testReturn_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/return_local.kt");
}

@Test
@TestMetadata("return_property.kt")
public void testReturn_property() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/return_property.kt");
}

@Test
@TestMetadata("share_local.kt")
public void testShare_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/share_local.kt");
}

@Test
@TestMetadata("share_properties.kt")
public void testShare_properties() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/share_properties.kt");
}

@Test
@TestMetadata("throw_local.kt")
public void testThrow_local() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/throw_local.kt");
}

@Test
@TestMetadata("throw_property.kt")
public void testThrow_property() {
runTest("formver.compiler-plugin/testData/diagnostics/uniqueness_checker/throw_property.kt");
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// UNIQUE_CHECK_ONLY

import org.jetbrains.kotlin.formver.plugin.Unique
import org.jetbrains.kotlin.formver.plugin.Borrowed
import org.jetbrains.kotlin.formver.plugin.Unique

class Box(@Unique val a: Any)

fun f_unique(@Unique box: Box) {}
fun f_borrowed(@Borrowed box: Box) {}
fun f_unique_borrowed(@Unique @Borrowed box: Box) {}
fun `unique parameter`(@Unique box: Box) {}

fun `borrowed parameter`(@Borrowed box: Box) {}

fun `unique borrowed parameter`(@Unique @Borrowed box: Box) {}

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/assign_local.kt:(290,291): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_local.kt:(387,388): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared local'.

/assign_local.kt:(598,599): error: Argument uniqueness mismatch: expected 'unique global', actual 'unique local'.

/assign_local.kt:(697,698): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_local.kt:(797,798): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared local'.

/assign_local.kt:(991,992): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_local.kt:(1115,1116): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared local'.

/assign_local.kt:(1254,1255): error: Argument uniqueness mismatch: expected 'unique global', actual 'unique local'.

/assign_local.kt:(1389,1390): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_local.kt:(1532,1533): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared local'.

/assign_local.kt:(1841,1842): error: Argument uniqueness mismatch: expected 'unique global', actual 'unique local'.

/assign_local.kt:(2028,2029): error: Argument uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_local.kt:(2194,2195): error: Assignment uniqueness mismatch: expected 'unique global', actual 'moved'.
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// UNIQUE_CHECK_ONLY

import org.jetbrains.kotlin.formver.plugin.Borrowed
import org.jetbrains.kotlin.formver.plugin.Unique

fun nondet(): Boolean {
return false
}

fun consume(@Unique x: Any) {}

// Var assignments

fun `assign shared`(x: Any) {
var y: Any

y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign borrowed`(@Borrowed x: Any) {
@Borrowed var y: Any

y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign unique`(@Unique x: Any) {
@Unique var y: Any

y = x

consume(y)
}

fun `assign unique-borrowed`(@Unique @Borrowed x: Any) {
@Unique @Borrowed var y: Any

y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

// Var declarations

fun `assign shared in declaration`(x: Any) {
var y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign borrowed in declaration`(@Borrowed x: Any) {
@Borrowed var y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign unique in unique declaration`(@Unique x: Any) {
@Unique var y = x

consume(y)
}

fun `assign unique in shared declaration`(@Unique x: Any) {
var y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign unique-borrowed in borrowed declaration`(@Unique @Borrowed x: Any) {
@Borrowed var y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

fun `assign unique-borrowed in unique-borrowed declaration`(@Unique @Borrowed x: Any) {
@Unique @Borrowed var y = x

consume(<!UNIQUENESS_VIOLATION!>y<!>)
}

// Assignment chaining

fun `chain shared assignments`(x: Any) {
var y: Any
var z: Any

y = x
z = y

consume(<!UNIQUENESS_VIOLATION!>z<!>)
}

fun `chain borrowed assignments`(@Borrowed x: Any) {
@Borrowed var y: Any
@Borrowed var z: Any

y = x
z = y

consume(<!UNIQUENESS_VIOLATION!>z<!>)
}

fun `chain unique assignments`(@Unique x: Any) {
@Unique var y: Any
@Unique var z: Any

y = x
z = y

consume(z)
}

fun `chain unique-borrowed assignments`(@Unique @Borrowed x: Any) {
@Unique @Borrowed var y: Any
@Unique @Borrowed var z: Any

y = x
z = y

consume(<!UNIQUENESS_VIOLATION!>z<!>)
}

// Conditional assignments

fun `assign unique or shared`(@Unique x: Any, y: Any) {
var z: Any;

if (nondet()) {
z = x
} else {
z = y
}

consume(<!UNIQUENESS_VIOLATION!>z<!>)
}

// Looping assignments

fun `assign unique to shared in loop`(@Unique x: Any, @Unique y: Any) {
@Unique var z: Any = y;

while (nondet()) {
z = <!UNIQUENESS_VIOLATION!>x<!>
}

consume(z)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/assign_property.kt:(409,410): error: Assignment uniqueness mismatch: expected 'unique global', actual 'shared global'.

/assign_property.kt:(506,507): error: Assignment uniqueness mismatch: expected 'unique global', actual 'shared local'.

/assign_property.kt:(727,728): error: Assignment uniqueness mismatch: expected 'unique global', actual 'unique local'.
Loading
Loading