专业的编程技术博客社区

网站首页 > 博客文章 正文

第六篇 SonarQube扫描常见Bug、漏洞修复整理

baijin 2024-08-28 11:25:27 博客文章 3 ℃ 0 评论

1.类中方法全为静态方法时,私有化构造器

问题样例

class StringUtils { // Noncompliant
 
  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }
 
}

原因:当类中的方法全部都是 static 关键字修饰时 ,它的构造方法最好作为 private 私有化,理由是方法全是 static, 不知道的人会去new对象去调用,需要调用构造方法。 但 static的方法直接用类名调用就行!

解决样例

class StringUtils { // Compliant
 
  private StringUtils() {
    throw new IllegalStateException("Utility class");
  }
 
  public static String concatenate(String s1, String s2) {
    return s1 + s2;
  }
 
}

2.多个异常使用多个方法块捕获

问题样例

try {
  /* ... */
} catch (Exception e) {
  if(e instanceof IOException) { /* ... */ }         // Noncompliant
  if(e instanceof NullPointerException{ /* ... */ }  // Noncompliant
}

原因:应使用适当类型的多个捕获块,而不是捕获一般异常,然后对该类型进行测试。

解决样例

class MyClass {
   private int my_field;
}

3.命名规范

问题样例

class MyClass {
   private int my_field;
}

原因:共享一些命名约定是团队高效协作的关键。此规则允许检查字段名是否与提供的正则表达式匹配。驼峰命名

解决样例

class MyClass {
   private int myField;
}

4.泛型定义具体类型

问题样例

List myList; // Noncompliant
Set mySet; // Noncompliant

原因:提供此泛型的参数化类型。

解决样例

List<String> myList;
Set<? extends Number> mySet;

5.移除未使用的变量、注释的代码

public int numberOfMinutes(int hours) {
  int seconds = 0;   // seconds is never used
  return hours * 60;
}
 
public int numberOfMinutes(int hours) {
  int seconds = 0;   // seconds is never used
  return hours * 60;
}

原因:删掉注释的代码行、未使用的变量、私有方法等、移除未使用的变量

public int numberOfMinutes(int hours) {
  return hours * 60;
}
 
public int numberOfMinutes(int hours) {
  return hours * 60;
}

6.常见空指针异常

String path = null;
path = UploadConfigurationRead.getInstance().getConfigItem(toUploadPath).trim();
            if(path != null){
                flag = "0";
            }else{
                flag = "1";
            }
fileType = path.substring(path.length()-1,path.length());
 
 
 
String errNode = hyperV(document.selectSingleNode("//responseMessage"));
errNode = convterEcfErrCode(errNode);

原因:常见的空指针异常

String path = null;
path = UploadConfigurationRead.getInstance().getConfigItem(toUploadPath).trim();
            if(path != null){
                flag = "0";
                fileType = path.substring(path.length()-1,path.length());
            }else{
                flag = "1";
                fileType = path.substring(path.length()-1,path.length());
            }
 
if(errNode==null) ///99999,无返回提示
                errCode = "99999-无提示";
            else{
                errCode=convterEcfErrCode(errNode);
            }

7.静态变量使用规范

public class MyClass {
  public static final int SOME_CONSTANT = 0;     // Compliant - constants are not checked
  public String firstName;                       // Noncompliant
 
}

原因:静态变量加final、驼峰命名、或者使用私有变量并提供方法访问

public class MyClass {
  public static final int SOME_CONSTANT = 0;     // Compliant - constants are not checked
  private String firstName;                      // Compliant
  public String getFirstName() {
    return firstName;
  }

8.集合使用建议

Vector cats = new Vector();

原因:Java API的早期类(如Vector、Hashtable和StringBuffer)被同步以使其线程安全。不幸的是,同步对性能有很大的负面影响,即使在从单个线程使用这些集合时也是如此。
最好使用新的非同步替换:
ArrayList或LinkedList而不是Vector
Deque而不是Stack
HashMap而不是Hashtable
StringBuilder而不是StringBuffer
即使在同步上下文中使用,在使用它之前也应该三思,因为它的用法可能很棘手。如果您确信使用是合法的,则可以安全地忽略此警告。

ArrayList cats = new ArrayList();
 
//这些同步类的使用在重写方法的签名中被忽略。
@Override
public Vector getCats() {...}

9.定义专用异常

public void foo(String bar) throws Throwable {  // Noncompliant
  throw new RuntimeException("My Message");     // Noncompliant
}

原因:定义并抛出专用异常,而不是使用通用异常。

public void foo(String bar) {
  throw new MyOwnRuntimeException("My Message");
}

10.IO等资源的正确使用

private void readTheFile() throws IOException {
  Path path = Paths.get(this.fileName);
  BufferedReader reader = Files.newBufferedReader(path, this.charset);
  // ...
  reader.close();  // Noncompliant
  // ...
  Files.lines("input.txt").forEach(System.out::println); // Noncompliant: The stream needs to be closed
}
 
@CheckForNull
String getName(){...}
 
public boolean isNameEmpty() {
  return getName().length() == 0; // Noncompliant; the result of getName() could be null, but isn't null-checked
}
 
Connection conn = null;
Statement stmt = null;
try{
  conn = DriverManager.getConnection(DB_URL,USER,PASS);
  stmt = conn.createStatement();
  // ...
 
}catch(Exception e){
  e.printStackTrace();
}finally{
  stmt.close();   // Noncompliant; stmt could be null if an exception was thrown in the try{} block
  conn.close();  // Noncompliant; conn could be null if an exception was thrown
}

原因:1.IO资源未使用try()catch包裹 2.资源未正确关闭
绝不应取消引用/访问对null的引用。这样做将导致引发NullPointerException。充其量,这种异常会导致程序突然终止。最坏的情况是,它可能会暴露对攻击者有用的调试信息,或者允许攻击者绕过安全措施。
请注意,当它们出现时,该规则利用JSR-305中定义的@CheckForNull和@Nonnull注释来了解哪些值是可为null的,哪些值不可为null,除非@Nonnull用于equals的参数,根据约定,这些值应始终与null一起工作。
IO资源应该在使用后关闭。在try语句中使用了Connections, streams, files等,这些类实现了Closeable 或者AutoCloseable接口,必须在finally块中关闭,否则,如果出现异常就可能无法关闭。对于实现了AutoCloseable接口的类,最好使用“try-with-resource”语句来自动关闭。如果不能正确地关闭资源,就会导致资源泄漏,这可能会导致应用程序甚至整个系统的崩溃。

关于IO资源的处理问题,以下比较三种解决方案。

  • close()放在try块中
  • close()放在finally块中
  • 使用try-with-resource语句
  • 方法一 jdk1.7前推荐
  • PrintWriter out = null; try { out = new PrintWriter( new BufferedWriter( new FileWriter("out.txt", true))); out.println("the text"); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { out.close(); } }
  • 方法二 jdk1.8实现了AutoCloseable接推荐
  • //try-with-resource statement try (PrintWriter out2 = new PrintWriter( new BufferedWriter( new FileWriter("out.txt", true)))) { out2.println("the text"); } catch (IOException e) { e.printStackTrace(); }

11.浮点数的使用

double d = 1.1;
 
BigDecimal bd1 = new BigDecimal(d); // Noncompliant; see comment above
BigDecimal bd2 = new BigDecimal(1.1); // Noncompliant; same result

原因:由于浮点不精确,您不太可能从BigDecimal(double)构造函数中获得所需的值。相反,您应该使用BigDecimal.valueOf,它在封面下使用字符串来消除浮点舍入错误,或者使用string参数的构造函数

double d = 1.1;
 
BigDecimal bd1 = BigDecimal.valueOf(d);
BigDecimal bd2 = new BigDecimal("1.1"); // using String constructor will result in precise value

12.数据计算及数据类型转换

float twoThirds = 2/3; // Noncompliant; int division. Yields 0.0
long millisInYear = 1_000*3_600*24*365; // Noncompliant; int multiplication. Yields 1471228928
long bigNum = Integer.MAX_VALUE + 2; // Noncompliant. Yields -2147483647
long bigNegNum =  Integer.MIN_VALUE-1; //Noncompliant, gives a positive result instead of a negative one.
Date myDate = new Date(seconds * 1_000); //Noncompliant, won't produce the expected result if seconds > 2_147_483

原因:当对整数执行算术运算时,结果将始终是整数。您可以通过自动类型转换将该结果分配给long、double或float,但如果以int或long开头,则结果可能不是您期望的结果。例如,如果将int除法的结果分配给浮点变量,则在分配之前精度将丢失。同样,如果乘法的结果被分配给long,那么在分配之前它可能已经溢出。无论哪种情况,结果都不会像预期的那样。相反,在操作发生之前,应至少将一个操作数强制转换或提升为最终类型。总的来说,要么强制转换,要么将其中一个变量提升

float twoThirds = 2f/3; // 2 promoted to float. Yields 0.6666667
float twoThirds = (float)2/3; // 2 cast to float
long millisInYear = (long)1_000*3_600*24*365; // 1_000 cast to long
long bigNum = (long)Integer.MAX_VALUE + 2;

13. ==比较

String firstName = getFirstName(); // String overrides equals
String lastName = getLastName();
 
if (firstName == lastName) { ... }; // Non-compliant; false even if the strings have the same value

原因:使用引用等式==或!=来比较java.lang.String或java.lang.Integer等装箱类型的两个实例几乎总是错误的,因为它不是比较实际值,而是比较内存中的位置。

String firstName = getFirstName();
String lastName = getLastName();

if (firstName != null && firstName.equals(lastName)) { ... };

14. 不同类型的比较判断

InvDetail invDetail = new InvDetail();
 
if(null != invDetail && !"".equals(invDetail)){
 
}

原因:删除对“equals”的调用;不相关类型之间的比较总是返回false。

InvDetail invDetail = new InvDetail();
if(null != invDetail)){
 
}

15.循环中break的使用

for (int i = 0; i < 10; i++) { // noncompliant, loop only executes once
  printf("i is %d", i);
  break;
}

原因:一次循环建议使用if。如果在循环中使用break,建议加上判断条件

for (int i = 0; i < 10; i++) {
  if (i == x) {
    break;
  } else {
    printf("i is %d", i);
  }
}

16.条件判断的注意事项

//什么都没做
if(resultMap!=null){
     if(PUBParm.RT_STATE_N.equals(resultMap.get("state"))){
     }else{
     }
}else{
}

//做了一样的事情
if (b == 0) {  // Noncompliant
  doOneMoreThing();
} else {
  doOneMoreThing();
}
 
//条件判断后 永远只进入一个分支
int b = a > 12 ? 4 : 4;  // Noncompliant
 
switch (i) {  // Noncompliant
  case 1:
    doSomething();
    break;
  case 2:
    doSomething();
    break;
  case 3:
    doSomething();
    break;
  default:
    doSomething();
}

17.左右运算符条件一致

if(!sts.equals("8")&&!sts.equals("8")){}
 
if ( a == b && a == b ) { // if the first one is true, the second one is too
  doX();
}
 
if(flag =0 0)

原因:运算符左右条件一致,更新判定条件

18.对于InterruptedExceptions的处理

public void run () {
  try {
    while (true) {
      // do stuff
    }
  }catch (InterruptedException e) { // Noncompliant; logging is not enough
    LOGGER.log(Level.WARN, "Interrupted!", e);
  }
}
 
 try {
    while (true) {
      // do stuff
    }
    latch.await();
        } catch (Exception e) {
Either re-interrupt this method or rethrow the "InterruptedException" that can be caught here.
            e.printStackTrace();
            throw new ServiceException(e.getMessage());
        }
        return ipg;

原因:代码中决不应忽略InterruptedExceptions,在这种情况下,只需将异常计数记录为“忽略”即可。抛出InterruptedException将清除线程的中断状态,因此如果异常处理不当,则线程被中断的信息将丢失。相反,InterruptedExceptions应该立即或在清理方法状态后重新抛出,或者通过调用thread.crupt()来重新中断线程,即使这是一个单线程应用程序。任何其他操作过程都有延迟线程关闭的风险,并丢失线程被中断的信息——可能没有完成任务。
同样,也应传播ThreadDeath异常。根据其JavaDoc:如果ThreadDeath被某个方法捕获,则必须对其进行重新处理,以使线程实际死亡。

public void run () {
  try {
    while (true) {
      // do stuff
    }
  }catch (InterruptedException e) {
    LOGGER.log(Level.WARN, "Interrupted!", e);
    // Restore interrupted state...
    Thread.currentThread().interrupt();
  }
}

19.fianlly块中不应使用retrun、break等语句

public static void main(String[] args) {
  try {
    doSomethingWhichThrowsException();
    System.out.println("OK");   // incorrect "OK" message is printed
  } catch (RuntimeException e) {
    System.out.println("ERROR");  // this message is not shown 
  }
}
 
public static void doSomethingWhichThrowsException() {
  try {
    throw new RuntimeException();
  } finally {
    for (int i = 0; i < 10; i ++) {
      //...
      if (q == i) {
        break; // ignored
      }
    }
 
    /* ... */
    return;      // Noncompliant - prevents the RuntimeException from being propagated
  }
}

原因:使用finally块中的return、break、throw等将抑制try或catch块中引发的任何未处理Throwable的传播。当跳转语句(break、continue、return、throw和goto)将强制控制流离开finally块时,此规则会引发问题。

public static void main(String[] args) {
  try {
    doSomethingWhichThrowsException();
    System.out.println("OK");
  } catch (RuntimeException e) {
    System.out.println("ERROR");  // "ERROR" is printed as expected
  }
}
 
public static void doSomethingWhichThrowsException() {
  try {
    throw new RuntimeException();
  } finally {
    for (int i = 0; i < 10; i ++) {
      //...
      if (q == i) {
        break; // ignored
      }
    }
 
    /* ... */
  }
}

20.根据函数返回的状态进行操作

if (zipFile.exists()) { 
    zipFile.delete();
  }
  zipFile.createNewFile();

原因:
当函数调用的返回值包含操作状态代码时,应测试该值以确保操作成功完成。
当忽略以下项的返回值时,此规则会引发问题:
java.io.返回状态代码的文件操作(mkdirs除外)
迭代器hasNext()
枚举.hhasMoreElements()
Lock.tryLock()
非void Condition.await*方法
CountDownLatch.await(long,TimeUnit)
Semaphore.try获取
BlockingQueue:提供,删除
可以直接根据调用函数返回的状态码值进行下一步操作

 if (!zipFile.delete();) { 
     zipFile.createNewFile();
  }

21.compareTo方法在判断中的使用建议

if (myClass.compareTo(arg) == -1) {  // Noncompliant
  // ...
}

原因:虽然大多数compareTo方法返回-1、0或1,但有些方法不返回,并且将compareTo的结果与0以外的特定值进行测试可能会导致错误否定。

if (myClass.compareTo(arg) < 0) {
  // ...
}

22.可能存在的1/0异常

User.numFormat(Math.abs((value - lastValue) / lastValue) * 100,2)).append("%,");

