首页 文章 精选 留言 我的

精选列表

搜索[UTF-8编码],共10014篇文章
优秀的个人博客,低调大师

java安全编码指南之:异常处理

异常简介 先上个图,看一下常见的几个异常类型。 所有的异常都来自于Throwable。Throwable有两个子类,Error和Exception。 Error通常表示的是严重错误,这些错误是不建议被catch的。 注意这里有一个例外,比如ThreadDeath也是继承自Error,但是它表示的是线程的死亡,虽然不是严重的异常,但是因为应用程序通常不会对这种异常进行catch,所以也归类到Error中。 Exception表示的是应用程序希望catch住的异常。 在Exception中有一个很特别的异常叫做RuntimeException。RuntimeException叫做运行时异常,是不需要被显示catch住的,所以也叫做unchecked Exception。而其他非RuntimeException的Exception则需要显示try catch,所以也叫做checked Exception。 不要忽略checked exceptions 我们知道checked exceptions是一定要被捕获的异常,我们在捕获异常之后通常有两种处理方式。 第一种就是按照业务逻辑处理异常,第二种就是本身并不处理异常,但是将异常再次抛出,由上层代码来处理。 如果捕获了,但是不处理,那么就是忽略checked exceptions。 接下来我们来考虑一下java中线程的中断异常。 java中有三个非常相似的方法interrupt,interrupted和isInterrupted。 isInterrupted()只会判断是否被中断,而不会清除中断状态。 interrupted()是一个类方法,调用isInterrupted(true)判断的是当前线程是否被中断。并且会清除中断状态。 前面两个是判断是否中断的方法,而interrupt()就是真正触发中断的方法。 它的工作要点有下面4点: 如果当前线程实例在调用Object类的wait(),wait(long)或wait(long,int)方法或join(),join(long),join(long,int)方法,或者在该实例中调用了Thread.sleep(long)或Thread.sleep(long,int)方法,并且正在阻塞状态中时,则其中断状态将被清除,并将收到InterruptedException。 如果此线程在InterruptibleChannel上的I / O操作中处于被阻塞状态,则该channel将被关闭,该线程的中断状态将被设置为true,并且该线程将收到java.nio.channels.ClosedByInterruptException异常。 如果此线程在java.nio.channels.Selector中处于被被阻塞状态,则将设置该线程的中断状态为true,并且它将立即从select操作中返回。 如果上面的情况都不成立,则设置中断状态为true。 看下面的例子: public void wrongInterrupted(){ try{ Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } 上面代码中我们捕获了一个InterruptedException,但是我们仅仅是打印出了异常信息,并没有做任何操作。这样程序的表现和没有发送一异常一样,很明显是有问题的。 根据上面的介绍,我们知道,interrupted()方法会清除中断状态,所以,如果我们自身处理不了异常的情况下,需要重新调用Thread.currentThread().interrupt()重新抛出中断,由上层代码负责处理,如下所示。 public void correctInterrupted(){ try{ Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } 不要在异常中暴露敏感信息 遇到异常的时候,通常我们需要进行一定程度的日志输出,从而来定位异常。但是我们在做日志输出的时候,一定要注意不要暴露敏感信息。 下表可以看到异常信息可能会暴露的敏感信息: 除了敏感信息之外,我们还要做好日志信息的安全保护。 在处理捕获的异常时,需要恢复对象的初始状态 如果我们在处理异常的时候,修改了对象中某些字段的状态,在捕获异常的时候需要怎么处理呢? private int age=30; public void wrongRestore(){ try{ age=20; throw new IllegalStateException("custom exception!"); }catch (IllegalStateException e){ System.out.println("we do nothing"); } } 上面的例子中,我们将age重置为20,然后抛出了异常。虽然抛出了异常,但是我们并没有重置age,最后导致age最终被修改了。 整个restore的逻辑没有处理完毕,但是我们部分修改了对象的数据,这是很危险的。 实际上,我们需要一个重置: public void rightRestore(){ try{ age=20; throw new IllegalStateException("custom exception!"); }catch (IllegalStateException e){ System.out.println("we do nothing"); age=30; } } 不要手动完成finally block 我们在使用try-finally和try-catch-finally语句时,一定不要在finally block中使用return, break, continue或者throw语句。 为什么呢? 根据Java Language Specification(JLS)的说明,finally block一定会被执行,不管try语句中是否抛出异常。 在try-finally和try-catch-finally语句中,如果try语句中抛出了异常R,然后finally block被执行,这时候有两种情况: 如果finally block正常执行,那么try语句被终止的原因是异常R。 如果在finally block中抛出了异常S,那么try语句被终止的原因将会变成S。 我们举个例子: public class FinallyUsage { public boolean wrongFinally(){ try{ throw new IllegalStateException("my exception!"); }finally { System.out.println("Code comes to here!"); return true; } } public boolean rightFinally(){ try{ throw new IllegalStateException("my exception!"); }finally { System.out.println("Code comes to here!"); } } public static void main(String[] args) { FinallyUsage finallyUsage=new FinallyUsage(); finallyUsage.wrongFinally(); finallyUsage.rightFinally(); } } 上面的例子中,我们定义了两个方法,一个方法中我们在finally中直接return,另一方法中,我们让finally正常执行完毕。 最终,我们可以看到wrongFinally将异常隐藏了,而rightFinally保留了try的异常。 同样的,如果我们在finally block中抛出了异常,我们一定要记得对其进行捕获,否则将会隐藏try block中的异常信息。 不要捕获NullPointerException和它的父类异常 通常来说NullPointerException表示程序代码有逻辑错误,是需要程序员来进行代码逻辑修改,从而进行修复的。 比如说加上一个null check。 不捕获NullPointerException的原因有三个。 使用null check的开销要远远小于异常捕获的开销。 如果在try block中有多个可能抛出NullPointerException的语句,我们很难定位到具体的错误语句。 最后,如果发生了NullPointerException,程序基本上不可能正常运行或者恢复,所以我们需要提前进行null check的判断。 同样的,程序也不要对NullPointerException的父类RuntimeException, Exception, or Throwable进行捕捉。 不要throw RuntimeException, Exception, or Throwable 我们抛出异常主要是为了能够找到准确的处理异常的方法,如果直接抛出RuntimeException, Exception, 或者 Throwable就会导致程序无法准确处理特定的异常。 通常来说我们需要自定义RuntimeException, Exception, 或者 Throwable的子类,通过具体的子类来区分具体的异常类型。 不要抛出未声明的checked Exception 一般来说checked Exception是需要显示catch住,或者在调用方法上使用throws做申明的。 但是我们可以通过某些手段来绕过这种限制,从而在使用checked Exception的时候不需要遵守上述规则。 当然这样做是需要避免的。我们看一个例子: private static Throwable throwable; private ThrowException() throws Throwable { throw throwable; } public static synchronized void undeclaredThrow(Throwable throwable) { ThrowException.throwable = throwable; try { ThrowException.class.newInstance(); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } finally { ThrowException.throwable = null; } } 上面的例子中,我们定义了一个ThrowException的private构造函数,这个构造函数会throw一个throwable,这个throwable是从方法传入的。 在undeclaredThrow方法中,我们调用了ThrowException.class.newInstance()实例化一个ThrowException实例,因为需要调用构造函数,所以会抛出传入的throwable。 因为Exception是throwable的子类,如果我们在调用的时候传入一个checked Exception,很明显,我们的代码并没有对其进行捕获: public static void main(String[] args) { ThrowException.undeclaredThrow( new Exception("Any checked exception")); } 怎么解决这个问题呢?换个思路,我们可以使用Constructor.newInstance()来替代class.newInstance()。 try { Constructor constructor = ThrowException.class.getConstructor(new Class<?>[0]); constructor.newInstance(); } catch (InstantiationException e) { } catch (InvocationTargetException e) { System.out.println("catch exception!"); } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } finally { ThrowException.throwable = null; } 上面的例子,我们使用Constructor的newInstance方法来创建对象的实例。和class.newInstance不同的是,这个方法会抛出InvocationTargetException异常,并且把所有的异常都封装进去。 所以,这次我们获得了一个checked Exception。 原文链接 本文为阿里云原创内容,未经允许不得转载。

优秀的个人博客,低调大师

java安全编码指南之:Number操作

简介 java中可以被称为Number的有byte,short,int,long,float,double和char,我们在使用这些Nubmer的过程中,需要注意些什么内容呢?一起来看看吧。 Number的范围 每种Number类型都有它的范围,我们看下java中Number类型的范围: 考虑到我们最常用的int操作,虽然int的范围够大,但是如果我们在做一些int操作的时候还是可能超出int的范围。 超出了int范围会发送什么事情呢?看下面的例子: public void testIntegerOverflow(){ System.out.println(Integer.MAX_VALUE+1000); } 运行结果:-2147482649。 很明显Integer.MAX_VALUE+1000将会超出Integer的最大值范围,但是我们没有得到异常提醒,反而得到了一个错误的结果。 正确的操作是如果我们遇到了Overflow的问题,需要抛出异常:ArithmeticException。 怎么防止这种IntegerOverflow的问题呢?一般来讲,我们有下面几种方式。 第一种方式:在做Integer操作之前,进行预判断是否超出范围: 举个例子: static final int safeAdd(int left, int right) { if (right > 0 ? left > Integer.MAX_VALUE - right : left < Integer.MIN_VALUE - right) { throw new ArithmeticException("Integer overflow"); } return left + right; } 上面的例子中,我们需要进行两个整数相加操作,在相加之前,我们需要进行范围的判断,从而保证计算的安全性。 第二种方式:使用Math的addExact和multiplyExact方法: Math的addExact和multiplyExact方法已经提供了Overflow的判断,我们看下addExact的实现: public static int addExact(int x, int y) { int r = x + y; // HD 2-12 Overflow iff both arguments have the opposite sign of the result if (((x ^ r) & (y ^ r)) < 0) { throw new ArithmeticException("integer overflow"); } return r; } 看下怎么使用: public int addUseMath(int a, int b){ return Math.addExact(a,b); } 第三种方式:向上转型 既然超出了Integer的范围,那么我们可以用范围更大的long来存储数据。 public static long intRangeCheck(long value) { if ((value < Integer.MIN_VALUE) || (value > Integer.MAX_VALUE)) { throw new ArithmeticException("Integer overflow"); } return value; } public int addUseUpcasting(int a, int b){ return (int)intRangeCheck((long)a+(long)b); } 上面的例子中,我们将a+b转换成了两个long相加,从而保证不溢出范围。 然后进行一次范围比较,从而判断相加之后的结果是否仍然在整数范围内。 第四种方式:使用BigInteger 我们可以使用BigInteger.valueOf(a)将int转换成为BigInteger,再进行后续操作: public int useBigInteger(int a, int b){ return BigInteger.valueOf(a).add(BigInteger.valueOf(b)).intValue(); } 区分位运算和算数运算 我们通常会对Integer进行位运算或者算数运算。虽然可以进行两种运算,但是最好不要将两种运算同时进行,这样会造成混淆。 比如下面的例子: x += (x << 1) + 1; 上面的例子是想做什么呢?其实它是想将3x+1的值赋给x。 但是这样写出来让人很难理解,所以我们需要避免这样实现。 再看下面的一个例子: public void testBitwiseOperation(){ int i = -10; System.out.println(i>>>2); System.out.println(i>>2); System.out.println(i/4); } 本来我们想做的是将i除以4,结果发现只有最后一个才是我们要的结果。 我们来解释一下,第一个i>>>2是逻辑右移,将会把最左边的填充成0,所以得出的结果是一个正值1073741821。 第二个i>>2是算数右移,最左边的还是会填充成1,但是会向下取整,所以得出结果是-3. 直接使用i/4,我们是向上取整,所以得出结果是-2. 注意不要使用0作为除数 我们在使用变量作为除数的时候,一定要注意先判断是否为0. 兼容C++的无符号整数类型 在java中只有16位的char表示的是无符号整数,而int实际上表示的是带符号的整数。 而在C或者C++中是可以直接表示无符号的整数的,那么,如果我们有一个32位的无符号整数,该怎么用java来处理呢? public int readIntWrong(DataInputStream is) throws IOException { return is.readInt(); } 看上面的例子,我们从Stream中读取一个int值,如果是一个32位的无符号整数,那么读出来int就变成了有符号的负整数,这和我们的期望是相符的。 考虑一下,long是64位的,我们是不是可以使用long来表示32位的无符号整数呢? public long readIntRight(DataInputStream is) throws IOException{ return is.readInt() & 0xFFFFFFFFL; // Mask with 32 one-bits } 看上面的例子,我们返回的是long,如果将32位的int转换成为64位的long,会自动根据符号位进行补全。 所以这时候我们需要和0xFFFFFFFFL进行mask操作,将高32位重置为0. NAN和INFINITY 在整型运算中,除数是不能为0的,否则直接运行异常。但是在浮点数运算中,引入了NAN和INFINITY的概念,我们来看一下Double和Float中的定义。 public static final double POSITIVE_INFINITY = 1.0 / 0.0; public static final double NEGATIVE_INFINITY = -1.0 / 0.0; public static final double NaN = 0.0d / 0.0; public static final float POSITIVE_INFINITY = 1.0f / 0.0f; public static final float NEGATIVE_INFINITY = -1.0f / 0.0f; public static final float NaN = 0.0f / 0.0f; 1除以0就是INFINITY,而0除以0就是NaN。 接下来,我们看一下NAN和INFINITY的比较: public void compareInfinity(){ System.out.println(Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY); } 运行结果是true。 public void compareNaN(){ System.out.println(Double.NaN == Double.NaN); } 运行结果是false。 可以看到NaN和NaN相比是false。 那么我们怎么比较NaN呢? 别急,Double提供了一个isNaN方法,我们可以这样使用: System.out.println(Double.isNaN(Double.NaN)); 接下来我们看一个在代码中经常会用到的一个Double解析: public void incorrectParse(String userInput){ double val = 0; try { val = Double.valueOf(userInput); } catch (NumberFormatException e) { } //do something for val } 这段代码有没有问题?咋看下好像没有问题,但是,如果我们的userInput是NaN,Infinity,或者-Infinity,Double.valueOf是可以解析得到结果的。 public void testNaN(){ System.out.println(Double.valueOf("NaN")); System.out.println(Double.valueOf("Infinity")); System.out.println(Double.valueOf("-Infinity")); } 运行输出: NaN Infinity -Infinity 所以,我们还需要额外去判断NaN和Infinity: public void correctParse(String userInput){ double val = 0; try { val = Double.valueOf(userInput); } catch (NumberFormatException e) { } if (Double.isInfinite(val)){ // Handle infinity error } if (Double.isNaN(val)) { // Handle NaN error } //do something for val } 不要使用float或者double作为循环的计数器 考虑下面的代码: for (float x = 0.1f; x <= 1.0f; x += 0.1f) { System.out.println(x); } 上面的代码有什么问题呢? 我们都知道java中浮点数是不准确的,但是不一定有人知道为什么不准确。 这里给大家解释一下,计算机中所有与的数都是以二进制存储的,我们以0.6为例。 0.6转成为二进制格式是乘2取整,0.6x2=1.2,取整剩余0.2,继续上面的步骤0.2x2=0.4,0.4x2=0.8,0.8x2=1.6,取整剩余0.6,产生了一个循环。 所以0.6的二进制格式是.1001 1001 1001 1001 1001 1001 1001 ... 无限循环下去。 所以,有些小数是无法用二进制精确的表示的,最终导致使用float或者double作为计数器是不准的。 BigDecimal的构建 为了解决float或者Double计算中精度缺失的问题,我们通常会使用BigDecimal。 那么在使用BigDecimal的时候,请注意一定不要从float构建BigDecimal,否则可能出现意想不到的问题。 public void getFromFloat(){ System.out.println(new BigDecimal(0.1)); } 上面的代码,我们得到的结果是:0.1000000000000000055511151231257827021181583404541015625。 这是因为二进制无法完美的展示所有的小数。 所以,我们需要从String来构建BigDecimal: public void getFromString(){ System.out.println(new BigDecimal("0.1")); } 类型转换问题 在java中各种类型的Number可以互相进行转换: 比如: short to byte or char char to byte or short int to byte, short, or char long to byte, short, char, or int float to byte, short, char, int, or long double to byte, short, char, int, long, or float 或者反向: byte to short, int, long, float, or double short to int, long, float, or double char to int, long, float, or double int to long, float, or double long to float or double float to double 从大范围的类型转向小范围的类型时,我们要考虑是否超出转换类型范围的情况: public void intToByte(int i){ if ((i < Byte.MIN_VALUE) || (i > Byte.MAX_VALUE)) { throw new ArithmeticException("Value is out of range"); } byte b = (byte) i; } 比如上面的例子中,我们将int转换成为byte,那么在转换之前,需要先判断int是否超出了byte的范围。 同时我们还需要考虑到精度的切换,看下面的例子: public void intToFloat(){ System.out.println(subtraction(1111111111,1111111111)); } public int subtraction(int i , float j){ return i - (int)j; } 结果是多少呢? 答案不是0,而是-57。 为什么呢? 因为这里我们做了两次转换,第一次从1111111111转换到float,float虽然有32位,但是只有23位是存放真正的数值的,1位是符号位,剩下的8位是指数位。 所以从1111111111转换到float发送了精度丢失。 我们可以把subtraction方法修改一下,首先判断float的范围,如果超出了23bit的表示范围,则说明发送了精度丢失,我们需要抛出异常: public int subtraction(int i , float j){ System.out.println(j); if ((j > 0x007fffff) || (j < -0x800000)) { throw new ArithmeticException("Insufficient precision"); } return i - (int)j; } 当然还有一种办法,我们可以用精度更高的double来做转换,double有52位来存放真正的数据,所以足够了。 public int subtractionWithDouble(int i , double j){ System.out.println(j); return i - (int)j; } 本文的代码: learn-java-base-9-to-20/tree/master/security 本文已收录于 http://www.flydean.com/java-security-code-line-number/ 最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现! 欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

优秀的个人博客,低调大师

java安全编码指南之:对象构建

简介 程序员肯定是不缺对象的,因为随时都可以构建一个,对象多了肯定会出现点安全问题,一起来看看在java的对象构建中怎么保证对象的安全性吧。 构造函数的异常 考虑下面的一个例子: public class SensitiveOperation { public SensitiveOperation(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); } } 上面的例子中,我们在构造函数中做了一个securityCheck,因为这个securityCheck返回的值是false,所以会抛出SecurityException。 看下调用的例子: public static void main(String[] args) { SensitiveOperation sensitiveOperation = new SensitiveOperation(); sensitiveOperation.storeMoney(); } 这个调用会抛出下面的异常: Exception in thread "main" java.lang.SecurityException: Security check failed! at com.flydean.SensitiveOperation.<init>(SensitiveOperation.java:11) at com.flydean.SensitiveUsage.main(SensitiveUsage.java:10) 那么问题来了,上面的这个class是不是安全的呢? Finalizer Attack 上面的class不是final的,所以我们可以构造一个class去继承它。然后考虑这样一个问题,当构造函数抛出异常之后,会执行什么操作呢? 如果该对象已经被构建了,那么这个对象在GC的时候需要执行finalize方法。那么我们是不是可以在finalize方法中绕过安全检查呢? 看下面的例子: public class SensitiveOperationFinalizer extends SensitiveOperation{ public SensitiveOperationFinalizer(){ } [@Override](https://my.oschina.net/u/1162528) protected void finalize() { System.out.println("We can still do store Money action!"); this.storeMoney(); System.exit(0); } } 上的例子中,我们继承了SensitiveOperation,并且实现了finalize方法,在finalize中,我们调用了storeMoney。看下运行的代码: public void testFinalizer() throws InterruptedException { try { SensitiveOperation sensitiveOperation = new SensitiveOperationFinalizer(); sensitiveOperation.storeMoney(); }catch (Exception e){ System.out.println(e.getMessage()); } System.gc(); Thread.sleep(10000); } 运行结果: Security check failed! We can still do store Money action! Store 1000000 RMB! 可以看到,虽然我们构造函数抛出了异常,但是storeMoney的操作还是被执行了! 这个操作就叫做Finalizer Attack。 解决Finalizer Attack 怎么解决这个构造函数抛出异常的问题呢?这里给大家介绍几种解决方法。 使用final class 如果使用final class,那么类是不能够被继承的,问题自然就解决了。 public final class SensitiveOperationFinal { public SensitiveOperationFinal(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); } } 使用final finalize方法 因为子类想要重写finalize方法,如果我们的父类中finalize方法定义为final,也可以解决这个问题。 public final class SensitiveOperationFinal { public SensitiveOperationFinal(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); } final protected void finalize() { } } 使用flag变量 我们可以在对象构建完毕的时候设置一个flag变量,然后在每次安全操作的时候都去判断一下这个flag变量,这样也可以避免之前提到的问题: public class SensitiveOperationFlag { private volatile boolean flag= false; public SensitiveOperationFlag(){ if(!doSecurityCheck()){ throw new SecurityException("Security check failed!"); } flag=true; } //Security check return false private boolean doSecurityCheck(){ return false; } public void storeMoney(){ if(!flag){ System.out.println("Object is not initiated yet!"); return; } System.out.println("Store 1000000 RMB!"); } } 注意,这里flag需要设置为volatile,只有这样才能保证构造函数在flag设置之前执行。也就是说需要保证happens-before特性。 使用this或者super 在JDK6或者更高版本中,如果对象的构造函数在java.lang.Object构造函数退出之前引发异常,则JVM将不会执行该对象的finalize方法。 因为Java确保java.lang.Object构造函数在任何构造函数的第一条语句之上或之前执行。如果构造函数中的第一个语句是对超类的构造函数或同一个类中的另一个构造函数的调用,则java.lang.Object构造函数将在该调用中的某个位置执行。否则,Java将在该构造函数的代码中的任何一个执行之前执行超类的默认构造函数,并且将通过隐式调用执行java.lang.Object构造函数。 也就是说如果异常发生在构造函数中的第一条this或者super中的时候,JVM将不会调用对象的finalize方法: public class SensitiveOperationThis { public SensitiveOperationThis(){ this(doSecurityCheck()); } private SensitiveOperationThis(boolean secure) { } //Security check return false private static boolean doSecurityCheck(){ throw new SecurityException("Security check failed!"); } public void storeMoney(){ System.out.println("Store 1000000 RMB!"); } } 本文的例子: learn-java-base-9-to-20/tree/master/security 本文已收录于 http://www.flydean.com/java-security-code-line-object/ 最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现! 欢迎关注我的公众号:「程序那些事」,懂技术,更懂你!

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册