Skip to content

Commit

Permalink
feat(EC-514): Handle Motion Sensor Leakage when started but not stopped
Browse files Browse the repository at this point in the history
  • Loading branch information
ExalTomiche committed May 29, 2024
1 parent daea639 commit 4f7d83e
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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("<EOF>")) {
if (motionSensorStarted && !motionSensorStopped) {
this.recordIssue(id.getStart().getStartIndex(), DEFAULT_ISSUE_MESSAGE);
}
motionSensorStarted = false;
motionSensorStopped = false;
}
}
}
3 changes: 3 additions & 0 deletions swift-lang/src/main/resources/ecocode_swift_profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
"EC509",
"EC512",
"EC513",
"EC514",
"EC519",
"EC520",
"EC522",
"EC524",
"EC530",
"EC533",
"EC534",
"EC603"
]
}
45 changes: 45 additions & 0 deletions swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<img src="http://www.neomades.com/extern/partage/ecoCode/2sur5_1x.png">
<p>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.

<code>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.</code></p>
<h2>Noncompliant Code Example</h2>
<pre>
import CoreMotion

let motionManager = CMMotionManager()

func startMotionUpdates() {
if motionManager.isAccelerometerAvailable {
motionManager.startAccelerometerUpdates(to: .main) { data, error in
// Handle accelerometer updates
}
}
}
</pre>
<h2>Compliant Code Example</h2>
<pre>
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()
}
}
</pre>
18 changes: 18 additions & 0 deletions swift-lang/src/main/resources/io/ecocode/rules/swift/EC514.json
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/
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> 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();
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 4f7d83e

Please sign in to comment.