原因:要确定 lastValue不为0

if (lastValue!=0) {
 User.numFormat(Math.abs((value - lastValue) / lastValue) * 100,2)).append("%,");
}

23.重写“equals()”,因此也应重写“hashCode()

class MyClass {    // Noncompliant - should also override "hashCode()"
 
  @Override
  public boolean equals(Object obj) {
    /* ... */
  }
 
}
 
//代码中的具体使用
public boolean equals(Object obj) {
  return true;
}

原因:此类重写“equals()”,因此也应重写“hashCode()”。

class MyClass {    // Compliant
 
  @Override
  public boolean equals(Object obj) {
    /* ... */
  }
 
  @Override
  public int hashCode() {
    /* ... */
  }
 
}

24.自我赋值

public void setName(String name) {
  name = name;
}

原因:没有理由将变量重新分配给它自己。要么是多余的,应该删除,要么重新赋值是错误的,而另一个值或变量用于赋值。

public void setName(String name) {
  this.name = name;
}

25.创建变量给参数重新赋值

public void doTheThing(String str, int i, List<String> strings) {
  str = Integer.toString(i); // Noncompliant
 
  for (String s : strings) {
    s = "hello world"; // Noncompliant
  }

26.调用函数有返回值要接收

propDeTypes.replace("|", "");

原因:调用函数有返回值要接收,当满足以下两个条件时,此规则不会产生问题:
方法调用位于带有关联catch子句的try块中。
方法名称以“parse”、“format”、“decode”或“valueOf”开头,或者方法是String.getBytes(Charset)。

public void handle(String command){
  String formattedCommand = command.toLowerCase();
  ...
}
 
private boolean textIsInteger(String textToCheck) {
    try {
        Integer.parseInt(textToCheck, 10); // OK
        return true;
    } catch (NumberFormatException ignored) {
        return false;
    }
}

27.Random应当抽取使用

public void doSomethingCommon() {
  Random rand = new Random();  // Noncompliant; new instance created with each invocation
  int rValue = rand.nextInt();
  //...

原因:每次需要随机值时创建一个新的Random对象是低效的,并且可能会产生不随机的数字,具体取决于JDK。为了获得更好的效率和随机性,请创建一个Random,然后存储并重用它。Random()构造函数每次都尝试用不同的值设置种子。然而,不能保证种子是随机的,甚至是均匀分布的。有些JDK将使用当前时间作为种子,这使得生成的数字完全不是随机的。可不修改

private Random rand = SecureRandom.getInstanceStrong();  // SecureRandom is preferred to Random
 
public void doSomethingCommon() {
  int rValue = this.rand.nextInt();
  //...

28.Calendar等类不应当定义为静态变量

public class MyClass {
  private static SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");  // Noncompliant
  private static Calendar calendar = Calendar.getInstance();  // Noncompliant

原因:并非标准Java库中的所有类都是线程安全的。以多线程方式使用它们极有可能在运行时导致数据问题或异常。当Calendar、DateFormat、javax.xml.xpath.XXPath或javax.xml.validation.SchemaFactory的实例标记为静态时,此规则会引发问题。
DateUtil工具中使用日历变量的,建议在代码中新增,而不使用静态日历变量

public class MyClass {
  private SimpleDateFormat format = new SimpleDateFormat("HH-mm-ss");
  private Calendar calendar = Calendar.getInstance();

29.正则表达式的使用规范

private static final String CHECK_TEL_VALUE ="/^[+]{0,1}(\\d){1,3}[ ]?([-]?((\\d)|[ ]){1,12})+$/";
Pattern.compile("$[a-z]+^"); // Noncompliant

原因:在正则表达式中,边界^和\A只能在输入的开头匹配(或者,如果^与MULTILINE标志组合,则为行的开头),$、\Z和\Z只能在结尾匹配。这些模式可能会被误用,例如,通过意外切换^和$来创建一个永远无法匹配的模式。

Pattern.compile("^[a-z]+#34;);

30.hibernate.hbm2ddl.auto使用

<session-factory>
  <property name="hibernate.hbm2ddl.auto">update</property>  <!-- Noncompliant -->
</session-factory>

原因:对hibernate.hbm2ddl.auto使用除“validate”以外的任何值都可能导致应用程序使用的数据库架构被更改、删除或清除所有数据。简而言之,使用该属性是有风险的,并且只有在生产中使用“validate”选项时,才能使用该属性

<session-factory>
  <property name="hibernate.hbm2ddl.auto">validate</property>  <!-- Compliant -->
</session-factory>
 
or
 
<session-factory>
  <!-- Property deleted -->
</session-factory>

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表