-
Notifications
You must be signed in to change notification settings - Fork 81
RUM-16056 Add @HotMethod annotation and HotMethodIllegalCall detekt rule
#3558
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| /* | ||
| * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. | ||
| * This product includes software developed at Datadog (https://www.datadoghq.com/). | ||
| * Copyright 2016-Present Datadog, Inc. | ||
| */ | ||
|
|
||
| package com.datadog.android.internal.lint | ||
|
|
||
| /** | ||
| * Marks a method as a hot path — one that can be invoked on every frame or touch event. | ||
| * | ||
| * Implementations must avoid heap allocations, blocking operations, and any work | ||
| * that introduces GC pressure. The IDE/Lint will highlight annotated methods as a | ||
| * reminder to reviewers. | ||
| * | ||
| * @property message A short description of why this method is hot (e.g. "called per frame by JankStats"). | ||
| * @property exclude Check names or specific function/constructor names to skip for this site only. | ||
| * Built-in category strings: `"constructor"`, `"anonymous-object"`, `"lambda"`, | ||
| * `"string-template"`, `"factory"`, `"collection-ops"`. | ||
| * You may also pass a bare method name (e.g. `"forEach"`) or a qualified name | ||
| * (e.g. `"kotlin.collections.List.forEach"`) to exclude just that call. | ||
| * Prefer a narrow per-name exclusion over a broad category exclusion. | ||
| */ | ||
| @Retention(AnnotationRetention.SOURCE) | ||
| @Target(AnnotationTarget.FUNCTION) | ||
| annotation class HotMethod(val message: String, val exclude: Array<String> = []) { | ||
| companion object { | ||
| const val CHECK_CONSTRUCTOR = "constructor" | ||
| const val CHECK_ANONYMOUS_OBJECT = "anonymous-object" | ||
| const val CHECK_LAMBDA = "lambda" | ||
| const val CHECK_STRING_TEMPLATE = "string-template" | ||
| const val CHECK_FACTORY = "factory" | ||
| const val CHECK_COLLECTION_OPS = "collection-ops" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| build: | ||
| maxIssues: 0 | ||
|
|
||
| config: | ||
| validation: true | ||
| warningsAsErrors: true | ||
|
|
||
| comments: | ||
| active: true | ||
| AbsentOrWrongFileLicense: | ||
| active: true | ||
| licenseTemplateFile: 'license.template' | ||
| licenseTemplateIsRegex: false | ||
| excludes: | ||
| # Gradle build outputs and generated artifacts. | ||
| - '**/build/**' | ||
| # Auto-generated Kotlin model fixtures produced by the buildSrc JSON-to-Kotlin code generator. | ||
| - '**/buildSrc/src/test/kotlin/com/example/model/**' | ||
| # Fixture files for the NoOpFactory annotation processor tests, read as text data rather than compiled. | ||
| - '**/tools/noopfactory/src/test/resources/**' | ||
| # Vendored test files ported from the upstream dd-trace-java library. | ||
| - '**/features/dd-sdk-android-trace-internal/src/test/kotlin/com/datadog/trace/**' | ||
|
|
||
| complexity: | ||
| active: false | ||
|
|
||
| coroutines: | ||
| active: false | ||
|
|
||
| empty-blocks: | ||
| active: false | ||
|
|
||
| exceptions: | ||
| active: false | ||
|
|
||
| naming: | ||
| active: false | ||
|
|
||
| potential-bugs: | ||
| active: false | ||
|
|
||
| style: | ||
| active: false | ||
|
|
||
| datadog: | ||
| active: true | ||
| excludes: | ||
| - '**/build/**' | ||
| - '**/test/**' | ||
| - '**/testDebug/**' | ||
| - '**/testRelease/**' | ||
| - '**/androidTest/**' | ||
| - '**/testFixtures/**' | ||
| - '**/buildSrc/**' | ||
| - '**/*.kts' | ||
| - '**/instrumented/**' | ||
| - '**/reliability/**' | ||
| - '**/sample/**' | ||
| - '**/tools/**' | ||
| HotMethodIllegalCall: | ||
| active: true | ||
| # Lambda literals passed to functions in `allowedInlineFunctions` are inlined by the compiler | ||
| # and do not allocate — they are excluded from the lambda-allocation check. | ||
| # Add any project-specific `inline` functions here. | ||
| allowedInlineFunctions: | ||
| # Kotlin scope functions | ||
| - 'let' | ||
| - 'also' | ||
| - 'apply' | ||
| - 'run' | ||
| - 'with' | ||
| - 'takeIf' | ||
| - 'takeUnless' | ||
| # Standard utilities | ||
| - 'synchronized' | ||
| - 'use' | ||
| - 'repeat' | ||
| - 'measureNanoTime' | ||
| - 'measureTimeMillis' | ||
| # Kotlin stdlib collection operations — ALL are inline, lambdas are not allocated | ||
| - 'filter' | ||
| - 'filterNot' | ||
| - 'filterIsInstance' | ||
| - 'filterNotNull' | ||
| - 'map' | ||
| - 'mapNotNull' | ||
| - 'mapIndexed' | ||
| - 'flatMap' | ||
| - 'flatten' | ||
| - 'forEach' | ||
| - 'forEachIndexed' | ||
| - 'any' | ||
| - 'all' | ||
| - 'none' | ||
| - 'count' | ||
| - 'find' | ||
| - 'findLast' | ||
| - 'first' | ||
| - 'firstOrNull' | ||
| - 'last' | ||
| - 'lastOrNull' | ||
| - 'single' | ||
| - 'singleOrNull' | ||
| - 'reduce' | ||
| - 'fold' | ||
| - 'foldIndexed' | ||
| - 'sumOf' | ||
| - 'maxByOrNull' | ||
| - 'minByOrNull' | ||
| - 'maxOrNull' | ||
| - 'minOrNull' | ||
| - 'groupBy' | ||
| - 'associate' | ||
| - 'associateBy' | ||
| - 'partition' | ||
| - 'sortedBy' | ||
| - 'sortedWith' | ||
| - 'sorted' | ||
| - 'toList' | ||
| - 'toMutableList' | ||
| - 'toSet' | ||
| - 'toMutableSet' | ||
| - 'toMap' | ||
| # Builder functions — inline, so the lambda body is inlined (the builder itself still allocates) | ||
| - 'buildString' | ||
| - 'buildList' | ||
| - 'buildSet' | ||
| - 'buildMap' | ||
| # Both lists use `ContainerFQN.method` format (or `SimpleClass.method` as a suffix match). | ||
| # For top-level factory functions the container is the package (e.g. kotlin.collections). | ||
| # emptyList/emptySet/emptyMap are intentionally excluded — they return cached singletons. | ||
| forbiddenFactoryFunctions: | ||
|
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.
The factory list omits lower-case array factories like Useful? React with 👍 / 👎. |
||
| - 'kotlin.collections.listOf' | ||
| - 'kotlin.collections.mutableListOf' | ||
| - 'kotlin.collections.arrayListOf' | ||
| - 'kotlin.collections.setOf' | ||
| - 'kotlin.collections.mutableSetOf' | ||
| - 'kotlin.collections.hashSetOf' | ||
| - 'kotlin.collections.linkedSetOf' | ||
| - 'kotlin.collections.mapOf' | ||
| - 'kotlin.collections.mutableMapOf' | ||
| - 'kotlin.collections.hashMapOf' | ||
| - 'kotlin.collections.linkedMapOf' | ||
| - 'kotlin.collections.buildList' | ||
| - 'kotlin.collections.buildSet' | ||
| - 'kotlin.collections.buildMap' | ||
| - 'kotlin.text.buildString' | ||
| forbiddenCalls: | ||
| # Linear search | ||
| - 'kotlin.collections.List.find' | ||
|
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.
With the current List-only entries, a hot method using a Useful? React with 👍 / 👎. |
||
| - 'kotlin.collections.List.findLast' | ||
| - 'kotlin.collections.List.first' | ||
| - 'kotlin.collections.List.firstOrNull' | ||
| - 'kotlin.collections.List.last' | ||
| - 'kotlin.collections.List.lastOrNull' | ||
| - 'kotlin.collections.List.single' | ||
| - 'kotlin.collections.List.singleOrNull' | ||
| - 'kotlin.collections.List.indexOf' | ||
| - 'kotlin.collections.List.indexOfFirst' | ||
| - 'kotlin.collections.List.indexOfLast' | ||
| - 'kotlin.collections.List.lastIndexOf' | ||
| - 'kotlin.collections.List.any' | ||
| - 'kotlin.collections.List.all' | ||
| - 'kotlin.collections.List.none' | ||
| - 'kotlin.collections.List.count' | ||
| # Full iteration — allocates an iterator each call | ||
| - 'kotlin.collections.List.forEach' | ||
| - 'kotlin.collections.List.forEachIndexed' | ||
| - 'kotlin.collections.List.reduce' | ||
| - 'kotlin.collections.List.fold' | ||
| - 'kotlin.collections.List.foldIndexed' | ||
| - 'kotlin.collections.List.sumOf' | ||
| - 'kotlin.collections.List.maxOrNull' | ||
| - 'kotlin.collections.List.minOrNull' | ||
| - 'kotlin.collections.List.maxByOrNull' | ||
| - 'kotlin.collections.List.minByOrNull' | ||
| # Materialising — allocates a new collection each call | ||
| - 'kotlin.collections.List.filter' | ||
| - 'kotlin.collections.List.filterNot' | ||
| - 'kotlin.collections.List.filterIsInstance' | ||
| - 'kotlin.collections.List.filterNotNull' | ||
| - 'kotlin.collections.List.map' | ||
| - 'kotlin.collections.List.mapNotNull' | ||
| - 'kotlin.collections.List.mapIndexed' | ||
| - 'kotlin.collections.List.flatMap' | ||
| - 'kotlin.collections.List.flatten' | ||
| - 'kotlin.collections.List.toList' | ||
| - 'kotlin.collections.List.toMutableList' | ||
| - 'kotlin.collections.List.toSet' | ||
| - 'kotlin.collections.List.toMutableSet' | ||
| - 'kotlin.collections.List.toMap' | ||
| - 'kotlin.collections.List.groupBy' | ||
| - 'kotlin.collections.List.associate' | ||
| - 'kotlin.collections.List.associateBy' | ||
| - 'kotlin.collections.List.partition' | ||
| # Sorting — O(N log N) | ||
| - 'kotlin.collections.List.sorted' | ||
| - 'kotlin.collections.List.sortedBy' | ||
| - 'kotlin.collections.List.sortedWith' | ||
| datadog-test-pyramid: | ||
| active: false | ||
Uh oh!
There was an error while loading. Please reload this page.