-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
@DisallowLambdaCapture
checker example
#22
Open
raulraja
wants to merge
11
commits into
main
Choose a base branch
from
rr-disallow-lambda-capture
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+201
−29
Open
Changes from 10 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
2a297ed
Progress unfinished on reflect branch
raulraja dd23f07
add failing test
raulraja ab0019c
Add immutable example, remove Reflect example
raulraja c00389a
remove chatgpt demo dep
raulraja 7713933
Merge branch 'main' into rr-cleanup
raulraja dd12be2
disallow lambda capture, prevents raise from leaking
raulraja fa18304
lookup all parents that are anonymous functions to avoid leaks
raulraja d4cf1f3
cleanup
raulraja acfd28e
Merge branch 'main' into rr-disallow-lambda-capture
raulraja 60dcb5f
adapt API to latest compiler snapshot
raulraja c1e767b
Merge branch 'main' into rr-disallow-lambda-capture
raulraja File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
60 changes: 60 additions & 0 deletions
60
arrow-reflect-annotations/src/main/kotlin/arrow/meta/samples/DisallowLambdaCapture.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package arrow.meta.samples | ||
|
||
import arrow.meta.Diagnostics | ||
import arrow.meta.FirMetaCheckerContext | ||
import arrow.meta.Meta | ||
import arrow.meta.samples.DisallowLambdaCaptureErrors.UnsafeCaptureDetected | ||
import org.jetbrains.kotlin.fir.FirSession | ||
import org.jetbrains.kotlin.fir.declarations.FirAnonymousFunction | ||
import org.jetbrains.kotlin.fir.declarations.InlineStatus | ||
import org.jetbrains.kotlin.fir.declarations.findArgumentByName | ||
import org.jetbrains.kotlin.fir.declarations.getAnnotationByClassId | ||
import org.jetbrains.kotlin.fir.expressions.FirAnnotation | ||
import org.jetbrains.kotlin.fir.expressions.FirConstExpression | ||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall | ||
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol | ||
import org.jetbrains.kotlin.name.ClassId | ||
import org.jetbrains.kotlin.name.FqName | ||
import org.jetbrains.kotlin.name.Name | ||
import org.jetbrains.kotlin.types.ConstantValueKind | ||
|
||
object DisallowLambdaCaptureErrors : Diagnostics.Error { | ||
val UnsafeCaptureDetected by error1() | ||
} | ||
|
||
@Meta | ||
@Target(AnnotationTarget.FUNCTION) | ||
annotation class DisallowLambdaCapture(val msg: String = "") { | ||
companion object : Meta.Checker.Expression<FirFunctionCall>, | ||
Diagnostics(UnsafeCaptureDetected) { | ||
|
||
val annotation = DisallowLambdaCapture::class.java | ||
|
||
override fun FirMetaCheckerContext.check(expression: FirFunctionCall) { | ||
val nameArg = expression | ||
.disallowLambdaCaptureAnnotation(session)?.findArgumentByName(Name.identifier(DisallowLambdaCapture::msg.name)) | ||
val userMsg = | ||
if (nameArg is FirConstExpression<*> && nameArg.kind == ConstantValueKind.String) nameArg.value as? String | ||
else null | ||
scopeDeclarations.filterIsInstance<FirAnonymousFunction>().forEach { scope -> | ||
if (scope.inlineStatus != InlineStatus.Inline) { | ||
expression.report( | ||
UnsafeCaptureDetected, | ||
userMsg | ||
?: "detected call to member @DisallowLambdaCapture `${+expression}` in non-inline anonymous function" | ||
) | ||
} | ||
} | ||
} | ||
|
||
private fun FirFunctionCall.disallowLambdaCaptureAnnotation(session: FirSession): FirAnnotation? = | ||
toResolvedCallableSymbol()?.fir?.getAnnotationByClassId( | ||
ClassId( | ||
FqName(annotation.`package`.name), | ||
Name.identifier(annotation.simpleName) | ||
), | ||
session | ||
) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
35 changes: 35 additions & 0 deletions
35
arrow-reflect-compiler-plugin/src/testData/diagnostics/capture_test.fir.txt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
FILE: capture_test.kt | ||
package foo.bar | ||
|
||
public abstract interface Raise<in E> : R|kotlin/Any| { | ||
@R|arrow/meta/samples/DisallowLambdaCapture|(msg = String(It's unsafe to capture `raise` inside non-inline anonymous functions)) public abstract fun raise(e: R|E|): R|kotlin/Nothing| | ||
|
||
} | ||
context(R|foo/bar/Raise<kotlin/String>|) | ||
public final fun shouldNotCapture(): R|() -> kotlin/Unit| { | ||
^shouldNotCapture fun <anonymous>(): R|kotlin/Unit| <inline=Unknown> { | ||
this@R|foo/bar/shouldNotCapture|.R|SubstitutionOverride<foo/bar/Raise.raise: R|kotlin/Nothing|>|(String(boom)) | ||
} | ||
|
||
} | ||
context(R|foo/bar/Raise<kotlin/String>|) | ||
public final fun inlineCaptureOk(): R|kotlin/Unit| { | ||
R|kotlin/collections/listOf|<R|kotlin/Int|>(vararg(Int(1), Int(2), Int(3))).R|kotlin/collections/map|<R|kotlin/Int|, R|kotlin/Nothing|>(<L> = map@fun <anonymous>(it: R|kotlin/Int|): R|kotlin/Nothing| <inline=Inline, kind=UNKNOWN> { | ||
this@R|foo/bar/inlineCaptureOk|.R|SubstitutionOverride<foo/bar/Raise.raise: R|kotlin/Nothing|>|(String(boom)) | ||
} | ||
) | ||
} | ||
context(R|foo/bar/Raise<kotlin/String>|) | ||
public final fun leakedNotOk(): R|() -> kotlin/Unit| { | ||
^leakedNotOk fun <anonymous>(): R|kotlin/Unit| <inline=Unknown> { | ||
R|kotlin/collections/listOf|<R|kotlin/Int|>(vararg(Int(1), Int(2), Int(3))).R|kotlin/collections/map|<R|kotlin/Int|, R|kotlin/Nothing|>(<L> = map@fun <anonymous>(it: R|kotlin/Int|): R|kotlin/Nothing| <inline=Inline, kind=UNKNOWN> { | ||
this@R|foo/bar/leakedNotOk|.R|SubstitutionOverride<foo/bar/Raise.raise: R|kotlin/Nothing|>|(String(boom)) | ||
} | ||
) | ||
} | ||
|
||
} | ||
context(R|foo/bar/Raise<kotlin/String>|) | ||
public final fun ok(): R|kotlin/Unit| { | ||
this@R|foo/bar/ok|.R|SubstitutionOverride<foo/bar/Raise.raise: R|kotlin/Nothing|>|(String(boom)) | ||
} |
27 changes: 27 additions & 0 deletions
27
arrow-reflect-compiler-plugin/src/testData/diagnostics/capture_test.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package foo.bar | ||
|
||
import arrow.meta.samples.DisallowLambdaCapture | ||
|
||
interface Raise<in E> { | ||
@DisallowLambdaCapture("It's unsafe to capture `raise` inside non-inline anonymous functions") fun raise(e: E): Nothing | ||
} | ||
|
||
context(Raise<String>) | ||
fun shouldNotCapture(): () -> Unit { | ||
return { <!UnsafeCaptureDetected!>raise("boom")<!> } | ||
} | ||
|
||
context(Raise<String>) | ||
fun inlineCaptureOk(): Unit { | ||
listOf(1, 2, 3).map { raise("boom") } | ||
} | ||
|
||
context(Raise<String>) | ||
fun leakedNotOk(): () -> Unit = { | ||
listOf(1, 2, 3).map { <!UnsafeCaptureDetected!>raise("boom")<!> } | ||
} | ||
|
||
context(Raise<String>) | ||
fun ok(): Unit { | ||
raise("boom") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,38 @@ | ||
package example | ||
package foo.bar | ||
|
||
import arrow.meta.samples.Product | ||
import arrow.meta.samples.DisallowLambdaCapture | ||
import kotlin.contracts.* | ||
|
||
@Product | ||
data class Sample(val name: String, val age: Int) | ||
interface Raise<in E> { | ||
@DisallowLambdaCapture("It's unsafe to capture `raise` inside non-inline anonymous functions") | ||
fun raise(e: E): Nothing | ||
} | ||
|
||
context(Raise<String>) | ||
fun shouldNotCapture(): () -> Unit { | ||
return { raise("boom") } | ||
} | ||
|
||
context(Raise<String>) | ||
fun inlineCaptureOk(): Unit { | ||
listOf(1, 2, 3).map { raise("boom") } | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
fun exactlyOne(f: () -> Unit): Unit { | ||
contract { | ||
callsInPlace(f, InvocationKind.EXACTLY_ONCE) | ||
} | ||
} | ||
|
||
@OptIn(ExperimentalContracts::class) | ||
fun exactlyOnce(f: () -> Unit): Unit { | ||
contract { | ||
callsInPlace(f, InvocationKind.EXACTLY_ONCE) | ||
} | ||
Comment on lines
+30
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure where this is checked in |
||
} | ||
|
||
fun main() { | ||
val properties = Sample("j", 12).product() | ||
println(properties) | ||
context(Raise<String>) | ||
fun ok(): () -> Unit = { | ||
exactlyOnce { raise("boom") } | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this not be more precise as
msg: String?
?