Saat ini semua orang tahu tentang layanan cloud. Banyak perusahaan telah memecahkan segmen pasar ini dan menciptakan layanan cloud mereka sendiri dari berbagai tujuan. Baru-baru ini tim kami juga tertarik dengan layanan ini dalam hal mengintegrasikan penganalisa kode PVS-Studio ke dalamnya. Kemungkinannya, pembaca reguler kami sudah menebak jenis proyek apa yang akan kami periksa kali ini. Pilihannya jatuh pada kode layanan cloud Huawei.
Pendahuluan
Jika Anda mengikuti posting tim PVS-Studio, Anda mungkin memperhatikan bahwa kami telah menggali jauh dalam teknologi cloud belakangan ini. Kami telah menerbitkan beberapa artikel yang membahas topik ini:
Tepat ketika saya sedang mencari proyek yang tidak biasa untuk artikel yang akan datang, saya mendapat email dengan tawaran pekerjaan dari
Huawei . Setelah mengumpulkan beberapa informasi tentang perusahaan ini, ternyata mereka memiliki layanan cloud sendiri, tetapi yang utama adalah bahwa kode sumber layanan ini tersedia di GitHub. Ini adalah alasan utama untuk memilih perusahaan ini untuk artikel ini. Seperti kata seorang bijak Tiongkok: "Kecelakaan itu bukan karena kecelakaan."
Biarkan saya memberi Anda beberapa detail tentang analisa kami. PVS-Studio adalah penganalisa statis untuk deteksi bug dalam kode sumber program, ditulis dalam C, C ++, C #, dan Java. Penganalisa bekerja pada Windows, Linux, dan macOS. Selain plugin untuk lingkungan pengembangan klasik, seperti Visual Studio atau IntelliJ IDEA, penganalisa memiliki kemampuan untuk berintegrasi ke SonarQube dan Jenkins:
Analisis proyek
Ketika saya sedang melakukan riset untuk artikel tersebut, saya menemukan bahwa Huawei memiliki
pusat pengembang dengan informasi, manual, dan sumber layanan cloud yang tersedia. Berbagai macam bahasa pemrograman digunakan untuk membuat layanan ini, tetapi bahasa seperti Go, Java dan Python adalah yang paling umum.
Sejak saya berspesialisasi di Jawa, proyek-proyek telah dipilih sesuai dengan pengetahuan dan keterampilan saya. Anda dapat menganalisis sumber proyek dalam artikel di
huaweicloud repositori GitHub.
Untuk menganalisis proyek, saya hanya perlu melakukan beberapa hal:
- Dapatkan proyek dari repositori;
- Gunakan instruksi penganalisis Java start-up dan jalankan analisis pada setiap proyek.
Setelah menganalisis proyek, kami hanya memilih tiga di antaranya, yang ingin kami perhatikan. Karena ukuran proyek Java yang lain ternyata terlalu kecil.
Hasil analisis proyek (jumlah peringatan dan jumlah file):
Ada beberapa peringatan, yang memberi tahu kita tentang kode berkualitas tinggi, terlebih lagi karena tidak semua peringatan menunjukkan kesalahan nyata. Ini disebabkan oleh fakta bahwa penganalisa kadang-kadang kekurangan informasi untuk membedakan kode yang benar dari kode yang salah. Karena alasan ini, kami mengubah diagnostik penganalisa hari demi hari dengan mencari informasi dari pengguna. Anda boleh melihat artikel "
Cara analis statis melawan positif palsu, dan mengapa mereka melakukannya ."
Saat menganalisis proyek ini, saya hanya mengambil peringatan paling banyak, yang akan saya bicarakan di artikel ini.
Urutan inisialisasi bidang
Siklus inisialisasi Kelas
V6050 hadir. Inisialisasi 'INSTANCE' muncul sebelum inisialisasi 'LOG'. UntrustedSSL.java (32), UntrustedSSL.java (59), UntrustedSSL.java (33)
public class UntrustedSSL { private static final UntrustedSSL INSTANCE = new UntrustedSSL(); private static final Logger LOG = LoggerFactory.getLogger(UntrustedSSL.class); .... private UntrustedSSL() { try { .... } catch (Throwable t) { LOG.error(t.getMessage(), t);
Jika ada pengecualian dalam konstruktor kelas
UntrustedSSL , informasi tentang pengecualian ini dicatat di blok
tangkap menggunakan
LOG logger. Namun, karena urutan inisialisasi bidang statis, pada saat inisialisasi bidang
INSTANCE ,
LOG belum diinisialisasi. Oleh karena itu, jika Anda mencatat informasi tentang pengecualian di konstruktor, itu akan menghasilkan
NullPointerException . Pengecualian ini adalah alasan untuk pengecualian lain
ExceptionInInitializerError , yang dilemparkan jika ada pengecualian ketika bidang statis telah diinisialisasi. Apa yang Anda butuhkan untuk menyelesaikan masalah ini adalah menempatkan inisialisasi
LOG sebelum menginisialisasi
INSTANCE .Kesalahan ketik yang tidak mencolok
V6005 Variabel 'this.metricSchema' ditugaskan untuk dirinya sendiri. OpenTSDBSchema.java (72)
public class OpenTSDBSchema { @JsonProperty("metric") private List<SchemaField> metricSchema; .... public void setMetricsSchema(List<SchemaField> metricsSchema) { this.metricSchema = metricSchema;
Kedua metode mengatur bidang
metricSchema , tetapi nama metode berbeda dengan simbol satu '. Programmer menamai argumen metode ini sesuai dengan nama metode tersebut. Akibatnya, di jalur yang ditunjuk oleh penganalisis, bidang metricSchema ditetapkan untuk dirinya sendiri, dan
argumen metode
metricsSchema tidak digunakan.
V6005 Variabel 'menangguhkan' ditugaskan untuk dirinya sendiri. SuspendTransferTaskRequest.java (77)
public class SuspendTransferTaskRequest { .... private boolean suspend; .... public void setSuspend(boolean suspend) { suspend = suspend; } .... }
Berikut ini adalah kesalahan sepele yang berkaitan dengan kecerobohan, oleh karena itu argumen
penangguhan ditugaskan untuk dirinya sendiri. Akibatnya, bidang yang
ditangguhkan tidak akan diberi nilai argumen yang diperoleh sebagaimana tersirat. Versi yang benar:
public void setSuspend(boolean suspend) { this.suspend = suspend; }
Kondisi yang ditentukan sebelumnya
Seperti yang sering terjadi, aturan
V6007 mendahului dalam hal jumlah peringatan.
Ekspresi
V6007 'firewallPolicyId == null' selalu salah. FirewallPolicyServiceImpl.java (125)
public FirewallPolicy removeFirewallRuleFromPolicy(String firewallPolicyId, String firewallRuleId) { checkNotNull(firewallPolicyId); checkNotNull(firewallRuleId); checkState(!(firewallPolicyId == null && firewallRuleId == null), "Either a Firewall Policy or Firewall Rule identifier must be set"); .... }
Dalam metode ini argumen diperiksa untuk
null oleh metode
checkNotNull :
@CanIgnoreReturnValue public static <T> T checkNotNull(T reference) { if (reference == null) { throw new NullPointerException(); } else { return reference; } }
Setelah memeriksa argumen dengan metode
checkNotNull , Anda dapat 100% yakin bahwa argumen yang diteruskan ke metode ini tidak sama dengan
nol . Karena kedua argumen metode
removeFirewallRuleFromPolicy diperiksa oleh metode
checkNotNull , pemeriksaan lebih lanjut untuk
null tidak masuk akal. Namun, ekspresi, di mana argumen
firewallPolicyId dan
firewallRuleId diperiksa ulang untuk
null , dilewatkan sebagai argumen pertama ke metode
checkState .
Peringatan serupa juga dikeluarkan untuk
firewallRuleId :
- Ekspresi V6007 'firewallRuleId == null' selalu salah. FirewallPolicyServiceImpl.java (125)
Ekspresi
V6007 'filteringParams! = Null' selalu benar. NetworkPolicyServiceImpl.java (60)
private Invocation<NetworkServicePolicies> buildInvocation(Map<String, String> filteringParams) { .... if (filteringParams == null) { return servicePoliciesInvocation; } if (filteringParams != null) {
Dalam metode ini, jika argumen
filteringParams adalah
nol , metode mengembalikan nilai. Inilah sebabnya pemeriksaan yang ditunjukkan oleh penganalisis akan selalu benar yang, pada gilirannya, berarti bahwa pemeriksaan ini tidak ada artinya.
13 kelas lebih mirip:
- Ekspresi V6007 'filteringParams! = Null' selalu benar. PolicyRuleServiceImpl.java (58)
- Ekspresi V6007 'filteringParams! = Null' selalu benar. GroupServiceImpl.java (58)
- Ekspresi V6007 'filteringParams! = Null' selalu benar. ExternalSegmentServiceImpl.java (57)
- Ekspresi V6007 'filteringParams! = Null' selalu benar. L3policyServiceImpl.java (57)
- Ekspresi V6007 'filteringParams! = Null' selalu benar. PolicyRuleSetServiceImpl.java (58)
- dan seterusnya ...
Referensi kosong
V6008 Potensi null dereference dari 'm.blockDeviceMapping'. NovaServerCreate.java (390)
@Override public ServerCreateBuilder blockDevice(BlockDeviceMappingCreate blockDevice) { if (blockDevice != null && m.blockDeviceMapping == null) { m.blockDeviceMapping = Lists.newArrayList(); } m.blockDeviceMapping.add(blockDevice);
Dalam metode ini, inisialisasi bidang referensi
m.blockDeviceMapping tidak akan terjadi jika argumen
blockDevice adalah
nol . Bidang ini diinisialisasi hanya dalam metode ini, jadi ketika memanggil metode
add dari bidang
m.blockDeviceMapping ,
NullPointerException akan terjadi.
V6008 Potensi dereferensi nol dari 'FileId.get (path)' dalam fungsi '<init>'. TrackedFile.java (140), TrackedFile.java (115)
public TrackedFile(FileFlow<?> flow, Path path) throws IOException { this(flow, path, FileId.get(path), ....); }
Konstruktor kelas
TrackedFile menerima hasil dari metode
FileId.get (path) statis sebagai argumen ketiga. Tetapi metode ini dapat mengembalikan
nol :
public static FileId get(Path file) throws IOException { if (!Files.exists(file)) { return null; } .... }
Di konstruktor, dipanggil melalui
ini , argumen
id tidak berubah sampai digunakan pertama kali:
public TrackedFile(...., ...., FileId id, ....) throws IOException { .... FileId newId = FileId.get(path); if (!id.equals(newId)) { .... } }
Seperti yang dapat kita lihat, jika
nol dilewatkan sebagai argumen ketiga ke metode, pengecualian akan terjadi.
Berikut ini adalah kasus serupa lainnya:
- V6008 Potensi null dereference dari 'buffer'. PublishingQueue.java (518)
V6008 Potensi null dereference dari 'dataTmpFile'. CacheManager.java (91)
@Override public void putToCache(PutRecordsRequest putRecordsRequest) { .... if (dataTmpFile == null || !dataTmpFile.exists()) { try { dataTmpFile.createNewFile();
NPE lagi. Sejumlah pemeriksaan di operator bersyarat memungkinkan objek
dataTmpFile nol untuk dereferensi lebih lanjut. Saya pikir ada dua kesalahan ketik di sini dan cek harus benar-benar terlihat seperti ini:
if (dataTmpFile != null && !dataTmpFile.exists())
Substring dan angka negatif
V6009 Fungsi 'substring' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen: 2. RemoveVersionProjectIdFromURL.java (37)
@Override public String apply(String url) { String urlRmovePojectId = url.substring(0, url.lastIndexOf("/")); return urlRmovePojectId.substring(0, urlRmovePojectId.lastIndexOf("/")); }
Implikasinya adalah bahwa metode ini mendapatkan URL sebagai string, yang tidak divalidasi dengan cara apa pun. Kemudian, string ini terputus beberapa kali menggunakan metode
lastIndexOf . Jika metode
lastIndexOf tidak menemukan kecocokan dalam string, itu akan mengembalikan -1. Ini akan mengarah ke
StringIndexOutOfBoundsException , karena argumen metode
substring harus berupa angka non-negatif. Untuk operasi metode yang benar, kita harus menambahkan validasi argumen input atau memeriksa bahwa hasil metode
lastIndexOf adalah angka non-negatif.
Berikut beberapa cuplikan lainnya dengan cara yang serupa:
- V6009 Fungsi 'substring' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen: 2. RemoveProjectIdFromURL.java (37)
- V6009 Fungsi 'substring' dapat menerima nilai '-1' sementara nilai non-negatif diharapkan. Periksa argumen: 2. RemoveVersionProjectIdFromURL.java (38)
Hasil yang terlupakan
V6010 Nilai kembali fungsi 'concat' diperlukan untuk dimanfaatkan. AKSK.java (278)
public static String buildCanonicalHost(URL url) { String host = url.getHost(); int port = url.getPort(); if (port > -1) { host.concat(":" + Integer.toString(port)); } return host; }
Saat menulis kode ini, pembuatnya tidak memperhitungkan bahwa pemanggilan metode
concat tidak akan mengubah string
host karena ketidakmampuan objek tipe
String . Untuk operasi metode yang benar, hasil dari metode
concat harus ditetapkan ke variabel
host di blok
if . Versi yang benar:
if (port > -1) { host = host.concat(":" + Integer.toString(port)); }
Variabel yang tidak digunakan
'URL'
V6021 variabel tidak digunakan. TriggerV2Service.java (95)
public ActionResponse deleteAllTriggersForFunction(String functionUrn) { checkArgument(!Strings.isNullOrEmpty(functionUrn), ....); String url = ClientConstants.FGS_TRIGGERS_V2 + ClientConstants.URI_SEP + functionUrn; return deleteWithResponse(uri(triggersUrlFmt, functionUrn)).execute(); }
Dalam metode ini, variabel
url tidak digunakan setelah inisialisasi. Kemungkinan besar, variabel
url harus diteruskan ke metode
uri sebagai argumen kedua alih-alih
functionUrn , karena variabel
functionUrn mengambil bagian dalam inisialisasi variabel
url .
Argumen tidak menggunakan konstruktor
V6022 Parameter 'returnType' tidak digunakan di dalam tubuh konstruktor. HttpRequest.java (68)
public class HttpReQuest<R> { .... Class<R> returnType; .... public HttpRequest(...., Class<R> returnType)
Dalam konstruktor ini, pemrogram lupa untuk menggunakan argumen
returnType , dan menetapkan nilainya ke bidang
returnType . Itu sebabnya ketika memanggil metode
getReturnType dari objek, dibuat oleh konstruktor ini,
null akan dikembalikan secara default. Tetapi kemungkinan besar, programmer bermaksud untuk mendapatkan objek, yang sebelumnya diteruskan ke konstruktor.
Fungsionalitas yang sama
V6032 Aneh bahwa tubuh metode 'aktifkan' sepenuhnya setara dengan tubuh metode lain 'nonaktifkan'. ServiceAction.java (32), ServiceAction.java (36)
public class ServiceAction implements ModelEntity { private String binary; private String host; private ServiceAction(String binary, String host) { this.binary = binary; this.host = host; } public static ServiceAction enable(String binary, String host) {
Memiliki dua metode yang identik bukanlah kesalahan, tetapi fakta bahwa dua metode melakukan tindakan yang sama setidaknya aneh. Melihat nama-nama metode di atas, kita dapat mengasumsikan bahwa mereka harus melakukan tindakan yang berlawanan. Faktanya, kedua metode melakukan hal yang sama - membuat dan mengembalikan objek
ServiceAction . Kemungkinan besar, metode
menonaktifkan dibuat dengan menyalin kode metode
aktifkan , tetapi tubuh metode tetap sama.
Lupa memeriksa hal utama
V6060 Referensi 'params' digunakan sebelum diverifikasi terhadap nol. DomainService.java (49), DomainService.java (46)
public Domains list(Map<String, String> params) { Preconditions.checkNotNull(params.get("page_size"), ....); Preconditions.checkNotNull(params.get("page_number"), ....); Invocation<Domains> domainInvocation = get(Domains.class, uri("/domains")); if (params != null) {
Dalam metode ini, penulis memutuskan untuk memeriksa isi struktur tipe
Peta untuk
null . Untuk melakukan ini, metode
get dipanggil dua kali dari argumen
params . Hasil dari metode
get diteruskan ke metode
checkNotNull . Segalanya tampak logis, tetapi tidak seperti itu! Argumen
params diperiksa untuk
null jika . Setelah ini diharapkan argumen input mungkin
nol , tetapi sebelum pemeriksaan ini, metode
get sudah dipanggil dua kali dari
params. Jika
nol dilewatkan sebagai argumen untuk metode ini, pertama kali Anda memanggil metode
get , pengecualian akan dilemparkan.
Situasi serupa terjadi di tiga tempat lain:
- V6060 Referensi 'params' digunakan sebelum diverifikasi terhadap nol. DomainService.java (389), DomainService.java (387)
- V6060 Referensi 'params' digunakan sebelum diverifikasi terhadap nol. DomainService.java (372), DomainService.java (369)
- V6060 Referensi 'params' digunakan sebelum diverifikasi terhadap nol. DomainService.java (353), DomainService.java (350)
Kesimpulan
Perusahaan besar saat ini tidak dapat melakukannya tanpa penggunaan layanan cloud. Sejumlah besar orang menggunakan layanan ini. Dalam pandangan ini, bahkan kesalahan kecil dalam suatu layanan dapat menyebabkan masalah bagi banyak orang dan juga kerugian tambahan, yang ditimbulkan oleh perusahaan untuk memperbaiki konsekuensi negatif dari kesalahan ini. Kelemahan manusia harus selalu diperhitungkan terutama karena cepat atau lambat semua orang melakukan kesalahan, seperti yang dijelaskan dalam artikel ini. Fakta ini mendukung penggunaan semua alat yang mungkin untuk meningkatkan kualitas kode.
PVS-Studio pasti akan memberi tahu perusahaan Huawei tentang hasil pemeriksaan layanan cloud mereka sehingga para pengembang Huawei dapat memikirkannya, karena satu kali penggunaan analisis kode statis yang dicakup oleh artikel ini (
1 ,
2 ) tidak dapat sepenuhnya menunjukkan semua kelebihannya. Anda dapat mengunduh penganalisa PVS-Studio di
sini .