天天看點

第六篇 SonarQube掃描常見Bug、漏洞修複整理

作者:學為先程式設計
第六篇 SonarQube掃描常見Bug、漏洞修複整理

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>           

繼續閱讀