From adfea7594346b7cd2fd9745994514d78b9a7a801 Mon Sep 17 00:00:00 2001 From: ExalTomiche Date: Thu, 30 May 2024 09:24:24 +0200 Subject: [PATCH] feat(EC-514): Handle Motion Sensor Leakage when started but not stopped (#30) --- .../sobriety/MotionSensorLeakCheck.java | 53 +++++++++++++++++++ .../main/resources/ecocode_swift_profile.json | 3 ++ .../io/ecocode/rules/swift/EC514.html | 45 ++++++++++++++++ .../io/ecocode/rules/swift/EC514.json | 18 +++++++ .../EcoCodeSwiftRulesDefinitionTest.java | 4 +- .../sobriety/MotionSensorLeakCheckTest.java | 51 ++++++++++++++++++ .../MotionSensorLeak_no_trigger.swift | 18 +++++++ .../sobriety/MotionSensorLeak_trigger.swift | 12 +++++ 8 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheck.java create mode 100644 swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.html create mode 100644 swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.json create mode 100644 swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheckTest.java create mode 100644 swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_no_trigger.swift create mode 100644 swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_trigger.swift diff --git a/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheck.java b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheck.java new file mode 100644 index 0000000..9281adc --- /dev/null +++ b/swift-lang/src/main/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheck.java @@ -0,0 +1,53 @@ +/* + * ecoCode iOS plugin - Help the earth, adopt this green plugin for your applications + * Copyright © 2023 green-code-initiative (https://www.ecocode.io/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.ecocode.ios.swift.checks.sobriety; + +import io.ecocode.ios.swift.SwiftRuleCheck; +import io.ecocode.ios.swift.antlr.generated.Swift5Parser; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.TerminalNodeImpl; +import org.sonar.check.Rule; + +@Rule(key = "EC514") +public class MotionSensorLeakCheck extends SwiftRuleCheck { + private static final String DEFAULT_ISSUE_MESSAGE = "Any motion sensor started should be stopped."; + private boolean motionSensorStarted = false; + private boolean motionSensorStopped = false; + Swift5Parser.ExpressionContext id; + + @Override + public void apply(ParseTree tree) { + if (tree instanceof Swift5Parser.ExpressionContext && tree.getText().contains("startAccelerometerUpdates")) { + id = (Swift5Parser.ExpressionContext) tree; + motionSensorStarted = true; + } + + if (tree instanceof Swift5Parser.ExpressionContext + && (tree.getText().contains("stopAccelerometerUpdates"))) { + motionSensorStopped = true; + } + + if (tree instanceof TerminalNodeImpl && tree.getText().equals("")) { + if (motionSensorStarted && !motionSensorStopped) { + this.recordIssue(id.getStart().getStartIndex(), DEFAULT_ISSUE_MESSAGE); + } + motionSensorStarted = false; + motionSensorStopped = false; + } + } +} \ No newline at end of file diff --git a/swift-lang/src/main/resources/ecocode_swift_profile.json b/swift-lang/src/main/resources/ecocode_swift_profile.json index c4d584f..f9c9f0d 100644 --- a/swift-lang/src/main/resources/ecocode_swift_profile.json +++ b/swift-lang/src/main/resources/ecocode_swift_profile.json @@ -5,11 +5,14 @@ "EC509", "EC512", "EC513", + "EC514", + "EC519", "EC520", "EC522", "EC524", "EC530", "EC533", + "EC534", "EC603" ] } diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.html b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.html new file mode 100644 index 0000000..75aa954 --- /dev/null +++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.html @@ -0,0 +1,45 @@ + +

Most iOS devices have built-in sensors that measure motion, orientation, and various environmental conditions. + Additionally, they have image sensors (a.k.a. Camera) and geo-positioning sensors (a.k.a. GPS). + + The common point of all these sensors is that they consume significant power while in use. Their common issue is + processing data unnecessarily when the app is in an idle state, typically when it enters the background or becomes + inactive. + + Consequently, calls to start and stop sensor updates must be carefully managed for motion sensor: + CMMotionManager#startAccelerometerUpdates()/CMMotionManager#stopAccelerometerUpdates(). + Failing to do so can drain the battery quickly.

+

Noncompliant Code Example

+
+import CoreMotion
+
+let motionManager = CMMotionManager()
+
+func startMotionUpdates() {
+    if motionManager.isAccelerometerAvailable {
+        motionManager.startAccelerometerUpdates(to: .main) { data, error in
+            // Handle accelerometer updates
+        }
+    }
+}
+
+

Compliant Code Example

+
+import CoreMotion
+
+let motionManager = CMMotionManager()
+
+func startMotionUpdates() {
+    if motionManager.isAccelerometerAvailable {
+        motionManager.startAccelerometerUpdates(to: .main) { data, error in
+            // Handle accelerometer updates
+        }
+    }
+}
+
+func stopMotionUpdates() {
+    if motionManager.isAccelerometerActive {
+        motionManager.stopAccelerometerUpdates()
+    }
+}
+
\ No newline at end of file diff --git a/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.json b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.json new file mode 100644 index 0000000..481f681 --- /dev/null +++ b/swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.json @@ -0,0 +1,18 @@ +{ + "key": "EC514", + "title": "Motion Sensor Leakage", + "defaultSeverity": "Major", + "description": "Any motion sensor started should be stopped.", + "status": "ready", + "remediation": { + "func": "Constant/Issue", + "constantCost": "5min" + }, + "tags": [ + "ecocode", + "environment", + "sobriety", + "eco-design" + ], + "type": "CODE_SMELL" +} \ No newline at end of file diff --git a/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java b/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java index db5c5f3..6d51e57 100644 --- a/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java +++ b/swift-lang/src/test/java/io/ecocode/ios/swift/EcoCodeSwiftRulesDefinitionTest.java @@ -54,8 +54,8 @@ public void testMetadata() { } @Test - public void testRegistredRules() { - assertThat(repository.rules()).hasSize(12); + public void testRegisteredRules() { + assertThat(repository.rules()).hasSize(13); } @Test diff --git a/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheckTest.java b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheckTest.java new file mode 100644 index 0000000..dc3568e --- /dev/null +++ b/swift-lang/src/test/java/io/ecocode/ios/swift/checks/sobriety/MotionSensorLeakCheckTest.java @@ -0,0 +1,51 @@ +/* + * ecoCode iOS plugin - Help the earth, adopt this green plugin for your applications + * Copyright © 2023 green-code-initiative (https://www.ecocode.io/) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package io.ecocode.ios.swift.checks.sobriety; + +import io.ecocode.ios.swift.checks.CheckTestHelper; +import org.junit.Test; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.Issue; +import org.sonar.api.batch.sensor.issue.IssueLocation; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +public class MotionSensorLeakCheckTest { + @Test + public void SensorLeak_trigger() { + SensorContextTester context = CheckTestHelper.analyzeTestFile("checks/sobriety/MotionSensorLeak_trigger.swift"); + assertThat(context.allIssues()).hasSize(1); + Optional issue = context.allIssues().stream().findFirst(); + issue.ifPresent(i -> { + assertThat(i.ruleKey().rule()).isEqualTo("EC514"); + + assertThat(i.ruleKey().repository()).isEqualTo("ecoCode-swift"); + IssueLocation location = i.primaryLocation(); + assertThat(location.textRange().start().line()).isEqualTo(7); + + }); + } + + @Test + public void SensorLeak_no_trigger() { + SensorContextTester context = CheckTestHelper.analyzeTestFile("checks/sobriety/MotionSensorLeak_no_trigger.swift"); + assertThat(context.allIssues()).isEmpty(); + } +} diff --git a/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_no_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_no_trigger.swift new file mode 100644 index 0000000..f5606fd --- /dev/null +++ b/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_no_trigger.swift @@ -0,0 +1,18 @@ +import CoreMotion + +let motionManager = CMMotionManager() + +func startMotionUpdates() { + if motionManager.isAccelerometerAvailable { + motionManager.startAccelerometerUpdates(to: .main) { data, error in + // Handle accelerometer updates + } + } + motionManager.magnetometerUpdateInterval = 0.1 +} + +func stopMotionUpdates() { + if motionManager.isAccelerometerActive { + motionManager.stopAccelerometerUpdates() + } +} \ No newline at end of file diff --git a/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_trigger.swift b/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_trigger.swift new file mode 100644 index 0000000..250b2c7 --- /dev/null +++ b/swift-lang/src/test/resources/checks/sobriety/MotionSensorLeak_trigger.swift @@ -0,0 +1,12 @@ +import CoreMotion + +let motionManager = CMMotionManager() + +func startMotionUpdates() { + if motionManager.isAccelerometerAvailable { + motionManager.startAccelerometerUpdates(to: .main) { data, error in + // Handle accelerometer updates + } + } + motionManager.magnetometerUpdateInterval = 0.1 +} \ No newline at end of file