// ex: se sts=4 sw=4 expandtab: /* * Yeti language compiler java bytecode generator. * * Copyright (c) 2007-2013 Madis Janson * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package yeti.lang.compiler; import java.util.*; import yeti.renamed.asmx.*; import yeti.lang.Tag; import java.io.IOException; import java.io.InputStream; /* * Encoding: * * 00 - format identifier * Follows type description * 00 XX XX - free type variable XXXX * XX, where XX is 01..08 - * primitives (same as YType.type UNIT - MAP_MARKER) * 09 x.. y.. - Function x -> y * 0A e.. i.. t.. - MAP<e,i,t> * 0B <requiredMembers...> FF <allowedMembers...> FF - Struct * 0C <requiredMembers...> FF <allowedMembers...> FF - Variant * 0C F9 ... - Variant with FL_ANY_CASE flag * (0B | 0C) F9? F8 ... - Variant or struct with FL_FLEX_TYPEDEF flag * 0D XX XX <param...> FF - java type * 0E e.. FF - java array e[] * FA XX XX <parameters...> FF - opaque type instance (X is "module:name") * FB XX XX - non-free type variable XXXX * FC ... - mutable field type * FD ... - the following type variable is ORDERED * FE XX XX - reference to non-primitive type * Follows list of type definitions * <typeDef name * typeDef array of type descriptions (FF) * ...> * FF * Follows utf8 encoded direct field mapping. * XX XX XX XX - length * 'F' fieldName 00 function-class 00 * 'P' fieldName 00 - property (field mapping as null) */ class TypeAttr extends Attribute { static final byte END = -1; static final byte REF = -2; static final byte ORDERED = -3; static final byte MUTABLE = -4; static final byte TAINTED = -5; static final byte OPAQUE = -6; static final byte ANYCASE = -7; static final byte SMART = -8; final ModuleType moduleType; private ByteVector encoded; final Compiler compiler; TypeAttr(ModuleType mt, Compiler ctx) { super("YetiModuleType"); moduleType = mt; compiler = ctx; } private static final class EncodeType { ClassWriter cw; ByteVector buf = new ByteVector(); Map refs = new IdentityHashMap(); Map vars = new IdentityHashMap(); Map opaque = new HashMap(); void writeMap(Map m) { if (m != null) for (Iterator i = m.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); YType t = (YType) e.getValue(); if (t.field == YetiType.FIELD_MUTABLE) buf.putByte(MUTABLE); write(t); int name = cw.newUTF8((String) e.getKey()); buf.putShort(name); } buf.putByte(END); } void writeArray(YType[] param) { for (int i = 0; i < param.length; ++i) write(param[i]); buf.putByte(END); } void write(YType type) { type = type.deref(); if (type.type == YetiType.VAR) { Integer id = (Integer) vars.get(type); if (id == null) { vars.put(type, id = new Integer(vars.size())); if (id.intValue() > 0x7fff) throw new RuntimeException("Too many type parameters"); if ((type.flags & YetiType.FL_ORDERED_REQUIRED) != 0) buf.putByte(ORDERED); } buf.putByte((type.flags & YetiType.FL_TAINTED_VAR) == 0 ? YetiType.VAR : TAINTED); buf.putShort(id.intValue()); return; } if (type.type < YetiType.PRIMITIVES.length && YetiType.PRIMITIVES[type.type] != null) { // primitives buf.putByte(type.type); return; } Integer id = (Integer) refs.get(type); if (id != null) { if (id.intValue() > 0x7fff) throw new RuntimeException("Too many type parts"); buf.putByte(REF); buf.putShort(id.intValue()); return; } refs.put(type, new Integer(refs.size())); if (type.type >= YetiType.OPAQUE_TYPES) { Object idstr = opaque.get(new Integer(type.type)); if (idstr == null) idstr = type.requiredMembers.keySet().toArray()[0]; buf.putByte(OPAQUE); buf.putShort(cw.newUTF8(idstr.toString())); writeArray(type.param); return; } buf.putByte(type.type); if (type.type == YetiType.FUN) { write(type.param[0]); write(type.param[1]); } else if (type.type == YetiType.MAP) { writeArray(type.param); } else if (type.type == YetiType.STRUCT || type.type == YetiType.VARIANT) { if ((type.allowedMembers == null || type.allowedMembers.isEmpty()) && (type.requiredMembers == null || type.requiredMembers.isEmpty())) throw new CompileException(0, 0, type.type == YetiType.STRUCT ? "Internal error: empty struct" : "Internal error: empty variant"); if ((type.flags & YetiType.FL_ANY_CASE) != 0) buf.putByte(ANYCASE); if ((type.flags & YetiType.FL_FLEX_TYPEDEF) != 0) buf.putByte(SMART); writeMap(type.allowedMembers); writeMap(type.requiredMembers); } else if (type.type == YetiType.JAVA) { buf.putShort(cw.newUTF8(type.javaType.description)); writeArray(type.param); } else if (type.type == YetiType.JAVA_ARRAY) { write(type.param[0]); } else { throw new RuntimeException("Unknown type: " + type.type); } } void writeTypeDefs(Map typeDefs) { for (Iterator i = typeDefs.entrySet().iterator(); i.hasNext();) { Map.Entry e = (Map.Entry) i.next(); buf.putShort(cw.newUTF8((String) e.getKey())); writeArray((YType[]) e.getValue()); } buf.putByte(END); } } private static final class DecodeType { private static final int VAR_DEPTH = 1; final ClassReader cr; final byte[] in; final char[] buf; int p; final int end; final Map vars = new HashMap(); final List refs = new ArrayList(); final Map opaqueTypes; DecodeType(ClassReader cr, int off, int len, char[] buf, Map opaqueTypes) { this.cr = cr; in = cr.b; p = off; end = p + len; this.buf = buf; this.opaqueTypes = opaqueTypes; } Map readMap() { if (in[p] == END) { ++p; return null; } Map res = new IdentityHashMap(); while (in[p] != END) { YType t = read(); res.put(cr.readUTF8(p, buf).intern(), t); p += 2; } ++p; return res; } YType[] readArray() { List param = new ArrayList(); while (in[p] != END) param.add(read()); ++p; return (YType[]) param.toArray(new YType[param.size()]); } YType read() { YType t; int tv; if (p >= end) throw new RuntimeException("Invalid type description"); switch (tv = in[p++]) { case YetiType.VAR: case TAINTED: { Integer var = new Integer(cr.readUnsignedShort(p)); p += 2; if ((t = (YType) vars.get(var)) == null) vars.put(var, t = new YType(VAR_DEPTH)); if (tv == TAINTED) t.flags |= YetiType.FL_TAINTED_VAR; return t; } case ORDERED: t = read(); t.flags |= YetiType.FL_ORDERED_REQUIRED; return t; case REF: { int v = cr.readUnsignedShort(p); p += 2; if (refs.size() <= v) throw new RuntimeException("Illegal type reference"); return (YType) refs.get(v); } case MUTABLE: return YetiType.fieldRef(1, read(), YetiType.FIELD_MUTABLE); } if (tv < YetiType.PRIMITIVES.length && tv > 0) return YetiType.PRIMITIVES[tv]; t = new YType(tv, null); refs.add(t); if (t.type == YetiType.FUN) { t.param = new YType[2]; t.param[0] = read(); t.param[1] = read(); } else if (tv == YetiType.MAP) { t.param = readArray(); } else if (tv == YetiType.STRUCT || tv == YetiType.VARIANT) { if (in[p] == ANYCASE) { t.flags |= YetiType.FL_ANY_CASE; ++p; } if (in[p] == SMART) { t.flags |= YetiType.FL_FLEX_TYPEDEF; ++p; } t.allowedMembers = readMap(); t.requiredMembers = readMap(); Map param; if (t.allowedMembers == null) { if ((param = t.requiredMembers) == null) param = new IdentityHashMap(); } else if (t.requiredMembers == null) { param = t.allowedMembers; } else { param = new IdentityHashMap(t.allowedMembers); param.putAll(t.requiredMembers); } t.param = new YType[param.size() + 1]; t.param[0] = new YType(VAR_DEPTH); Iterator i = param.values().iterator(); for (int n = 1; i.hasNext(); ++n) t.param[n] = (YType) i.next(); } else if (tv == YetiType.JAVA) { t.javaType = JavaType.fromDescription(cr.readUTF8(p, buf)); p += 2; t.param = readArray(); } else if (tv == YetiType.JAVA_ARRAY) { t.param = new YType[] { read() }; } else if (tv == OPAQUE) { String idstr = cr.readUTF8(p, buf); p += 2; synchronized (opaqueTypes) { YType old = (YType) opaqueTypes.get(idstr); if (old != null) { t.type = old.type; } else { t.type = opaqueTypes.size() + YetiType.OPAQUE_TYPES; opaqueTypes.put(idstr, t); } } t.requiredMembers = Collections.singletonMap(idstr, YetiType.NO_TYPE); t.param = readArray(); } else { throw new RuntimeException("Unknown type id: " + tv); } return t; } Map readTypeDefs() { Map result = new HashMap(); while (in[p] != END) { String name = cr.readUTF8(p, buf); p += 2; result.put(name.intern(), readArray()); } ++p; return result; } } protected Attribute read(ClassReader cr, int off, int len, char[] buf, int codeOff, Label[] labels) { int hdr = 3; switch (cr.b[off]) { case 0: hdr = 1; // version 0 has only version in header case 1: break; default: throw new RuntimeException("Unknown type encoding: " + cr.b[off]); } DecodeType decoder = new DecodeType(cr, off + hdr, len - hdr, buf, compiler.opaqueTypes); YType t = decoder.read(); Map typeDefs = decoder.readTypeDefs(); return new TypeAttr(new ModuleType(t, typeDefs, hdr != 1, -1), compiler); } protected ByteVector write(ClassWriter cw, byte[] code, int len, int maxStack, int maxLocals) { if (encoded != null) { return encoded; } EncodeType enc = new EncodeType(); Iterator i = moduleType.typeDefs.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); YType[] def = (YType[]) e.getValue(); YType t = def[def.length - 1]; if (t.type >= YetiType.OPAQUE_TYPES && t.requiredMembers == null) enc.opaque.put(new Integer(t.type), moduleType.name + ':' + e.getKey()); } enc.cw = cw; enc.buf.putByte(1); // encoding version enc.buf.putShort(0); enc.write(moduleType.type); enc.writeTypeDefs(moduleType.typeDefs); return encoded = enc.buf; } } class ModuleType extends YetiParser.Node { final YType type; final Map typeDefs; final boolean directFields; Scope typeScope; String topDoc; String name; boolean deprecated; boolean fromClass; boolean hasSource; long lastModified; private YType[] free; ModuleType(YType type, Map typeDefs, boolean directFields, int depth) { this.typeDefs = typeDefs; this.directFields = directFields; this.type = copy(depth, type); } YType copy(int depth, YType t) { if (t == null) t = type; if (depth == -1) return t; if (free == null) { List freeVars = new ArrayList(); YetiType.getAllTypeVar(freeVars, null, t, false); free = (YType[]) freeVars.toArray(new YType[freeVars.size()]); } return YetiType.copyType(t, YetiType.createFreeVars(free, depth), new IdentityHashMap()); } Tag yetiType() { return TypeDescr.yetiType(type, typeScope != null ? TypePattern.toPattern(typeScope, true) : TypePattern.toPattern(typeDefs), null); } } class YetiTypeVisitor extends ClassVisitor { TypeAttr typeAttr; private boolean deprecated; YetiTypeVisitor() { super(Opcodes.ASM5); } public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { deprecated = (access & Opcodes.ACC_DEPRECATED) != 0; } public void visitEnd() { } public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return null; } public void visitAttribute(Attribute attr) { if (attr.type == "YetiModuleType") { if (typeAttr != null) throw new RuntimeException( "Multiple YetiModuleType attributes are forbidden"); typeAttr = (TypeAttr) attr; } } public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { return null; } public void visitInnerClass(String name, String outerName, String innerName, int access) { } public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { return null; } public void visitOuterClass(String owner, String name, String desc) { } public void visitSource(String source, String debug) { } static ModuleType readType(Compiler compiler, InputStream in) throws IOException { YetiTypeVisitor visitor = new YetiTypeVisitor(); ClassReader reader = new ClassReader(in); reader.accept(visitor, new Attribute[] { new TypeAttr(null, compiler) }, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES); in.close(); if (visitor.typeAttr == null) return null; ModuleType mt = visitor.typeAttr.moduleType; if (mt != null) mt.deprecated = visitor.deprecated; mt.name = reader.getClassName(); mt.fromClass = true; return mt; } static ModuleType getType(Compiler ctx, YetiParser.Node node, String name, boolean byPath) { final boolean bySourcePath = false; final String cname = name.toLowerCase(); ModuleType t = (ModuleType) ctx.types.get(cname); if (t != null) return t; try { int flags = byPath ? Compiler.CF_FORCE_COMPILE : Compiler.CF_RESOLVE_MODULE; if (node == null && !byPath) { t = ctx.moduleType(cname); if (t != null) return t; flags |= Compiler.CF_IGNORE_CLASSPATH; } t = (ModuleType) ctx.types.get(ctx.compile(name, null, flags | Compiler.CF_EXPECT_MODULE).name); if (t == null) throw new CompileException(node, "Could not compile `" + name + "' to a module"); if (!byPath && !cname.equals(t.name)) throw new CompileException(node, "Found " + t.name.replace('/', '.') + " instead of " + name.replace('/', '.')); if (!t.directFields) ctx.warn(new CompileException(node, "The `" + t.name.replace('/', '.') + "' module is compiled " + "with pre-0.9.8 version\n of Yeti compiler and " + "might not work with newer standard library.")); return t; } catch (CompileException ex) { if (ex.line == 0) if (node != null) { ex.line = node.line; ex.col = node.col; } throw ex; } catch (RuntimeException ex) { throw ex; } catch (Exception ex) { throw new CompileException(node, ex.getMessage()); } } }