Untuk programmer Java, ada alat yang berguna untuk membantu Anda menulis kode berkualitas tinggi, misalnya, lingkungan pengembangan IntelliJ IDEA yang kuat, SpotBugs gratis, analisis PMD, dan lainnya. Semua ini sudah digunakan dalam pengembangan proyek Platform CUBA, dan dalam ulasan tentang cacat kode yang ditemukan ini, saya akan memberi tahu Anda bagaimana cara lain Anda dapat meningkatkan kualitas proyek menggunakan penganalisis kode statis PVS-Studio.
Tentang proyek dan penganalisa
Platform CUBA adalah kerangka kerja Java tingkat tinggi untuk membuat aplikasi perusahaan dengan cepat dengan antarmuka web penuh. Platform ini mengabstraksi pengembang dari teknologi heterogen sehingga Anda dapat fokus pada penyelesaian masalah bisnis, pada saat yang sama, tanpa membuatnya mustahil untuk bekerja dengan mereka secara langsung. Kode sumber diambil dari repositori di
GitHub .
PVS-Studio adalah alat untuk mendeteksi kesalahan dan kerentanan potensial dalam kode sumber program yang ditulis dalam C, C ++, C # dan Java. Ini bekerja pada sistem 64-bit pada Windows, Linux dan macOS. Untuk kenyamanan programmer Java, kami telah mengembangkan plugin untuk Maven, Gradle, dan IntelliJ IDEA. Proyek Platform CUBA mudah diverifikasi menggunakan plugin Gradle.
Kesalahan dalam kondisi
Peringatan 1Ekspresi
V6007 'StringUtils.isNotEmpty ("handleTabKey")' selalu benar. SourceCodeEditorLoader.java (60)
@Override public void loadComponent() { .... String handleTabKey = element.attributeValue("handleTabKey"); if (StringUtils.isNotEmpty("handleTabKey")) { resultComponent.setHandleTabKey(Boolean.parseBoolean(handleTabKey)); } .... }
Setelah mengambil nilai atribut dari suatu elemen, verifikasi nilai ini tidak dilakukan. Sebagai gantinya, string konstan diteruskan ke fungsi
isNotEmpty , tetapi kami harus melewati variabel
handleTabKey .
Ada kesalahan serupa lainnya di file AbstractTableLoader.java:
- Ekspresi V6007 'StringUtils.isNotEmpty ("dapat diedit")' selalu benar. AbstractTableLoader.java (596)
Peringatan 2Ekspresi
V6007 'previousMenuItemFlatIndex> = 0' selalu benar. CubaSideMenuWidget.java (328)
protected MenuItemWidget findNextMenuItem(MenuItemWidget currentItem) { List<MenuTreeNode> menuTree = buildVisibleTree(this); List<MenuItemWidget> menuItemWidgets = menuTreeToList(menuTree); int menuItemFlatIndex = menuItemWidgets.indexOf(currentItem); int previousMenuItemFlatIndex = menuItemFlatIndex + 1; if (previousMenuItemFlatIndex >= 0) { return menuItemWidgets.get(previousMenuItemFlatIndex); } return null; }
Fungsi
indexOf dapat mengembalikan
-1 jika item tidak ditemukan dalam daftar. Kemudian satu ditambahkan ke indeks, sehingga menyembunyikan situasi ketika elemen yang diinginkan hilang. Masalah potensial lainnya mungkin adalah fakta bahwa variabel
beforeMenuItemFlatIndex akan selalu lebih besar atau sama dengan nol. Jika, misalnya, daftar
menuItemWidgets kosong, maka menjadi mungkin untuk melampaui batas array.
Peringatan 3V6009 Fungsi 'delete' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen: 1. AbstractCollectionDatasource.java (556)
protected DataLoadContextQuery createDataQuery(....) { .... StringBuilder orderBy = new StringBuilder(); .... if (orderBy.length() > 0) { orderBy.delete(orderBy.length() - 2, orderBy.length()); orderBy.insert(0, " order by "); } .... }
Dalam
pesanan buffer karakter
Dengan 2 karakter terakhir dihapus jika jumlah totalnya lebih besar dari nol, yaitu. string berisi satu karakter atau lebih. Tetapi posisi awal untuk menghapus karakter diatur dengan offset 2 karakter. Jadi, jika
orderBy tiba-tiba terdiri dari 1 karakter, upaya untuk menghapus akan melempar
StringIndexOutOfBoundsException .
Peringatan 4Objek
V6013 'masterCollection' dan 'entitas' dibandingkan dengan referensi. Mungkin perbandingan kesetaraan dimaksudkan. CollectionPropertyContainerImpl.java (81)
@Override public void setItems(@Nullable Collection<E> entities) { super.setItems(entities); Entity masterItem = master.getItemOrNull(); if (masterItem != null) { MetaProperty masterProperty = getMasterProperty(); Collection masterCollection = masterItem.getValue(masterProperty.getName()); if (masterCollection != entities) { updateMasterCollection(masterProperty, masterCollection, entities); } } }
Dalam fungsi
updateMasterCollection, nilai dari
entitas disalin ke
masterCollection . Sebelum ini, koleksi dibandingkan dengan referensi, tetapi mungkin mereka direncanakan untuk dibandingkan dengan nilai.
Peringatan 5V6013 Objek 'nilai' dan 'nilai lama' dibandingkan dengan referensi. Mungkin perbandingan kesetaraan dimaksudkan. WebOptionsList.java (278)
protected boolean isCollectionValuesChanged(Collection<I> value, Collection<I> oldValue) { return value != oldValue; }
Contoh ini mirip dengan yang sebelumnya. Inilah
isCollectionValuesChanged untuk membandingkan koleksi. Mungkin perbandingan dengan referensi juga bukan cara yang diharapkan.
Kelebihan kondisi
Peringatan 1Ekspresi
V6007 'mask.charAt (i + offset)! = PlaceHolder' selalu benar. DatePickerDocument.java (238)
private String calculateFormattedString(int offset, String text) .... { .... if ((mask.charAt(i + offset) == placeHolder)) {
Dalam perbandingan kedua, ekspresi yang berlawanan dengan yang pertama diperiksa. Pemeriksaan kedua dapat dihapus untuk menyederhanakan kode.
Konektor V6007 Ekspresi '== null' selalu salah. HTML5Support.java (169)
private boolean validate(NativeEvent event) { .... while (connector == null) { widget = widget.getParent(); connector = Util.findConnectorFor(widget); } if (this.connector == connector) { return true; } else if (connector == null) {
Setelah
loop sementara selesai, nilai variabel
konektor tidak akan
nol , oleh karena itu, pemeriksaan redundan dapat dihapus.
Tempat mencurigakan lain yang harus diperhatikan pengembang:
- Ekspresi V6007 'StringUtils.isBlank (strValue)' selalu benar. Param.java (818)
Kode yang tidak terjangkau dalam tes
V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. TransactionTest.java (283)
private void throwException() { throw new RuntimeException(TEST_EXCEPTION_MSG); } @Test public void testSuspendRollback() { Transaction tx = cont.persistence().createTransaction(); try { .... Transaction tx1 = cont.persistence().createTransaction(); try { EntityManager em1 = cont.persistence().getEntityManager(); assertTrue(em != em1); Server server1 = em1.find(Server.class, server.getId()); assertNull(server1); throwException();
Fungsi
throwException melempar pengecualian yang
mencegah panggilan ke fungsi
tx1.commit. Mungkin garis-garis ini harus dipertukarkan.
Beberapa tempat serupa di tes lain:
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. TransactionTest.java (218)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. TransactionTest.java (163)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. TransactionTest.java (203)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. TransactionTest.java (137)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. UpdateDetachedTest.java (153)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. EclipseLinkDetachedTest.java (132)
- V6019 Kode tidak terjangkau terdeteksi. Mungkin saja ada kesalahan. PersistenceTest.java (223)
Argumen Fungsi Mencurigakan
Peringatan 1V6023 Parameter 'garam' selalu ditulis ulang dalam tubuh metode sebelum digunakan. BCryptEncryptionModule.java (47)
@Override public String getHash(String content, String salt) { salt = BCrypt.gensalt(); return BCrypt.hashpw(content, salt); }
Dalam kriptografi,
garam adalah serangkaian data yang diteruskan ke fungsi hash bersama dengan kata sandi. Ini terutama digunakan untuk perlindungan terhadap pencarian dan serangan kamus menggunakan tabel pelangi, serta menyembunyikan kata sandi yang sama. Baca lebih lanjut:
Garam (kriptografi) .
Dalam fungsi ini, garis digosok segera setelah memasuki fungsi. Mungkin mengabaikan nilai yang diteruskan merupakan kerentanan potensial.
Peringatan 2Untuk fungsi yang dipertimbangkan, penganalisa segera memberikan dua peringatan:
- V6023 Parameter 'offsetWidth' selalu ditulis ulang dalam tubuh metode sebelum digunakan. CubaSuggestionFieldWidget.java (433)
- V6023 Parameter 'offsetHeight' selalu ditulis ulang dalam tubuh metode sebelum digunakan. CubaSuggestionFieldWidget.java (433)
@Override public void setPosition(int offsetWidth, int offsetHeight) { offsetHeight = getOffsetHeight(); .... if (offsetHeight + getPopupTop() > ....)) { .... } .... offsetWidth = containerFirstChild.getOffsetWidth(); if (offsetWidth + getPopupLeft() > ....)) { .... } else { left = getPopupLeft(); } setPopupPosition(left, top); }
Kode lucu. Fungsi hanya mengambil dua variabel:
offsetWidth dan
offsetHeight , keduanya ditimpa sebelum digunakan.
Peringatan 3V6022 'Pintasan' Parameter tidak digunakan di dalam badan konstruktor. DeclarativeTrackingAction.java (47)
public DeclarativeTrackingAction(String id, String caption, String description, String icon, String enable, String visible, String methodName, @Nullable String shortcut, ActionsHolder holder) { super(id); this.caption = caption; this.description = description; this.icon = icon; setEnabled(enable == null || Boolean.parseBoolean(enable)); setVisible(visible == null || Boolean.parseBoolean(visible)); this.methodName = methodName; checkActionsHolder(holder); }
Nilai parameter
pintasan tidak digunakan dalam fungsi. Antarmuka fungsi mungkin kedaluwarsa atau peringatan ini bukan kesalahan.
Beberapa tempat serupa:
- V6022 'Jenis' Parameter tidak digunakan di dalam tubuh konstruktor. QueryNode.java (36)
- V6022 Parameter 'text2' tidak digunakan di dalam tubuh konstruktor. MarkerAddition.java (22)
- V6022 'Pemilihan' Parameter tidak digunakan di dalam tubuh konstruktor. AceEditor.java (114)
- V6022 'Opsi' Parameter tidak digunakan di dalam tubuh konstruktor. EntitySerialization.java (379)
Fungsi berbeda dengan kode yang sama
Peringatan 1V6032 Aneh bahwa tubuh metode 'firstItemId' sepenuhnya setara dengan tubuh metode lain 'lastItemId'. ContainerTableItems.java (213), ContainerTableItems.java (219)
@Override public Object firstItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); } @Override public Object lastItemId() { List<E> items = container.getItems(); return items.isEmpty() ? null : items.get(0).getId(); }
Fungsi
firstItemId dan
lastItemId memiliki implementasi yang sama. Kemungkinan besar, dalam yang terakhir itu perlu untuk mendapatkan elemen bukan dengan indeks
0 , tetapi untuk menghitung indeks elemen terakhir.
Peringatan 2V6032 Aneh bahwa tubuh metode sepenuhnya setara dengan tubuh metode lain. SearchComboBoxPainter.java (495), SearchComboBoxPainter.java (501)
private void paintBackgroundDisabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); } private void paintBackgroundEnabledAndEditable(Graphics2D g) { rect = decodeRect1(); g.setPaint(color53); g.fill(rect); }
Dua fungsi lagi dengan implementasi yang mencurigakan identik. Saya berani menyarankan bahwa di salah satu dari mereka perlu menggunakan warna yang berbeda dari
color53 .
Referensi kosong
Peringatan 1V6060 Referensi 'descriptionPopup' digunakan sebelum diverifikasi terhadap nol. SuggestPopup.java (252), SuggestPopup.java (251)
protected void updateDescriptionPopupPosition() { int x = getAbsoluteLeft() + WIDTH; int y = getAbsoluteTop(); descriptionPopup.setPopupPosition(x, y); if (descriptionPopup!=null) { descriptionPopup.setPopupPosition(x, y); } }
Hanya dalam dua baris, penulis berhasil menulis kode yang sangat mencurigakan. Pertama, metode
setPopupPosition dipanggil pada objek
descriptionPopup , dan kemudian objek tersebut dibandingkan dengan
nol . Kemungkinan besar, panggilan pertama ke fungsi
setPopupPosition berlebihan dan berbahaya. Sepertinya konsekuensi dari kegagalan refactoring.
Peringatan 2V6060 Referensi 'tableModel' digunakan sebelum diverifikasi terhadap nol. DesktopAbstractTable.java (1580), DesktopAbstractTable.java (1564)
protected Column addRuntimeGeneratedColumn(String columnId) {
Situasi serupa ada dalam fungsi ini. Setelah banyak panggilan ke objek
tableModel , pemeriksaan dilakukan apakah itu
nol atau tidak.
Contoh lain:
- V6060 Referensi 'tableModel' digunakan sebelum diverifikasi terhadap nol. DesktopAbstractTable.java (596), DesktopAbstractTable.java (579)
Mungkin kesalahan logis
V6026 Nilai ini sudah ditetapkan ke variabel 'sortAscending'. CubaScrollTableWidget.java (488)
@Override protected void sortColumn() { .... if (sortAscending) { if (sortClickCounter < 2) {
Dalam kondisi pertama, variabel
sortAscending sudah benar , tetapi masih diberi nilai yang sama. Mungkin ini adalah kesalahan dan ingin menugaskan
salah .
Contoh serupa dari file lain:
- V6026 Nilai ini sudah ditetapkan ke variabel 'sortAscending'. CubaTreeTableWidget.java (444)
Nilai pengembalian fungsi aneh
Peringatan 1V6037 '
Pengembalian ' tanpa syarat dalam satu lingkaran. QueryCacheManager.java (128)
public <T> T getSingleResultFromCache(QueryKey queryKey, List<View> views) { .... for (Object id : queryResult.getResult()) { return (T) em.find(metaClass.getJavaClass(), id, views.toArray(....)); } .... }
Penganalisis mendeteksi panggilan tanpa syarat ke
pernyataan kembali pada iterasi pertama dari
for loop. Mungkin ini kesalahan, atau Anda perlu menulis ulang kode untuk menggunakan
pernyataan if .
Peringatan 2V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. DefaultExceptionHandler.java (40)
@Override public boolean handle(ErrorEvent event, App app) { Throwable t = event.getThrowable(); if (t instanceof SocketException || ExceptionUtils.getRootCause(t) instanceof SocketException) { return true; } if (ExceptionUtils.getThrowableList(t).stream() .anyMatch(o -> o.getClass().getName().equals("...."))) { return true; } if (StringUtils.contains(ExceptionUtils.getMessage(t), "....")) { return true; } AppUI ui = AppUI.getCurrent(); if (ui == null) { return true; } if (t != null) { if (app.getConnection().getSession() != null) { showDialog(app, t); } else { showNotification(app, t); } } return true; }
Fungsi ini dalam semua kasus mengembalikan
true . Tapi di sini, di baris terakhir meminta kembalinya yang
salah . Mungkin ada kesalahan.
Seluruh daftar fungsi mencurigakan dengan kode yang serupa:
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. ErrorNodesFinder.java (31)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. FileDownloadController.java (69)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. IdVarSelector.java (73)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. IdVarSelector.java (48)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. IdVarSelector.java (67)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. IdVarSelector.java (46)
- V6014 Aneh bahwa metode ini selalu mengembalikan satu dan nilai yang sama. JoinVariableNode.java (57)
Peringatan 3Ekspresi
V6007 'needReload' selalu salah. WebAbstractTable.java (2702)
protected boolean handleSpecificVariables(Map<String, Object> variables) { boolean needReload = false; if (isUsePresentations() && presentations != null) { Presentations p = getPresentations(); if (p.getCurrent() != null && p.isAutoSave(p.getCurrent()) && needUpdatePresentation(variables)) { Element e = p.getSettings(p.getCurrent()); saveSettings(e); p.setSettings(p.getCurrent(), e); } } return needReload; }
Fungsi mengembalikan variabel
needReload , yang nilainya selalu
salah . Kemungkinan besar, di salah satu kondisi mereka lupa menambahkan kode untuk mengubah nilai variabel.
Peringatan 4V6062 Kemungkinan rekursi tak terbatas di dalam metode 'isFocused'. GwtAceEditor.java (189), GwtAceEditor.java (190)
public final native void focus() ; public final boolean isFocused() { return this.isFocused(); }
Penganalisa mendeteksi suatu fungsi yang disebut rekursif tanpa syarat untuk menghentikan rekursi. File ini memiliki banyak fungsi yang ditandai dengan kata kunci
asli dan berisi kode komentar. Kemungkinan besar, file saat ini sedang ditimpa dan segera pengembang akan memperhatikan fungsi
isFocused .
Peringatan lain-lain
Peringatan 1V6002 Pernyataan beralih tidak mencakup semua nilai dari 'Operasi' enum: ADD. DesktopAbstractTable.java (665)
enum Operation { REFRESH, CLEAR, ADD, REMOVE, UPDATE } @Override public void setDatasource(final CollectionDatasource datasource) { .... collectionChangeListener = e -> { switch (e.getOperation()) { case CLEAR: case REFRESH: fieldDatasources.clear(); break; case UPDATE: case REMOVE: for (Object entity : e.getItems()) { fieldDatasources.remove(entity); } break; } }; .... }
Pernyataan beralih tidak membahas nilai
ADD . Ini adalah satu-satunya yang belum ditinjau, jadi ada baiknya memeriksa apakah itu kesalahan atau tidak.
Peringatan 2V6021 'Sumber' variabel tidak digunakan. DefaultHorizontalLayoutDropHandler.java (177)
@Override protected void handleHTML5Drop(DragAndDropEvent event) { LayoutBoundTransferable transferable = (LayoutBoundTransferable) event .getTransferable(); HorizontalLayoutTargetDetails details = (HorizontalLayoutTargetDetails) event .getTargetDetails(); AbstractOrderedLayout layout = (AbstractOrderedLayout) details .getTarget(); Component source = event.getTransferable().getSourceComponent();
Variabel sumber dideklarasikan dan tidak digunakan dalam kode. Mungkin, seperti variabel
comp lain dari jenis yang sama, mereka lupa untuk menambahkan
sumber ke
tata letak .
Lebih banyak fungsi dengan variabel yang tidak digunakan:
- V6021 'Sumber' variabel tidak digunakan. DefaultHorizontalLayoutDropHandler.java (175)
- V6021 Nilai ini diberikan ke variabel 'r' tetapi tidak digunakan. ExcelExporter.java (262)
- V6021 Variabel 'over' tidak digunakan. DefaultCssLayoutDropHandler.java (49)
- V6021 Variabel 'dapat ditransfer' tidak digunakan. DefaultHorizontalLayoutDropHandler.java (171)
- V6021 Variabel 'dapat ditransfer' tidak digunakan. DefaultHorizontalLayoutDropHandler.java (169)
- V6021 'beanLocator' Variabel tidak digunakan. ScreenEventMixin.java (28)
Peringatan 3Kelas
V6054 tidak boleh dibandingkan dengan namanya. MessageTools.java (283)
public boolean hasPropertyCaption(MetaProperty property) { Class<?> declaringClass = property.getDeclaringClass(); if (declaringClass == null) return false; String caption = getPropertyCaption(property); int i = caption.indexOf('.'); if (i > 0 && declaringClass.getSimpleName().equals(caption.substring(0, i))) return false; else return true; }
Penganalisa mendeteksi suatu situasi ketika membandingkan kelas dilakukan dengan nama. Perbandingan semacam itu tidak benar, karena, sesuai dengan spesifikasi, kelas JVM memiliki nama unik hanya di dalam paket. Ini dapat menyebabkan perbandingan yang salah dan eksekusi kode yang salah yang telah direncanakan.
Umpan Balik Pengembang Platform CUBA
Tentu saja, dalam setiap proyek besar ada bug. Itulah sebabnya kami dengan senang hati menyetujui proposal tim PVS-Studio untuk memeriksa proyek kami. Repositori CUBA termasuk fork dari beberapa perpustakaan OSS pihak ketiga di bawah lisensi Apache 2, dan tampaknya kita perlu lebih memperhatikan kode ini, penganalisa menemukan beberapa masalah dalam sumber-sumber ini. Sekarang kami menggunakan SpotBugs sebagai analis utama, dan tidak menemukan beberapa masalah signifikan yang ditemukan oleh PVS-Studio. Sudah waktunya untuk pergi dan menulis cek tambahan sendiri. Banyak terima kasih kepada tim PVS-Studio untuk pekerjaan yang dilakukan.Pengembang juga mencatat bahwa peringatan V6013 dan V6054 salah. Kode itu ditulis dengan sengaja. Analis statis dirancang untuk mendeteksi fragmen kode yang mencurigakan dan kemungkinan menemukan kesalahan berbeda untuk semua inspeksi. Namun demikian, mudah untuk bekerja dengan peringatan semacam itu menggunakan mekanisme yang nyaman untuk
penindasan massal terhadap penganalisa tanpa memodifikasi file kode sumber.
Tim PVS-Studio lain tidak dapat mengabaikan frasa “saatnya untuk pergi dan menulis cek tambahan sendiri” dan tidak meninggalkan gambar ini :)
Kesimpulan
PVS-Studio akan menjadi tambahan yang bagus untuk proyek Anda saat ini untuk meningkatkan alat kualitas kode. Apalagi jika ada puluhan, ratusan dan ribuan karyawan. PVS-Studio dirancang tidak hanya untuk menemukan kesalahan, tetapi juga untuk memperbaikinya. Selain itu, kami tidak berbicara tentang pengeditan kode otomatis, tetapi tentang kontrol kualitas kode yang andal. Dalam sebuah perusahaan besar, tidak mungkin untuk membayangkan situasi di mana benar-benar semua pengembang akan secara independen memverifikasi kode mereka dengan berbagai alat, jadi alat seperti PVS-Studio lebih cocok untuk perusahaan seperti itu, di mana kontrol kualitas kode disediakan pada semua tahap pengembangan, tidak hanya untuk programmer biasa.

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Svyatoslav Razmyslov.
Menganalisis Kode Platform CUBA dengan PVS-Studio