Di abad ini, semua orang telah mendengar layanan cloud. Banyak perusahaan telah menguasai segmen pasar ini dan menciptakan layanan cloud mereka di berbagai arah. Tim kami juga baru-baru ini tertarik pada layanan ini dalam hal mengintegrasikan penganalisa kode PVS-Studio dengan mereka. Kami pikir pembaca reguler kami sudah menebak jenis proyek apa yang akan kami periksa kali ini. Pilihannya jatuh pada kode layanan cloud dari Huawei.
Pendahuluan
Jika Anda mengikuti tim PVS-Studio, Anda mungkin memperhatikan bahwa baru-baru ini kami sangat tertarik dengan teknologi cloud. Kami telah menerbitkan beberapa artikel yang berkaitan dengan topik ini:
Jadi, ketika saya mengambil proyek lain yang menarik untuk artikel yang akan datang, saya menerima tawaran pekerjaan dari
Huawei melalui pos. Ketika mengumpulkan informasi tentang perusahaan ini, ternyata mereka memiliki layanan cloud sendiri, tetapi yang utama adalah bahwa kode sumber layanan ini tersedia secara bebas di GitHub. Ini adalah alasan utama untuk memilih perusahaan ini untuk artikel ini. Seperti kata seorang bijak Tiongkok: "Kecelakaan tidak disengaja."
Sekarang sedikit tentang analisa kami. PVS-Studio adalah penganalisa kode statis yang mengidentifikasi kesalahan dan kerentanan dalam kode sumber program yang ditulis dalam C, C ++, C # dan Java. Alat analisa bekerja pada platform Windows, Linux dan macOS. Selain plugin untuk lingkungan pengembangan klasik seperti Visual Studio atau IntelliJ IDEA, penganalisa memiliki kemampuan untuk berintegrasi dengan SonarQube dan Jenkins:
Analisis proyek
Dalam proses mencari informasi untuk artikel tersebut, saya menemukan bahwa Huawei memiliki
pusat pengembang tempat Anda dapat memperoleh informasi, panduan, dan kode sumber untuk layanan cloud mereka. Berbagai bahasa pemrograman digunakan untuk membuat layanan ini, tetapi bahasa seperti Go, Java, dan Python ternyata menjadi yang paling populer.
Karena saya berspesialisasi dalam Jawa, proyek-proyek tersebut dipilih sesuai dengan itu. Sumber proyek yang dianalisis dalam artikel dapat diperoleh di
huaweicloud repositori GitHub.
Untuk menganalisis proyek, saya hanya perlu melakukan beberapa tindakan:
- Dapatkan proyek dari repositori;
- Gunakan instruksi untuk memulai penganalisis Java dan memulai analisis pada masing-masing proyek.
Setelah menganalisis proyek, kami berhasil memilih hanya tiga yang ingin saya perhatikan. Ini disebabkan oleh fakta bahwa ukuran proyek Jawa yang tersisa terlalu kecil.
Hasil analisis proyek (jumlah peringatan dan jumlah file):
Ada beberapa peringatan, jadi secara umum kita dapat mengatakan tentang kualitas kode yang baik. Selain itu, tidak semua operasi adalah kesalahan yang valid. Ini disebabkan oleh fakta bahwa penganalisa kadang-kadang tidak memiliki informasi yang cukup untuk membedakan kode yang benar dari yang salah. Oleh karena itu, diagnostik penganalisa ditingkatkan setiap hari dengan bantuan informasi yang diterima dari pengguna. Lihat juga artikel "
Bagaimana dan mengapa analisa statis melawan positif palsu ."
Dalam proses menganalisis proyek, saya memilih peringatan yang paling menarik, yang akan saya bahas dalam artikel ini.
Urutan inisialisasi lapangan
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 terjadi dalam konstruktor kelas
UntrustedSSL , informasi tentang pengecualian ini dicatat di
blok tangkap menggunakan
LOG logger. Namun, karena urutan inisialisasi bidang statis,
LOG pada saat inisialisasi bidang
INSTANCE belum diinisialisasi. Oleh karena itu, ketika mencatat informasi pengecualian di konstruktor,
NullPointerException akan dilempar. Pengecualian ini adalah penyebab
pengecualian ExceptionInInitializerError lain yang dilemparkan jika pengecualian terjadi saat menginisialisasi bidang statis. Semua yang diperlukan untuk menyelesaikan masalah yang dijelaskan adalah menempatkan inisialisasi
LOG sebelum inisialisasi
INSTANCE .
Kesalahan ketik yang tak terlihat
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 karakter seseorang. Programmer menamai argumen metode ini sesuai dengan nama metode tersebut. Akibatnya, pada baris yang ditunjukkan oleh penganalisa, bidang
metricSchema ditugaskan untuk dirinya sendiri, dan argumen dari 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 adalah kesalahan dangkal yang terkait dengan kecerobohan, yang menyebabkan penugasan argumen itu sendiri terjadi. Akibatnya, nilai argumen yang tiba tidak akan ditugaskan ke bidang
menangguhkan , seperti tersirat. Dengan 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 bukan
nol . Karena kedua argumen untuk metode
removeFirewallRuleFromPolicy diperiksa oleh metode
checkNotNull , tidak masuk akal untuk memeriksa argumen untuk
nilai null lagi. Namun, ekspresi dilewatkan ke metode
checkState sebagai argumen pertama, di mana argumen
firewallPolicyId dan
firewallRuleId diperiksa ulang untuk
null .
Peringatan serupa 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 , maka metode keluar dan mengembalikan nilai. Oleh karena itu, tes yang ditunjukkan oleh penganalisa akan selalu benar, yang berarti bahwa tes ini tidak masuk akal.
Serupa terjadi di 13 kelas:
- 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 metode
add dipanggil, bidang
m.blockDeviceMapping akan melempar
NullPointerException .
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), ....); }
Hasil dari metode statis
FileId.get (path) diteruskan ke konstruktor dari kelas
TrackedFile sebagai argumen ketiga. Tetapi metode ini dapat mengembalikan
nol :
public static FileId get(Path file) throws IOException { if (!Files.exists(file)) { return null; } .... }
Dalam konstruktor yang 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)) { .... } }
Oleh karena itu, jika
nol dilewatkan ke metode sebagai argumen ketiga, pengecualian akan terjadi.
Situasi serupa terjadi dalam kasus lain:
- 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();
Dan lagi NPE. Sejumlah pemeriksaan dalam pernyataan bersyarat memungkinkan objek
dataTmpFile nol untuk dereferencing lebih lanjut. Saya pikir ada dua kesalahan ketik di sini, dan cek harus benar-benar 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("/")); }
Dapat dipahami bahwa URL diteruskan ke metode ini sebagai string, yang tidak divalidasi dengan cara apa pun. Setelah string ini dipangkas beberapa kali menggunakan metode
lastIndexOf . Jika
lastIndexOf tidak menemukan kecocokan dalam string, itu akan mengembalikan -1. Ini akan melempar
StringIndexOutOfBoundsException , karena argumen ke metode
substring harus berupa angka non-negatif. Agar metode berfungsi dengan benar, Anda harus menambahkan validasi argumen input atau memverifikasi bahwa hasil dari
metodeIndexOf terakhir bukan angka negatif.
Berikut adalah beberapa tempat lain di mana ini terjadi:
- 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, tidak diperhitungkan bahwa memanggil metode
concat tidak
akan mengubah string
host karena ketidakmampuan objek-objek dari tipe
String . Agar metode berfungsi dengan benar, Anda harus menetapkan hasil metode
concat ke variabel
host di blok
if . Dengan 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 terlibat dalam inisialisasi variabel
url .
Argumen tidak digunakan dalam 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, mereka lupa menggunakan argumen
returnType dan menetapkan nilainya ke bidang
returnType . Oleh karena itu, ketika memanggil metode
getReturnType , objek yang dibuat oleh konstruktor ini akan mengembalikan nilai default
nol , meskipun itu kemungkinan besar dimaksudkan 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) {
Kehadiran 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 melakukan tindakan yang berlawanan. Faktanya, kedua metode melakukan hal yang sama - mereka membuat dan mengembalikan objek
ServiceAction . Kemungkinan besar, metode
menonaktifkan dibuat dengan menyalin kode metode
aktifkan , tetapi lupa untuk mengubah tubuh metode.
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, kami memutuskan untuk memeriksa konten struktur tipe
Map untuk ketimpangan
nol . Untuk melakukan ini, argumen
params memanggil metode
get dua kali, yang hasilnya diteruskan ke metode
checkNotNull . Segalanya tampak logis, tetapi bagaimanapun caranya!
Jika , argumen
params diperiksa untuk
null . Setelah itu dapat diasumsikan bahwa argumen input mungkin
nol , tetapi sebelum pemeriksaan ini, metode
get dipanggil
params dua kali. Jika
null dilewatkan sebagai argumen untuk metode ini, maka pertama kali metode
get dipanggil, pengecualian akan dilemparkan.
Situasi serupa terjadi di tiga tempat lagi:
- 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 modern saat ini tidak dapat melakukannya tanpa menggunakan layanan cloud. Sejumlah besar orang menggunakan layanan ini, sehingga kesalahan sekecil apa pun dalam layanan dapat menyebabkan masalah bagi banyak orang, serta biaya tambahan bagi perusahaan untuk memperbaiki konsekuensi dari kesalahan ini. Dalam mengembangkannya, selalu perlu memperhitungkan faktor manusia, karena siapa pun cepat atau lambat membuat kesalahan, dan artikel ini adalah contohnya. Itu sebabnya Anda harus menggunakan semua alat yang mungkin untuk meningkatkan kualitas kode.
PVS-Studio tentu saja akan memberi tahu Huawei tentang hasil pemeriksaan layanan cloud mereka sehingga pengembang perusahaan ini dapat mempelajarinya lebih detail.
Penggunaan satu kali analisis kode statis yang ditunjukkan dalam artikel tidak dapat menunjukkan semua kelebihannya. Informasi lebih rinci tentang cara menggunakan analisis statis dengan benar dapat ditemukan di
sini dan di
sini . Anda dapat mengunduh penganalisa PVS-Studio di
sini .

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Valery Komarov.
Huawei Cloud: Berawan di PVS-Studio Hari Ini .