diff options
Diffstat (limited to 'src/cmd')
283 files changed, 21245 insertions, 7143 deletions
diff --git a/src/cmd/5c/gc.h b/src/cmd/5c/gc.h index 5349114f8..20652682b 100644 --- a/src/cmd/5c/gc.h +++ b/src/cmd/5c/gc.h @@ -304,7 +304,8 @@ void gpseudo(int, Sym*, Node*); int swcmp(const void*, const void*); void doswit(Node*); void swit1(C1*, int, int32, Node*); -void cas(void); +void swit2(C1*, int, int32, Node*); +void newcase(void); void bitload(Node*, Node*, Node*, Node*, Node*); void bitstore(Node*, Node*, Node*, Node*, Node*); int mulcon(Node*, Node*); diff --git a/src/cmd/5c/swt.c b/src/cmd/5c/swt.c index 32032532f..7268f9af2 100644 --- a/src/cmd/5c/swt.c +++ b/src/cmd/5c/swt.c @@ -28,12 +28,31 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. - #include "gc.h" void swit1(C1 *q, int nc, int32 def, Node *n) { + Node nreg; + + if(typev[n->type->etype]) { + regsalloc(&nreg, n); + nreg.type = types[TVLONG]; + cgen(n, &nreg); + swit2(q, nc, def, &nreg); + return; + } + + regalloc(&nreg, n, Z); + nreg.type = types[TLONG]; + cgen(n, &nreg); + swit2(q, nc, def, &nreg); + regfree(&nreg); +} + +void +swit2(C1 *q, int nc, int32 def, Node *n) +{ C1 *r; int i; int32 v; @@ -65,12 +84,12 @@ swit1(C1 *q, int nc, int32 def, Node *n) sp = p; gopcode(OEQ, nodconst(r->val), n, Z); /* just gen the B.EQ */ patch(p, r->label); - swit1(q, i, def, n); + swit2(q, i, def, n); if(debug['W']) print("case < %.8ux\n", r->val); patch(sp, pc); - swit1(r+1, nc-i-1, def, n); + swit2(r+1, nc-i-1, def, n); return; direct: diff --git a/src/cmd/5g/cgen.c b/src/cmd/5g/cgen.c index 0ea8695a0..8865027dc 100644 --- a/src/cmd/5g/cgen.c +++ b/src/cmd/5g/cgen.c @@ -213,11 +213,11 @@ cgen(Node *n, Node *res) goto ret; case OMINUS: + regalloc(&n1, nl->type, N); + cgen(nl, &n1); nodconst(&n3, nl->type, 0); regalloc(&n2, nl->type, res); - regalloc(&n1, nl->type, N); gmove(&n3, &n2); - cgen(nl, &n1); gins(optoas(OSUB, nl->type), &n1, &n2); gmove(&n2, res); regfree(&n1); @@ -850,6 +850,7 @@ bgen(Node *n, int true, Prog *to) int et, a; Node *nl, *nr, *r; Node n1, n2, n3, n4, tmp; + NodeList *ll; Prog *p1, *p2; USED(n4); // in unreachable code below @@ -950,7 +951,10 @@ bgen(Node *n, int true, Prog *to) p1 = gbranch(AB, T); p2 = gbranch(AB, T); patch(p1, pc); + ll = n->ninit; + n->ninit = nil; bgen(n, 1, p2); + n->ninit = ll; patch(gbranch(AB, T), to); patch(p2, pc); goto ret; @@ -1062,7 +1066,7 @@ bgen(Node *n, int true, Prog *to) } if(nr->op == OLITERAL) { - if(nr->val.ctype == CTINT && mpgetfix(nr->val.u.xval) == 0) { + if(isconst(nr, CTINT) && mpgetfix(nr->val.u.xval) == 0) { gencmp0(nl, nl->type, a, to); break; } @@ -1189,7 +1193,7 @@ stkof(Node *n) * NB: character copy assumed little endian architecture */ void -sgen(Node *n, Node *res, int32 w) +sgen(Node *n, Node *res, int64 w) { Node dst, src, tmp, nend; int32 c, odst, osrc; @@ -1197,14 +1201,17 @@ sgen(Node *n, Node *res, int32 w) Prog *p, *ploop; if(debug['g']) { - print("\nsgen w=%d\n", w); + print("\nsgen w=%lld\n", w); dump("r", n); dump("res", res); } - if(w < 0) - fatal("sgen copy %d", w); + if(n->ullman >= UINF && res->ullman >= UINF) fatal("sgen UINF"); + + if(w < 0 || (int32)w != w) + fatal("sgen copy %lld", w); + if(n->type == T) fatal("sgen: missing type"); @@ -1236,7 +1243,7 @@ sgen(Node *n, Node *res, int32 w) break; } if(w%align) - fatal("sgen: unaligned size %d (align=%d) for %T", w, align, n->type); + fatal("sgen: unaligned size %lld (align=%d) for %T", w, align, n->type); c = w / align; // offset on the stack diff --git a/src/cmd/5g/gg.h b/src/cmd/5g/gg.h index c826d2652..7dbf3beec 100644 --- a/src/cmd/5g/gg.h +++ b/src/cmd/5g/gg.h @@ -43,6 +43,8 @@ struct Prog uchar scond; }; +#define TEXTFLAG reg + #define REGALLOC_R0 0 #define REGALLOC_RMAX REGEXT #define REGALLOC_F0 (REGALLOC_RMAX+1) @@ -92,7 +94,7 @@ void igen(Node*, Node*, Node*); void agenr(Node *n, Node *a, Node *res); vlong fieldoffset(Type*, Node*); void bgen(Node*, int, Prog*); -void sgen(Node*, Node*, int32); +void sgen(Node*, Node*, int64); void gmove(Node*, Node*); Prog* gins(int, Node*, Node*); int samaddr(Node*, Node*); diff --git a/src/cmd/5g/ggen.c b/src/cmd/5g/ggen.c index 3f38318e7..832767e86 100644 --- a/src/cmd/5g/ggen.c +++ b/src/cmd/5g/ggen.c @@ -14,7 +14,6 @@ defframe(Prog *ptxt) { // fill in argument size ptxt->to.type = D_CONST2; - ptxt->reg = 0; // flags ptxt->to.offset2 = rnd(curfn->type->argwid, widthptr); // fill in final stack size diff --git a/src/cmd/5g/gobj.c b/src/cmd/5g/gobj.c index 9f728dee7..b562ba888 100644 --- a/src/cmd/5g/gobj.c +++ b/src/cmd/5g/gobj.c @@ -307,6 +307,7 @@ datastring(char *s, int len, Addr *a) a->offset = widthptr+4; // skip header a->reg = NREG; a->sym = sym; + a->node = sym->def; } /* @@ -325,6 +326,7 @@ datagostring(Strlit *sval, Addr *a) a->offset = 0; // header a->reg = NREG; a->sym = sym; + a->node = sym->def; } void diff --git a/src/cmd/5g/gsubr.c b/src/cmd/5g/gsubr.c index f8920df87..d8460ff75 100644 --- a/src/cmd/5g/gsubr.c +++ b/src/cmd/5g/gsubr.c @@ -356,7 +356,7 @@ regalloc(Node *n, Type *t, Node *o) { int i, et, fixfree, floatfree; - if(debug['r']) { + if(0 && debug['r']) { fixfree = 0; for(i=REGALLOC_R0; i<=REGALLOC_RMAX; i++) if(reg[i] == 0) @@ -429,7 +429,7 @@ regfree(Node *n) { int i, fixfree, floatfree; - if(debug['r']) { + if(0 && debug['r']) { fixfree = 0; for(i=REGALLOC_R0; i<=REGALLOC_RMAX; i++) if(reg[i] == 0) @@ -512,8 +512,15 @@ nodarg(Type *t, int fp) fatal("nodarg: offset not computed for %T", t); n->xoffset = t->width; n->addable = 1; + n->orig = t->nname; fp: + // Rewrite argument named _ to __, + // or else the assignment to _ will be + // discarded during code generation. + if(isblank(n)) + n->sym = lookup("__"); + switch(fp) { default: fatal("nodarg %T %d", t, fp); @@ -1263,6 +1270,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->sym = n->left->sym; a->type = D_OREG; a->name = D_PARAM; + a->node = n->left->orig; break; case ONAME: @@ -1275,6 +1283,9 @@ naddr(Node *n, Addr *a, int canemitcode) } a->offset = n->xoffset; a->sym = n->sym; + a->node = n->orig; + //if(a->node >= (Node*)&n) + // fatal("stack node"); if(a->sym == S) a->sym = lookup(".noname"); if(n->method) { @@ -1293,8 +1304,6 @@ naddr(Node *n, Addr *a, int canemitcode) break; case PAUTO: a->name = D_AUTO; - if (n->sym) - a->node = n->orig; break; case PPARAM: case PPARAMOUT: @@ -1317,6 +1326,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->dval = mpgetflt(n->val.u.fval); break; case CTINT: + case CTRUNE: a->sym = S; a->type = D_CONST; a->offset = mpgetfix(n->val.u.xval); @@ -1774,7 +1784,7 @@ sudoaddable(int as, Node *n, Addr *a, int *w) switch(n->op) { case OLITERAL: - if(n->val.ctype != CTINT) + if(!isconst(n, CTINT)) break; v = mpgetfix(n->val.u.xval); if(v >= 32000 || v <= -32000) diff --git a/src/cmd/5g/reg.c b/src/cmd/5g/reg.c index 9dd3f07f1..b72b9c165 100644 --- a/src/cmd/5g/reg.c +++ b/src/cmd/5g/reg.c @@ -42,6 +42,9 @@ int noreturn(Prog *p); static int first = 0; +static void fixjmp(Prog*); + + Reg* rega(void) { @@ -92,8 +95,8 @@ setoutvar(void) ovar.b[z] |= bit.b[z]; t = structnext(&save); } -//if(bany(b)) -//print("ovars = %Q\n", &ovar); +//if(bany(ovar)) +//print("ovar = %Q\n", ovar); } void @@ -171,6 +174,8 @@ regopt(Prog *firstp) if(first == 0) { fmtinstall('Q', Qconv); } + + fixjmp(firstp); first++; if(debug['K']) { @@ -911,10 +916,12 @@ mkvar(Reg *r, Adr *a) } node = a->node; - if(node == N || node->op != ONAME || node->orig != N) + if(node == N || node->op != ONAME || node->orig == N) goto none; node = node->orig; - if(node->sym->name[0] == '.') + if(node->orig != node) + fatal("%D: bad node", a); + if(node->sym == S || node->sym->name[0] == '.') goto none; et = a->etype; o = a->offset; @@ -1157,10 +1164,12 @@ loopit(Reg *r, int32 nr) r1 = rpo2r[i]; me = r1->rpo; d = -1; - if(r1->p1 != R && r1->p1->rpo < me) + // rpo2r[r->rpo] == r protects against considering dead code, + // which has r->rpo == 0. + if(r1->p1 != R && rpo2r[r1->p1->rpo] == r1->p1 && r1->p1->rpo < me) d = r1->p1->rpo; for(r1 = r1->p2; r1 != nil; r1 = r1->p2link) - if(r1->rpo < me) + if(rpo2r[r1->rpo] == r1 && r1->rpo < me) d = rpolca(idom, d, r1->rpo); idom[i] = d; } @@ -1571,7 +1580,7 @@ dumpone(Reg *r) if(bany(&r->refahead)) print(" ra:%Q ", r->refahead); if(bany(&r->calbehind)) - print("cb:%Q ", r->calbehind); + print(" cb:%Q ", r->calbehind); if(bany(&r->calahead)) print(" ca:%Q ", r->calahead); if(bany(&r->regdiff)) @@ -1606,3 +1615,123 @@ dumpit(char *str, Reg *r0) // } } } + +/* + * the code generator depends on being able to write out JMP (B) + * instructions that it can jump to now but fill in later. + * the linker will resolve them nicely, but they make the code + * longer and more difficult to follow during debugging. + * remove them. + */ + +/* what instruction does a JMP to p eventually land on? */ +static Prog* +chasejmp(Prog *p, int *jmploop) +{ + int n; + + n = 0; + while(p != P && p->as == AB && p->to.type == D_BRANCH) { + if(++n > 10) { + *jmploop = 1; + break; + } + p = p->to.branch; + } + return p; +} + +/* + * reuse reg pointer for mark/sweep state. + * leave reg==nil at end because alive==nil. + */ +#define alive ((void*)0) +#define dead ((void*)1) + +/* mark all code reachable from firstp as alive */ +static void +mark(Prog *firstp) +{ + Prog *p; + + for(p=firstp; p; p=p->link) { + if(p->regp != dead) + break; + p->regp = alive; + if(p->as != ABL && p->to.type == D_BRANCH && p->to.branch) + mark(p->to.branch); + if(p->as == AB || p->as == ARET || (p->as == ABL && noreturn(p))) + break; + } +} + +static void +fixjmp(Prog *firstp) +{ + int jmploop; + Prog *p, *last; + + if(debug['R'] && debug['v']) + print("\nfixjmp\n"); + + // pass 1: resolve jump to B, mark all code as dead. + jmploop = 0; + for(p=firstp; p; p=p->link) { + if(debug['R'] && debug['v']) + print("%P\n", p); + if(p->as != ABL && p->to.type == D_BRANCH && p->to.branch && p->to.branch->as == AB) { + p->to.branch = chasejmp(p->to.branch, &jmploop); + if(debug['R'] && debug['v']) + print("->%P\n", p); + } + p->regp = dead; + } + if(debug['R'] && debug['v']) + print("\n"); + + // pass 2: mark all reachable code alive + mark(firstp); + + // pass 3: delete dead code (mostly JMPs). + last = nil; + for(p=firstp; p; p=p->link) { + if(p->regp == dead) { + if(p->link == P && p->as == ARET && last && last->as != ARET) { + // This is the final ARET, and the code so far doesn't have one. + // Let it stay. + } else { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + } + if(last) + last->link = p; + last = p; + } + last->link = P; + + // pass 4: elide JMP to next instruction. + // only safe if there are no jumps to JMPs anymore. + if(!jmploop) { + last = nil; + for(p=firstp; p; p=p->link) { + if(p->as == AB && p->to.type == D_BRANCH && p->to.branch == p->link) { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + if(last) + last->link = p; + last = p; + } + last->link = P; + } + + if(debug['R'] && debug['v']) { + print("\n"); + for(p=firstp; p; p=p->link) + print("%P\n", p); + print("\n"); + } +} diff --git a/src/cmd/5l/asm.c b/src/cmd/5l/asm.c index 5b7f6f111..fe3a2f3f2 100644 --- a/src/cmd/5l/asm.c +++ b/src/cmd/5l/asm.c @@ -73,6 +73,7 @@ enum { ElfStrShstrtab, ElfStrRelPlt, ElfStrPlt, + ElfStrNoteNetbsdIdent, NElfStr }; @@ -164,6 +165,8 @@ doelf(void) elfstr[ElfStrText] = addstring(shstrtab, ".text"); elfstr[ElfStrData] = addstring(shstrtab, ".data"); elfstr[ElfStrBss] = addstring(shstrtab, ".bss"); + if(HEADTYPE == Hnetbsd) + elfstr[ElfStrNoteNetbsdIdent] = addstring(shstrtab, ".note.netbsd.ident"); addstring(shstrtab, ".rodata"); addstring(shstrtab, ".gosymtab"); addstring(shstrtab, ".gopclntab"); @@ -232,7 +235,7 @@ doelf(void) /* define dynamic elf table */ s = lookup(".dynamic", 0); s->reachable = 1; - s->type = SELFROSECT; + s->type = SELFSECT; // writable /* * .dynamic table @@ -251,6 +254,7 @@ doelf(void) elfwritedynent(s, DT_PLTREL, DT_REL); elfwritedynentsymsize(s, DT_PLTRELSZ, lookup(".rel.plt", 0)); elfwritedynentsym(s, DT_JMPREL, lookup(".rel.plt", 0)); + elfwritedynent(s, DT_DEBUG, 0); elfwritedynent(s, DT_NULL, 0); } } @@ -293,7 +297,7 @@ asmb(void) { int32 t; int a, dynsym; - uint32 fo, symo, startva; + uint32 fo, symo, startva, resoff; ElfEhdr *eh; ElfPhdr *ph, *pph; ElfShdr *sh; @@ -321,11 +325,6 @@ asmb(void) cseek(segdata.fileoff); datblk(segdata.vaddr, segdata.filelen); - /* output read-only data in text segment */ - sect = segtext.sect->next; - cseek(sect->vaddr - segtext.vaddr + segtext.fileoff); - datblk(sect->vaddr, sect->len); - if(iself) { /* index of elf text section; needed by asmelfsym, double-checked below */ /* !debug['d'] causes extra sections before the .text section */ @@ -335,6 +334,8 @@ asmb(void) if(elfverneed) elftextsh += 2; } + if(HEADTYPE == Hnetbsd) + elftextsh += 1; } /* output symbol table */ @@ -370,7 +371,7 @@ asmb(void) cseek(symo); if(iself) { if(debug['v']) - Bprint(&bso, "%5.2f elfsym\n", cputime()); + Bprint(&bso, "%5.2f elfsym\n", cputime()); asmelfsym(); cflush(); cwrite(elfstrdat, elfstrsize); @@ -454,6 +455,7 @@ asmb(void) eh = getElfEhdr(); fo = HEADR; startva = INITTEXT - fo; /* va of byte 0 of file */ + resoff = ELFRESERVE; /* This null SHdr must appear before all others */ newElfShdr(elfstr[ElfStrEmpty]); @@ -486,7 +488,7 @@ asmb(void) sh->addralign = 1; if(interpreter == nil) interpreter = linuxdynld; - elfinterp(sh, startva, interpreter); + resoff -= elfinterp(sh, startva, resoff, interpreter); ph = newElfPhdr(); ph->type = PT_INTERP; @@ -494,11 +496,24 @@ asmb(void) phsh(ph, sh); } + if(HEADTYPE == Hnetbsd) { + sh = newElfShdr(elfstr[ElfStrNoteNetbsdIdent]); + sh->type = SHT_NOTE; + sh->flags = SHF_ALLOC; + sh->addralign = 4; + resoff -= elfnetbsdsig(sh, startva, resoff); + + ph = newElfPhdr(); + ph->type = PT_NOTE; + ph->flags = PF_R; + phsh(ph, sh); + } + elfphload(&segtext); elfphload(&segdata); /* Dynamic linking sections */ - if (!debug['d']) { /* -d suppresses dynamic loader format */ + if(!debug['d']) { /* -d suppresses dynamic loader format */ /* S headers for dynamic linking */ sh = newElfShdr(elfstr[ElfStrGot]); sh->type = SHT_PROGBITS; @@ -589,7 +604,7 @@ asmb(void) for(sect=segdata.sect; sect!=nil; sect=sect->next) elfshbits(sect); - if (!debug['s']) { + if(!debug['s']) { sh = newElfShdr(elfstr[ElfStrSymtab]); sh->type = SHT_SYMTAB; sh->off = symo; @@ -631,8 +646,10 @@ asmb(void) a += elfwritehdr(); a += elfwritephdrs(); a += elfwriteshdrs(); - cflush(); - if(a+elfwriteinterp() > ELFRESERVE) + a += elfwriteinterp(elfstr[ElfStrInterp]); + if(HEADTYPE == Hnetbsd) + a += elfwritenetbsdsig(elfstr[ElfStrNoteNetbsdIdent]); + if(a > ELFRESERVE) diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE); break; } diff --git a/src/cmd/5l/l.h b/src/cmd/5l/l.h index dabe93d37..b1a48ded8 100644 --- a/src/cmd/5l/l.h +++ b/src/cmd/5l/l.h @@ -143,6 +143,7 @@ struct Sym int32 value; int32 sig; int32 size; + int32 align; // if non-zero, required alignment in bytes uchar special; uchar fnptr; // used as fn ptr Sym* hash; // in hash table diff --git a/src/cmd/5l/obj.c b/src/cmd/5l/obj.c index fc5806aac..149671943 100644 --- a/src/cmd/5l/obj.c +++ b/src/cmd/5l/obj.c @@ -136,6 +136,11 @@ main(int argc, char *argv[]) case 'V': print("%cl version %s\n", thechar, getgoversion()); errorexit(); + case 'X': + // TODO: golang.org/issue/2676 + EARGF(usage()); + EARGF(usage()); + break; } ARGEND USED(argc); @@ -585,6 +590,10 @@ loop: errorexit(); } cursym = s; + if(s->type != 0 && s->type != SXREF && (p->reg & DUPOK)) { + skip = 1; + goto casedef; + } if(ntext++ == 0 && s->type != 0 && s->type != SXREF) { /* redefinition, so file has probably been seen before */ if(debug['v']) @@ -592,13 +601,8 @@ loop: return; } skip = 0; - if(s->type != 0 && s->type != SXREF) { - if(p->reg & DUPOK) { - skip = 1; - goto casedef; - } + if(s->type != 0 && s->type != SXREF) diag("redefinition: %s\n%P", s->name, p); - } if(etextp) etextp->next = s; else diff --git a/src/cmd/6a/a.y b/src/cmd/6a/a.y index c0fa4106e..8459ff323 100644 --- a/src/cmd/6a/a.y +++ b/src/cmd/6a/a.y @@ -429,6 +429,12 @@ imm: $$.type = D_FCONST; $$.dval = $3; } +| '$' '(' '-' LFCONST ')' + { + $$ = nullgen; + $$.type = D_FCONST; + $$.dval = -$4; + } | '$' '-' LFCONST { $$ = nullgen; diff --git a/src/cmd/6c/cgen.c b/src/cmd/6c/cgen.c index 7f717dcbb..71822586c 100644 --- a/src/cmd/6c/cgen.c +++ b/src/cmd/6c/cgen.c @@ -1237,11 +1237,12 @@ void boolgen(Node *n, int true, Node *nn) { int o; - Prog *p1, *p2; + Prog *p1, *p2, *p3; Node *l, *r, nod, nod1; int32 curs; if(debug['g']) { + print("boolgen %d\n", true); prtree(nn, "boolgen lhs"); prtree(n, "boolgen"); } @@ -1353,6 +1354,15 @@ boolgen(Node *n, int true, Node *nn) case OLO: case OLS: o = n->op; + if(true && typefd[l->type->etype] && (o == OEQ || o == ONE)) { + // Cannot rewrite !(l == r) into l != r with float64; it breaks NaNs. + // Jump around instead. + boolgen(n, 0, Z); + p1 = p; + gbranch(OGOTO); + patch(p1, pc); + goto com; + } if(true) o = comrel[relindex(o)]; if(l->complex >= FNX && r->complex >= FNX) { @@ -1367,6 +1377,10 @@ boolgen(Node *n, int true, Node *nn) break; } if(immconst(l)) { + // NOTE: Reversing the comparison here is wrong + // for floating point ordering comparisons involving NaN, + // but we don't have any of those yet so we don't + // bother worrying about it. o = invrel[relindex(o)]; /* bad, 13 is address of external that becomes constant */ if(r->addable < INDEXED || r->addable == 13) { @@ -1388,10 +1402,11 @@ boolgen(Node *n, int true, Node *nn) cgen(r, &nod1); gopcode(o, l->type, &nod, &nod1); regfree(&nod1); - } else + } else { gopcode(o, l->type, &nod, r); + } regfree(&nod); - goto com; + goto fixfloat; } regalloc(&nod, r, nn); cgen(r, &nod); @@ -1406,6 +1421,33 @@ boolgen(Node *n, int true, Node *nn) } else gopcode(o, l->type, l, &nod); regfree(&nod); + fixfloat: + if(typefd[l->type->etype]) { + switch(o) { + case OEQ: + // Already emitted AJEQ; want AJEQ and AJPC. + p1 = p; + gbranch(OGOTO); + p2 = p; + patch(p1, pc); + gins(AJPC, Z, Z); + patch(p2, pc); + break; + + case ONE: + // Already emitted AJNE; want AJNE or AJPS. + p1 = p; + gins(AJPS, Z, Z); + p2 = p; + gbranch(OGOTO); + p3 = p; + patch(p1, pc); + patch(p2, pc); + gbranch(OGOTO); + patch(p3, pc); + break; + } + } com: if(nn != Z) { diff --git a/src/cmd/6c/gc.h b/src/cmd/6c/gc.h index 0c23b115c..b0081abb5 100644 --- a/src/cmd/6c/gc.h +++ b/src/cmd/6c/gc.h @@ -299,7 +299,8 @@ void gpseudo(int, Sym*, Node*); int swcmp(const void*, const void*); void doswit(Node*); void swit1(C1*, int, int32, Node*); -void cas(void); +void swit2(C1*, int, int32, Node*); +void newcase(void); void bitload(Node*, Node*, Node*, Node*, Node*); void bitstore(Node*, Node*, Node*, Node*, Node*); int32 outstring(char*, int32); diff --git a/src/cmd/6c/swt.c b/src/cmd/6c/swt.c index 3de86306d..f16d0f78a 100644 --- a/src/cmd/6c/swt.c +++ b/src/cmd/6c/swt.c @@ -33,6 +33,21 @@ void swit1(C1 *q, int nc, int32 def, Node *n) { + Node nreg; + + regalloc(&nreg, n, Z); + if(typev[n->type->etype]) + nreg.type = types[TVLONG]; + else + nreg.type = types[TLONG]; + cgen(n, &nreg); + swit2(q, nc, def, &nreg); + regfree(&nreg); +} + +void +swit2(C1 *q, int nc, int32 def, Node *n) +{ C1 *r; int i; Prog *sp; @@ -58,12 +73,12 @@ swit1(C1 *q, int nc, int32 def, Node *n) gbranch(OGOTO); p->as = AJEQ; patch(p, r->label); - swit1(q, i, def, n); + swit2(q, i, def, n); if(debug['W']) print("case < %.8llux\n", r->val); patch(sp, pc); - swit1(r+1, nc-i-1, def, n); + swit2(r+1, nc-i-1, def, n); } void diff --git a/src/cmd/6g/cgen.c b/src/cmd/6g/cgen.c index 43bec0059..fd8493231 100644 --- a/src/cmd/6g/cgen.c +++ b/src/cmd/6g/cgen.c @@ -717,6 +717,7 @@ bgen(Node *n, int true, Prog *to) int et, a; Node *nl, *nr, *l, *r; Node n1, n2, tmp; + NodeList *ll; Prog *p1, *p2; if(debug['g']) { @@ -834,7 +835,10 @@ bgen(Node *n, int true, Prog *to) p1 = gbranch(AJMP, T); p2 = gbranch(AJMP, T); patch(p1, pc); + ll = n->ninit; // avoid re-genning ninit + n->ninit = nil; bgen(n, 1, p2); + n->ninit = ll; patch(gbranch(AJMP, T), to); patch(p2, pc); goto ret; @@ -1019,13 +1023,13 @@ stkof(Node *n) * memmove(&ns, &n, w); */ void -sgen(Node *n, Node *ns, int32 w) +sgen(Node *n, Node *ns, int64 w) { Node nodl, nodr, oldl, oldr, cx, oldcx, tmp; int32 c, q, odst, osrc; if(debug['g']) { - print("\nsgen w=%d\n", w); + print("\nsgen w=%lld\n", w); dump("r", n); dump("res", ns); } @@ -1034,7 +1038,7 @@ sgen(Node *n, Node *ns, int32 w) fatal("sgen UINF"); if(w < 0) - fatal("sgen copy %d", w); + fatal("sgen copy %lld", w); if(w == 16) if(componentgen(n, ns)) diff --git a/src/cmd/6g/gg.h b/src/cmd/6g/gg.h index 8a80ee9fb..47a540082 100644 --- a/src/cmd/6g/gg.h +++ b/src/cmd/6g/gg.h @@ -41,6 +41,8 @@ struct Prog void* reg; // pointer to containing Reg struct }; +#define TEXTFLAG from.scale + EXTERN int32 dynloc; EXTERN uchar reg[D_NONE]; EXTERN int32 pcloc; // instruction counter @@ -85,7 +87,7 @@ void agen(Node*, Node*); void igen(Node*, Node*, Node*); vlong fieldoffset(Type*, Node*); void bgen(Node*, int, Prog*); -void sgen(Node*, Node*, int32); +void sgen(Node*, Node*, int64); void gmove(Node*, Node*); Prog* gins(int, Node*, Node*); int samaddr(Node*, Node*); diff --git a/src/cmd/6g/gobj.c b/src/cmd/6g/gobj.c index 4dcce39c8..80de2f750 100644 --- a/src/cmd/6g/gobj.c +++ b/src/cmd/6g/gobj.c @@ -310,6 +310,7 @@ datastring(char *s, int len, Addr *a) sym = stringsym(s, len); a->type = D_EXTERN; a->sym = sym; + a->node = sym->def; a->offset = widthptr+4; // skip header a->etype = TINT32; } @@ -326,6 +327,7 @@ datagostring(Strlit *sval, Addr *a) sym = stringsym(sval->s, sval->len); a->type = D_EXTERN; a->sym = sym; + a->node = sym->def; a->offset = 0; // header a->etype = TINT32; } @@ -504,7 +506,7 @@ genembedtramp(Type *rcvr, Type *method, Sym *newnam, int iface) USED(iface); - if(debug['r']) + if(0 && debug['r']) print("genembedtramp %T %T %S\n", rcvr, method, newnam); e = method->sym; diff --git a/src/cmd/6g/gsubr.c b/src/cmd/6g/gsubr.c index 92b15ef00..cf00c3c49 100644 --- a/src/cmd/6g/gsubr.c +++ b/src/cmd/6g/gsubr.c @@ -481,12 +481,20 @@ nodarg(Type *t, int fp) n = nod(ONAME, N, N); n->type = t->type; n->sym = t->sym; + if(t->width == BADWIDTH) fatal("nodarg: offset not computed for %T", t); n->xoffset = t->width; n->addable = 1; + n->orig = t->nname; fp: + // Rewrite argument named _ to __, + // or else the assignment to _ will be + // discarded during code generation. + if(isblank(n)) + n->sym = lookup("__"); + switch(fp) { case 0: // output arg n->op = OINDREG; @@ -1119,6 +1127,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->offset = n->xoffset; a->sym = n->left->sym; a->type = D_PARAM; + a->node = n->left->orig; break; case ONAME: @@ -1131,6 +1140,9 @@ naddr(Node *n, Addr *a, int canemitcode) } a->offset = n->xoffset; a->sym = n->sym; + a->node = n->orig; + //if(a->node >= (Node*)&n) + // fatal("stack node"); if(a->sym == S) a->sym = lookup(".noname"); if(n->method) { @@ -1148,8 +1160,6 @@ naddr(Node *n, Addr *a, int canemitcode) break; case PAUTO: a->type = D_AUTO; - if (n->sym) - a->node = n->orig; break; case PPARAM: case PPARAMOUT: @@ -1172,6 +1182,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->dval = mpgetflt(n->val.u.fval); break; case CTINT: + case CTRUNE: a->sym = S; a->type = D_CONST; a->offset = mpgetfix(n->val.u.xval); @@ -1875,7 +1886,7 @@ sudoaddable(int as, Node *n, Addr *a) switch(n->op) { case OLITERAL: - if(n->val.ctype != CTINT) + if(!isconst(n, CTINT)) break; v = mpgetfix(n->val.u.xval); if(v >= 32000 || v <= -32000) diff --git a/src/cmd/6g/reg.c b/src/cmd/6g/reg.c index f380ced8c..82a2ce312 100644 --- a/src/cmd/6g/reg.c +++ b/src/cmd/6g/reg.c @@ -89,8 +89,8 @@ setoutvar(void) ovar.b[z] |= bit.b[z]; t = structnext(&save); } -//if(bany(b)) -//print("ovars = %Q\n", &ovar); +//if(bany(&ovar)) +//print("ovars = %Q\n", ovar); } static void @@ -151,6 +151,8 @@ static char* regname[] = { ".X15", }; +static void fixjmp(Prog*); + void regopt(Prog *firstp) { @@ -166,6 +168,8 @@ regopt(Prog *firstp) first = 0; } + fixjmp(firstp); + // count instructions nr = 0; for(p=firstp; p!=P; p=p->link) @@ -800,9 +804,9 @@ brk: if(ostats.ndelmov) print(" %4d delmov\n", ostats.ndelmov); if(ostats.nvar) - print(" %4d delmov\n", ostats.nvar); + print(" %4d var\n", ostats.nvar); if(ostats.naddr) - print(" %4d delmov\n", ostats.naddr); + print(" %4d addr\n", ostats.naddr); memset(&ostats, 0, sizeof(ostats)); } @@ -968,11 +972,14 @@ mkvar(Reg *r, Adr *a) n = t; break; } + node = a->node; - if(node == N || node->op != ONAME || node->orig != N) + if(node == N || node->op != ONAME || node->orig == N) goto none; node = node->orig; - if(node->sym->name[0] == '.') + if(node->orig != node) + fatal("%D: bad node", a); + if(node->sym == S || node->sym->name[0] == '.') goto none; et = a->etype; o = a->offset; @@ -1214,10 +1221,12 @@ loopit(Reg *r, int32 nr) r1 = rpo2r[i]; me = r1->rpo; d = -1; - if(r1->p1 != R && r1->p1->rpo < me) + // rpo2r[r->rpo] == r protects against considering dead code, + // which has r->rpo == 0. + if(r1->p1 != R && rpo2r[r1->p1->rpo] == r1->p1 && r1->p1->rpo < me) d = r1->p1->rpo; for(r1 = r1->p2; r1 != nil; r1 = r1->p2link) - if(r1->rpo < me) + if(rpo2r[r1->rpo] == r1 && r1->rpo < me) d = rpolca(idom, d, r1->rpo); idom[i] = d; } @@ -1622,7 +1631,7 @@ dumpone(Reg *r) if(bany(&r->refahead)) print(" ra:%Q ", r->refahead); if(bany(&r->calbehind)) - print("cb:%Q ", r->calbehind); + print(" cb:%Q ", r->calbehind); if(bany(&r->calahead)) print(" ca:%Q ", r->calahead); if(bany(&r->regdiff)) @@ -1682,3 +1691,123 @@ noreturn(Prog *p) return 1; return 0; } + +/* + * the code generator depends on being able to write out JMP + * instructions that it can jump to now but fill in later. + * the linker will resolve them nicely, but they make the code + * longer and more difficult to follow during debugging. + * remove them. + */ + +/* what instruction does a JMP to p eventually land on? */ +static Prog* +chasejmp(Prog *p, int *jmploop) +{ + int n; + + n = 0; + while(p != P && p->as == AJMP && p->to.type == D_BRANCH) { + if(++n > 10) { + *jmploop = 1; + break; + } + p = p->to.branch; + } + return p; +} + +/* + * reuse reg pointer for mark/sweep state. + * leave reg==nil at end because alive==nil. + */ +#define alive ((void*)0) +#define dead ((void*)1) + +/* mark all code reachable from firstp as alive */ +static void +mark(Prog *firstp) +{ + Prog *p; + + for(p=firstp; p; p=p->link) { + if(p->reg != dead) + break; + p->reg = alive; + if(p->as != ACALL && p->to.type == D_BRANCH && p->to.branch) + mark(p->to.branch); + if(p->as == AJMP || p->as == ARET || (p->as == ACALL && noreturn(p))) + break; + } +} + +static void +fixjmp(Prog *firstp) +{ + int jmploop; + Prog *p, *last; + + if(debug['R'] && debug['v']) + print("\nfixjmp\n"); + + // pass 1: resolve jump to AJMP, mark all code as dead. + jmploop = 0; + for(p=firstp; p; p=p->link) { + if(debug['R'] && debug['v']) + print("%P\n", p); + if(p->as != ACALL && p->to.type == D_BRANCH && p->to.branch && p->to.branch->as == AJMP) { + p->to.branch = chasejmp(p->to.branch, &jmploop); + if(debug['R'] && debug['v']) + print("->%P\n", p); + } + p->reg = dead; + } + if(debug['R'] && debug['v']) + print("\n"); + + // pass 2: mark all reachable code alive + mark(firstp); + + // pass 3: delete dead code (mostly JMPs). + last = nil; + for(p=firstp; p; p=p->link) { + if(p->reg == dead) { + if(p->link == P && p->as == ARET && last && last->as != ARET) { + // This is the final ARET, and the code so far doesn't have one. + // Let it stay. + } else { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + } + if(last) + last->link = p; + last = p; + } + last->link = P; + + // pass 4: elide JMP to next instruction. + // only safe if there are no jumps to JMPs anymore. + if(!jmploop) { + last = nil; + for(p=firstp; p; p=p->link) { + if(p->as == AJMP && p->to.type == D_BRANCH && p->to.branch == p->link) { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + if(last) + last->link = p; + last = p; + } + last->link = P; + } + + if(debug['R'] && debug['v']) { + print("\n"); + for(p=firstp; p; p=p->link) + print("%P\n", p); + print("\n"); + } +} diff --git a/src/cmd/6l/asm.c b/src/cmd/6l/asm.c index 3a8223e65..7d36b170e 100644 --- a/src/cmd/6l/asm.c +++ b/src/cmd/6l/asm.c @@ -37,13 +37,12 @@ #include "../ld/macho.h" #include "../ld/pe.h" -#define Dbufslop 100 - #define PADDR(a) ((uint32)(a) & ~0x80000000) char linuxdynld[] = "/lib64/ld-linux-x86-64.so.2"; char freebsddynld[] = "/libexec/ld-elf.so.1"; char openbsddynld[] = "/usr/libexec/ld.so"; +char netbsddynld[] = "/libexec/ld.elf_so"; char zeroes[32]; @@ -95,6 +94,7 @@ enum { ElfStrPlt, ElfStrGnuVersion, ElfStrGnuVersionR, + ElfStrNoteNetbsdIdent, NElfStr }; @@ -558,7 +558,7 @@ doelf(void) { Sym *s, *shstrtab, *dynstr; - if(HEADTYPE != Hlinux && HEADTYPE != Hfreebsd && HEADTYPE != Hopenbsd) + if(HEADTYPE != Hlinux && HEADTYPE != Hfreebsd && HEADTYPE != Hopenbsd && HEADTYPE != Hnetbsd) return; /* predefine strings we need for section headers */ @@ -570,6 +570,8 @@ doelf(void) elfstr[ElfStrText] = addstring(shstrtab, ".text"); elfstr[ElfStrData] = addstring(shstrtab, ".data"); elfstr[ElfStrBss] = addstring(shstrtab, ".bss"); + if(HEADTYPE == Hnetbsd) + elfstr[ElfStrNoteNetbsdIdent] = addstring(shstrtab, ".note.netbsd.ident"); addstring(shstrtab, ".elfdata"); addstring(shstrtab, ".rodata"); addstring(shstrtab, ".gosymtab"); @@ -649,7 +651,7 @@ doelf(void) /* define dynamic elf table */ s = lookup(".dynamic", 0); s->reachable = 1; - s->type = SELFROSECT; + s->type = SELFSECT; // writable /* * .dynamic table @@ -670,6 +672,8 @@ doelf(void) elfwritedynentsymsize(s, DT_PLTRELSZ, lookup(".rela.plt", 0)); elfwritedynentsym(s, DT_JMPREL, lookup(".rela.plt", 0)); + elfwritedynent(s, DT_DEBUG, 0); + // Do not write DT_NULL. elfdynhash will finish it. } } @@ -701,7 +705,7 @@ asmb(void) { int32 magic; int a, dynsym; - vlong vl, startva, symo, machlink; + vlong vl, startva, symo, dwarfoff, machlink, resoff; ElfEhdr *eh; ElfPhdr *ph, *pph; ElfShdr *sh; @@ -736,8 +740,19 @@ asmb(void) datblk(segdata.vaddr, segdata.filelen); machlink = 0; - if(HEADTYPE == Hdarwin) + if(HEADTYPE == Hdarwin) { + if(debug['v']) + Bprint(&bso, "%5.2f dwarf\n", cputime()); + + dwarfoff = rnd(HEADR+segtext.len, INITRND) + rnd(segdata.filelen, INITRND); + cseek(dwarfoff); + + segdwarf.fileoff = cpos(); + dwarfemitdebugsections(); + segdwarf.filelen = cpos() - segdwarf.fileoff; + machlink = domacholink(); + } switch(HEADTYPE) { default: @@ -750,6 +765,7 @@ asmb(void) break; case Hlinux: case Hfreebsd: + case Hnetbsd: case Hopenbsd: debug['8'] = 1; /* 64-bit addresses */ /* index of elf text section; needed by asmelfsym, double-checked below */ @@ -760,6 +776,8 @@ asmb(void) if(elfverneed) elftextsh += 2; } + if(HEADTYPE == Hnetbsd) + elftextsh += 1; break; case Hwindows: break; @@ -785,6 +803,7 @@ asmb(void) break; case Hlinux: case Hfreebsd: + case Hnetbsd: case Hopenbsd: symo = rnd(HEADR+segtext.len, INITRND)+segdata.filelen; symo = rnd(symo, INITRND); @@ -809,7 +828,6 @@ asmb(void) dwarfemitdebugsections(); } break; - case Hdarwin: case Hwindows: if(debug['v']) Bprint(&bso, "%5.2f dwarf\n", cputime()); @@ -855,11 +873,13 @@ asmb(void) break; case Hlinux: case Hfreebsd: + case Hnetbsd: case Hopenbsd: /* elf amd-64 */ eh = getElfEhdr(); startva = INITTEXT - HEADR; + resoff = ELFRESERVE; /* This null SHdr must appear before all others */ newElfShdr(elfstr[ElfStrEmpty]); @@ -898,12 +918,15 @@ asmb(void) case Hfreebsd: interpreter = freebsddynld; break; + case Hnetbsd: + interpreter = netbsddynld; + break; case Hopenbsd: interpreter = openbsddynld; break; } } - elfinterp(sh, startva, interpreter); + resoff -= elfinterp(sh, startva, resoff, interpreter); ph = newElfPhdr(); ph->type = PT_INTERP; @@ -911,11 +934,24 @@ asmb(void) phsh(ph, sh); } + if(HEADTYPE == Hnetbsd) { + sh = newElfShdr(elfstr[ElfStrNoteNetbsdIdent]); + sh->type = SHT_NOTE; + sh->flags = SHF_ALLOC; + sh->addralign = 4; + resoff -= elfnetbsdsig(sh, startva, resoff); + + ph = newElfPhdr(); + ph->type = PT_NOTE; + ph->flags = PF_R; + phsh(ph, sh); + } + elfphload(&segtext); elfphload(&segdata); /* Dynamic linking sections */ - if (!debug['d']) { /* -d suppresses dynamic loader format */ + if(!debug['d']) { /* -d suppresses dynamic loader format */ /* S headers for dynamic linking */ sh = newElfShdr(elfstr[ElfStrGot]); sh->type = SHT_PROGBITS; @@ -1039,7 +1075,7 @@ asmb(void) for(sect=segdata.sect; sect!=nil; sect=sect->next) elfshbits(sect); - if (!debug['s']) { + if(!debug['s']) { sh = newElfShdr(elfstr[ElfStrSymtab]); sh->type = SHT_SYMTAB; sh->off = symo; @@ -1064,6 +1100,8 @@ asmb(void) eh->ident[EI_MAG3] = 'F'; if(HEADTYPE == Hfreebsd) eh->ident[EI_OSABI] = ELFOSABI_FREEBSD; + else if(HEADTYPE == Hnetbsd) + eh->ident[EI_OSABI] = ELFOSABI_NETBSD; else if(HEADTYPE == Hopenbsd) eh->ident[EI_OSABI] = ELFOSABI_OPENBSD; eh->ident[EI_CLASS] = ELFCLASS64; @@ -1083,8 +1121,10 @@ asmb(void) a += elfwritehdr(); a += elfwritephdrs(); a += elfwriteshdrs(); - cflush(); - if(a+elfwriteinterp() > ELFRESERVE) + a += elfwriteinterp(elfstr[ElfStrInterp]); + if(HEADTYPE == Hnetbsd) + a += elfwritenetbsdsig(elfstr[ElfStrNoteNetbsdIdent]); + if(a > ELFRESERVE) diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE); break; case Hwindows: diff --git a/src/cmd/6l/doc.go b/src/cmd/6l/doc.go index b8a6013d6..c18b0f29d 100644 --- a/src/cmd/6l/doc.go +++ b/src/cmd/6l/doc.go @@ -25,15 +25,14 @@ Options new in this version: Elide the dynamic linking header. With this option, the binary is statically linked and does not refer to dynld. Without this option (the default), the binary's contents are identical but it is loaded with dynld. --e - Emit an extra ELF-compatible symbol table useful with tools such as - nm, gdb, and oprofile. This option makes the binary file considerably larger. -Hdarwin Write Apple Mach-O binaries (default when $GOOS is darwin) -Hlinux Write Linux ELF binaries (default when $GOOS is linux) -Hfreebsd Write FreeBSD ELF binaries (default when $GOOS is freebsd) +-Hnetbsd + Write NetBSD ELF binaries (default when $GOOS is netbsd) -Hopenbsd Write OpenBSD ELF binaries (default when $GOOS is openbsd) -Hwindows diff --git a/src/cmd/6l/l.h b/src/cmd/6l/l.h index b291d5f3d..5f62239a1 100644 --- a/src/cmd/6l/l.h +++ b/src/cmd/6l/l.h @@ -133,6 +133,7 @@ struct Sym int32 sig; int32 plt; int32 got; + int32 align; // if non-zero, required alignment in bytes Sym* hash; // in hash table Sym* allsym; // in all symbol list Sym* next; // in text or data list @@ -163,7 +164,7 @@ struct Optab short as; uchar* ytab; uchar prefix; - uchar op[20]; + uchar op[22]; }; struct Movtab { diff --git a/src/cmd/6l/obj.c b/src/cmd/6l/obj.c index a7ef58db4..d258f05c9 100644 --- a/src/cmd/6l/obj.c +++ b/src/cmd/6l/obj.c @@ -44,16 +44,17 @@ char* thestring = "amd64"; char* paramspace = "FP"; Header headers[] = { - "plan9x32", Hplan9x32, - "plan9", Hplan9x64, - "elf", Helf, - "darwin", Hdarwin, - "linux", Hlinux, - "freebsd", Hfreebsd, - "openbsd", Hopenbsd, - "windows", Hwindows, - "windowsgui", Hwindows, - 0, 0 + "plan9x32", Hplan9x32, + "plan9", Hplan9x64, + "elf", Helf, + "darwin", Hdarwin, + "linux", Hlinux, + "freebsd", Hfreebsd, + "netbsd", Hnetbsd, + "openbsd", Hopenbsd, + "windows", Hwindows, + "windowsgui", Hwindows, + 0, 0 }; /* @@ -63,6 +64,7 @@ Header headers[] = { * -Hdarwin -Tx -Rx is apple MH-exec * -Hlinux -Tx -Rx is linux elf-exec * -Hfreebsd -Tx -Rx is FreeBSD elf-exec + * -Hnetbsd -Tx -Rx is NetBSD elf-exec * -Hopenbsd -Tx -Rx is OpenBSD elf-exec * -Hwindows -Tx -Rx is MS Windows PE32+ * @@ -130,6 +132,11 @@ main(int argc, char *argv[]) case 'V': print("%cl version %s\n", thechar, getgoversion()); errorexit(); + case 'X': + // TODO: golang.org/issue/2676 + EARGF(usage()); + EARGF(usage()); + break; } ARGEND if(argc != 1) @@ -183,7 +190,7 @@ main(int argc, char *argv[]) case Hdarwin: /* apple MACH */ /* * OS X system constant - offset from 0(GS) to our TLS. - * Explained in ../../libcgo/darwin_amd64.c. + * Explained in ../../pkg/runtime/cgo/gcc_darwin_amd64.c. */ tlsoffset = 0x8a0; machoinit(); @@ -197,12 +204,13 @@ main(int argc, char *argv[]) break; case Hlinux: /* elf64 executable */ case Hfreebsd: /* freebsd */ + case Hnetbsd: /* netbsd */ case Hopenbsd: /* openbsd */ /* * ELF uses TLS offset negative from FS. * Translate 0(FS) and 8(FS) into -16(FS) and -8(FS). - * Also known to ../../pkg/runtime/linux/amd64/sys.s - * and ../../libcgo/linux_amd64.s. + * Also known to ../../pkg/runtime/sys_linux_amd64.s + * and ../../pkg/runtime/cgo/gcc_linux_amd64.c. */ tlsoffset = -16; elfinit(); @@ -462,7 +470,7 @@ loop: sig = 1729; if(sig != 0){ if(s->sig != 0 && s->sig != sig) - diag("incompatible type signatures" + diag("incompatible type signatures " "%ux(%s) and %ux(%s) for %s", s->sig, s->file, sig, pn, s->name); s->sig = sig; @@ -589,6 +597,10 @@ loop: case ATEXT: s = p->from.sym; if(s->text != nil) { + if(p->from.scale & DUPOK) { + skip = 1; + goto casdef; + } diag("%s: %s: redefinition", pn, s->name); return; } diff --git a/src/cmd/6l/optab.c b/src/cmd/6l/optab.c index 0a4c0eb07..2308e0dfe 100644 --- a/src/cmd/6l/optab.c +++ b/src/cmd/6l/optab.c @@ -200,7 +200,8 @@ uchar ymovq[] = Ymm, Ymr, Zm_r_xm, 1, // MMX MOVD Ymr, Ymm, Zr_m_xm, 1, // MMX MOVD Yxr, Ymr, Zm_r_xm_nr, 2, // MOVDQ2Q - Yxr, Ym, Zr_m_xm_nr, 2, // MOVQ xmm store + Yxm, Yxr, Zm_r_xm_nr, 2, // MOVQ xmm1/m64 -> xmm2 + Yxr, Yxm, Zr_m_xm_nr, 2, // MOVQ xmm1 -> xmm2/m64 Yml, Yxr, Zm_r_xm, 2, // MOVD xmm load Yxr, Yml, Zr_m_xm, 2, // MOVD xmm store Yiauto, Yrl, Zaut_r, 2, // built-in LEAQ @@ -862,7 +863,7 @@ Optab optab[] = { AMOVNTPD, yxr_ml, Pe, 0x2b }, { AMOVNTPS, yxr_ml, Pm, 0x2b }, { AMOVNTQ, ymr_ml, Pm, 0xe7 }, - { AMOVQ, ymovq, Pw, 0x89,0x8b,0x31,0xc7,(00),0xb8,0xc7,(00),0x6f,0x7f,0x6e,0x7e,Pf2,0xd6,Pe,0xd6,Pe,0x6e,Pe,0x7e }, + { AMOVQ, ymovq, Pw, 0x89, 0x8b, 0x31, 0xc7,(00), 0xb8, 0xc7,(00), 0x6f, 0x7f, 0x6e, 0x7e, Pf2,0xd6, Pf3,0x7e, Pe,0xd6, Pe,0x6e, Pe,0x7e }, { AMOVQOZX, ymrxr, Pf3, 0xd6,0x7e }, { AMOVSB, ynone, Pb, 0xa4 }, { AMOVSD, yxmov, Pf2, 0x10,0x11 }, @@ -978,7 +979,7 @@ Optab optab[] = { APSHUFW, ymshuf, Pm, 0x70 }, { APSLLO, ypsdq, Pq, 0x73,(07) }, { APSLLL, yps, Py, 0xf2, 0x72,(06), Pe,0xf2, Pe,0x72,(06) }, - { APSLLQ, yps, Py, 0xf3, 0x73,(06), Pe,0xf3, Pe,0x7e,(06) }, + { APSLLQ, yps, Py, 0xf3, 0x73,(06), Pe,0xf3, Pe,0x73,(06) }, { APSLLW, yps, Py, 0xf1, 0x71,(06), Pe,0xf1, Pe,0x71,(06) }, { APSRAL, yps, Py, 0xe2, 0x72,(04), Pe,0xe2, Pe,0x72,(04) }, { APSRAW, yps, Py, 0xe1, 0x71,(04), Pe,0xe1, Pe,0x71,(04) }, diff --git a/src/cmd/6l/pass.c b/src/cmd/6l/pass.c index d9e0b2fc1..2357a7f77 100644 --- a/src/cmd/6l/pass.c +++ b/src/cmd/6l/pass.c @@ -276,7 +276,7 @@ patch(void) // Convert // op n(GS), reg // to - // MOVL 0x58(GS), reg + // MOVL 0x28(GS), reg // op n(reg), reg // The purpose of this patch is to fix some accesses // to extern register variables (TLS) on Windows, as @@ -291,11 +291,11 @@ patch(void) q->as = p->as; p->as = AMOVQ; p->from.type = D_INDIR+D_GS; - p->from.offset = 0x58; + p->from.offset = 0x28; } } if(HEADTYPE == Hlinux || HEADTYPE == Hfreebsd - || HEADTYPE == Hopenbsd) { + || HEADTYPE == Hopenbsd || HEADTYPE == Hnetbsd) { // ELF uses FS instead of GS. if(p->from.type == D_INDIR+D_GS) p->from.type = D_INDIR+D_FS; @@ -421,18 +421,18 @@ dostkoff(void) p = appendp(p); // load g into CX p->as = AMOVQ; if(HEADTYPE == Hlinux || HEADTYPE == Hfreebsd - || HEADTYPE == Hopenbsd) // ELF uses FS + || HEADTYPE == Hopenbsd || HEADTYPE == Hnetbsd) // ELF uses FS p->from.type = D_INDIR+D_FS; else p->from.type = D_INDIR+D_GS; p->from.offset = tlsoffset+0; p->to.type = D_CX; if(HEADTYPE == Hwindows) { - // movq %gs:0x58, %rcx + // movq %gs:0x28, %rcx // movq (%rcx), %rcx p->as = AMOVQ; p->from.type = D_INDIR+D_GS; - p->from.offset = 0x58; + p->from.offset = 0x28; p->to.type = D_CX; diff --git a/src/cmd/6l/span.c b/src/cmd/6l/span.c index 9b869a493..28eb38f40 100644 --- a/src/cmd/6l/span.c +++ b/src/cmd/6l/span.c @@ -266,10 +266,6 @@ instinit(void) ycover[Ym*Ymax + Ymm] = 1; ycover[Ymr*Ymax + Ymm] = 1; - ycover[Yax*Ymax + Yxm] = 1; - ycover[Ycx*Ymax + Yxm] = 1; - ycover[Yrx*Ymax + Yxm] = 1; - ycover[Yrl*Ymax + Yxm] = 1; ycover[Ym*Ymax + Yxm] = 1; ycover[Yxr*Ymax + Yxm] = 1; diff --git a/src/cmd/8a/a.y b/src/cmd/8a/a.y index 96976089d..f1881808f 100644 --- a/src/cmd/8a/a.y +++ b/src/cmd/8a/a.y @@ -392,6 +392,12 @@ imm: $$.type = D_FCONST; $$.dval = $3; } +| '$' '(' '-' LFCONST ')' + { + $$ = nullgen; + $$.type = D_FCONST; + $$.dval = -$4; + } | '$' '-' LFCONST { $$ = nullgen; diff --git a/src/cmd/8a/lex.c b/src/cmd/8a/lex.c index ca2e2c138..403669404 100644 --- a/src/cmd/8a/lex.c +++ b/src/cmd/8a/lex.c @@ -313,8 +313,8 @@ struct "IDIVL", LTYPE2, AIDIVL, "IDIVW", LTYPE2, AIDIVW, "IMULB", LTYPE2, AIMULB, - "IMULL", LTYPE2, AIMULL, - "IMULW", LTYPE2, AIMULW, + "IMULL", LTYPEI, AIMULL, + "IMULW", LTYPEI, AIMULW, "INB", LTYPE0, AINB, "INL", LTYPE0, AINL, "INW", LTYPE0, AINW, diff --git a/src/cmd/8c/cgen.c b/src/cmd/8c/cgen.c index 7f02bd96e..869d31ace 100644 --- a/src/cmd/8c/cgen.c +++ b/src/cmd/8c/cgen.c @@ -1221,7 +1221,7 @@ void boolgen(Node *n, int true, Node *nn) { int o; - Prog *p1, *p2; + Prog *p1, *p2, *p3; Node *l, *r, nod, nod1; int32 curs; @@ -1346,6 +1346,15 @@ boolgen(Node *n, int true, Node *nn) cgen64(n, Z); goto com; } + if(true && typefd[l->type->etype] && (o == OEQ || o == ONE)) { + // Cannot rewrite !(l == r) into l != r with float64; it breaks NaNs. + // Jump around instead. + boolgen(n, 0, Z); + p1 = p; + gbranch(OGOTO); + patch(p1, pc); + goto com; + } if(true) o = comrel[relindex(o)]; if(l->complex >= FNX && r->complex >= FNX) { @@ -1378,6 +1387,30 @@ boolgen(Node *n, int true, Node *nn) } else fgopcode(o, l, &fregnode0, 0, 1); } + switch(o) { + case OEQ: + // Already emitted AJEQ; want AJEQ and AJPC. + p1 = p; + gbranch(OGOTO); + p2 = p; + patch(p1, pc); + gins(AJPC, Z, Z); + patch(p2, pc); + break; + + case ONE: + // Already emitted AJNE; want AJNE or AJPS. + p1 = p; + gins(AJPS, Z, Z); + p2 = p; + gbranch(OGOTO); + p3 = p; + patch(p1, pc); + patch(p2, pc); + gbranch(OGOTO); + patch(p3, pc); + break; + } goto com; } if(l->op == OCONST) { diff --git a/src/cmd/8c/gc.h b/src/cmd/8c/gc.h index 32b80e995..4a57f5d3c 100644 --- a/src/cmd/8c/gc.h +++ b/src/cmd/8c/gc.h @@ -304,7 +304,8 @@ void gpseudo(int, Sym*, Node*); int swcmp(const void*, const void*); void doswit(Node*); void swit1(C1*, int, int32, Node*); -void cas(void); +void swit2(C1*, int, int32, Node*); +void newcase(void); void bitload(Node*, Node*, Node*, Node*, Node*); void bitstore(Node*, Node*, Node*, Node*, Node*); int32 outstring(char*, int32); diff --git a/src/cmd/8c/swt.c b/src/cmd/8c/swt.c index 006bfdfe2..f1ca4c25f 100644 --- a/src/cmd/8c/swt.c +++ b/src/cmd/8c/swt.c @@ -33,6 +33,26 @@ void swit1(C1 *q, int nc, int32 def, Node *n) { + Node nreg; + + if(typev[n->type->etype]) { + regsalloc(&nreg, n); + nreg.type = types[TVLONG]; + cgen(n, &nreg); + swit2(q, nc, def, &nreg); + return; + } + + regalloc(&nreg, n, Z); + nreg.type = types[TLONG]; + cgen(n, &nreg); + swit2(q, nc, def, &nreg); + regfree(&nreg); +} + +void +swit2(C1 *q, int nc, int32 def, Node *n) +{ C1 *r; int i; Prog *sp; @@ -58,12 +78,12 @@ swit1(C1 *q, int nc, int32 def, Node *n) gbranch(OGOTO); p->as = AJEQ; patch(p, r->label); - swit1(q, i, def, n); + swit2(q, i, def, n); if(debug['W']) print("case < %.8ux\n", r->val); patch(sp, pc); - swit1(r+1, nc-i-1, def, n); + swit2(r+1, nc-i-1, def, n); } void diff --git a/src/cmd/8g/cgen.c b/src/cmd/8g/cgen.c index 21b7815fd..7dd3a7bb1 100644 --- a/src/cmd/8g/cgen.c +++ b/src/cmd/8g/cgen.c @@ -787,6 +787,7 @@ bgen(Node *n, int true, Prog *to) int et, a; Node *nl, *nr, *r; Node n1, n2, tmp, t1, t2, ax; + NodeList *ll; Prog *p1, *p2; if(debug['g']) { @@ -902,7 +903,10 @@ bgen(Node *n, int true, Prog *to) p1 = gbranch(AJMP, T); p2 = gbranch(AJMP, T); patch(p1, pc); + ll = n->ninit; // avoid re-genning ninit + n->ninit = nil; bgen(n, 1, p2); + n->ninit = ll; patch(gbranch(AJMP, T), to); patch(p2, pc); break; @@ -1126,21 +1130,21 @@ stkof(Node *n) * memmove(&res, &n, w); */ void -sgen(Node *n, Node *res, int32 w) +sgen(Node *n, Node *res, int64 w) { Node dst, src, tdst, tsrc; int32 c, q, odst, osrc; if(debug['g']) { - print("\nsgen w=%d\n", w); + print("\nsgen w=%ld\n", w); dump("r", n); dump("res", res); } if(n->ullman >= UINF && res->ullman >= UINF) fatal("sgen UINF"); - if(w < 0) - fatal("sgen copy %d", w); + if(w < 0 || (int32)w != w) + fatal("sgen copy %lld", w); if(w == 0) { // evaluate side effects only. diff --git a/src/cmd/8g/gg.h b/src/cmd/8g/gg.h index e23ee9e27..0a4f0ad2d 100644 --- a/src/cmd/8g/gg.h +++ b/src/cmd/8g/gg.h @@ -43,6 +43,8 @@ struct Prog void* reg; // pointer to containing Reg struct }; +#define TEXTFLAG from.scale + // foptoas flags enum { @@ -97,7 +99,7 @@ void agenr(Node *n, Node *a, Node *res); void igen(Node*, Node*, Node*); vlong fieldoffset(Type*, Node*); void bgen(Node*, int, Prog*); -void sgen(Node*, Node*, int32); +void sgen(Node*, Node*, int64); void gmove(Node*, Node*); Prog* gins(int, Node*, Node*); int samaddr(Node*, Node*); diff --git a/src/cmd/8g/gobj.c b/src/cmd/8g/gobj.c index 7025a536e..d8c8f5ab9 100644 --- a/src/cmd/8g/gobj.c +++ b/src/cmd/8g/gobj.c @@ -308,6 +308,7 @@ datastring(char *s, int len, Addr *a) sym = stringsym(s, len); a->type = D_EXTERN; a->sym = sym; + a->node = sym->def; a->offset = widthptr+4; // skip header a->etype = TINT32; } @@ -324,6 +325,7 @@ datagostring(Strlit *sval, Addr *a) sym = stringsym(sval->s, sval->len); a->type = D_EXTERN; a->sym = sym; + a->node = sym->def; a->offset = 0; // header a->etype = TINT32; } diff --git a/src/cmd/8g/gsubr.c b/src/cmd/8g/gsubr.c index 1aae34e35..9d0f7025f 100644 --- a/src/cmd/8g/gsubr.c +++ b/src/cmd/8g/gsubr.c @@ -964,8 +964,15 @@ nodarg(Type *t, int fp) fatal("nodarg: offset not computed for %T", t); n->xoffset = t->width; n->addable = 1; + n->orig = t->nname; break; } + + // Rewrite argument named _ to __, + // or else the assignment to _ will be + // discarded during code generation. + if(isblank(n)) + n->sym = lookup("__"); switch(fp) { default: @@ -1152,6 +1159,7 @@ memname(Node *n, Type *t) strcpy(namebuf, n->sym->name); namebuf[0] = '.'; // keep optimizer from registerizing n->sym = lookup(namebuf); + n->orig->sym = n->sym; } void @@ -1828,6 +1836,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->offset = n->xoffset; a->sym = n->left->sym; a->type = D_PARAM; + a->node = n->left->orig; break; case ONAME: @@ -1840,6 +1849,9 @@ naddr(Node *n, Addr *a, int canemitcode) } a->offset = n->xoffset; a->sym = n->sym; + a->node = n->orig; + //if(a->node >= (Node*)&n) + // fatal("stack node"); if(a->sym == S) a->sym = lookup(".noname"); if(n->method) { @@ -1857,8 +1869,6 @@ naddr(Node *n, Addr *a, int canemitcode) break; case PAUTO: a->type = D_AUTO; - if (n->sym) - a->node = n->orig; break; case PPARAM: case PPARAMOUT: @@ -1881,6 +1891,7 @@ naddr(Node *n, Addr *a, int canemitcode) a->dval = mpgetflt(n->val.u.fval); break; case CTINT: + case CTRUNE: a->sym = S; a->type = D_CONST; a->offset = mpgetfix(n->val.u.xval); diff --git a/src/cmd/8g/reg.c b/src/cmd/8g/reg.c index de5fd87ac..227628226 100644 --- a/src/cmd/8g/reg.c +++ b/src/cmd/8g/reg.c @@ -39,6 +39,8 @@ static int first = 1; +static void fixjmp(Prog*); + Reg* rega(void) { @@ -89,8 +91,8 @@ setoutvar(void) ovar.b[z] |= bit.b[z]; t = structnext(&save); } -//if(bany(b)) -//print("ovars = %Q\n", &ovar); +//if(bany(ovar)) +//print("ovars = %Q\n", ovar); } static void @@ -132,6 +134,8 @@ regopt(Prog *firstp) exregoffset = D_DI; // no externals first = 0; } + + fixjmp(firstp); // count instructions nr = 0; @@ -694,9 +698,9 @@ brk: if(ostats.ndelmov) print(" %4d delmov\n", ostats.ndelmov); if(ostats.nvar) - print(" %4d delmov\n", ostats.nvar); + print(" %4d var\n", ostats.nvar); if(ostats.naddr) - print(" %4d delmov\n", ostats.naddr); + print(" %4d addr\n", ostats.naddr); memset(&ostats, 0, sizeof(ostats)); } @@ -848,10 +852,12 @@ mkvar(Reg *r, Adr *a) } node = a->node; - if(node == N || node->op != ONAME || node->orig != N) + if(node == N || node->op != ONAME || node->orig == N) goto none; node = node->orig; - if(node->sym->name[0] == '.') + if(node->orig != node) + fatal("%D: bad node", a); + if(node->sym == S || node->sym->name[0] == '.') goto none; et = a->etype; o = a->offset; @@ -1095,10 +1101,12 @@ loopit(Reg *r, int32 nr) r1 = rpo2r[i]; me = r1->rpo; d = -1; - if(r1->p1 != R && r1->p1->rpo < me) + // rpo2r[r->rpo] == r protects against considering dead code, + // which has r->rpo == 0. + if(r1->p1 != R && rpo2r[r1->p1->rpo] == r1->p1 && r1->p1->rpo < me) d = r1->p1->rpo; for(r1 = r1->p2; r1 != nil; r1 = r1->p2link) - if(r1->rpo < me) + if(rpo2r[r1->rpo] == r1 && r1->rpo < me) d = rpolca(idom, d, r1->rpo); idom[i] = d; } @@ -1482,7 +1490,7 @@ dumpone(Reg *r) if(bany(&r->refahead)) print(" ra:%Q ", r->refahead); if(bany(&r->calbehind)) - print("cb:%Q ", r->calbehind); + print(" cb:%Q ", r->calbehind); if(bany(&r->calahead)) print(" ca:%Q ", r->calahead); if(bany(&r->regdiff)) @@ -1542,3 +1550,123 @@ noreturn(Prog *p) return 1; return 0; } + +/* + * the code generator depends on being able to write out JMP + * instructions that it can jump to now but fill in later. + * the linker will resolve them nicely, but they make the code + * longer and more difficult to follow during debugging. + * remove them. + */ + +/* what instruction does a JMP to p eventually land on? */ +static Prog* +chasejmp(Prog *p, int *jmploop) +{ + int n; + + n = 0; + while(p != P && p->as == AJMP && p->to.type == D_BRANCH) { + if(++n > 10) { + *jmploop = 1; + break; + } + p = p->to.branch; + } + return p; +} + +/* + * reuse reg pointer for mark/sweep state. + * leave reg==nil at end because alive==nil. + */ +#define alive ((void*)0) +#define dead ((void*)1) + +/* mark all code reachable from firstp as alive */ +static void +mark(Prog *firstp) +{ + Prog *p; + + for(p=firstp; p; p=p->link) { + if(p->reg != dead) + break; + p->reg = alive; + if(p->as != ACALL && p->to.type == D_BRANCH && p->to.branch) + mark(p->to.branch); + if(p->as == AJMP || p->as == ARET || (p->as == ACALL && noreturn(p))) + break; + } +} + +static void +fixjmp(Prog *firstp) +{ + int jmploop; + Prog *p, *last; + + if(debug['R'] && debug['v']) + print("\nfixjmp\n"); + + // pass 1: resolve jump to AJMP, mark all code as dead. + jmploop = 0; + for(p=firstp; p; p=p->link) { + if(debug['R'] && debug['v']) + print("%P\n", p); + if(p->as != ACALL && p->to.type == D_BRANCH && p->to.branch && p->to.branch->as == AJMP) { + p->to.branch = chasejmp(p->to.branch, &jmploop); + if(debug['R'] && debug['v']) + print("->%P\n", p); + } + p->reg = dead; + } + if(debug['R'] && debug['v']) + print("\n"); + + // pass 2: mark all reachable code alive + mark(firstp); + + // pass 3: delete dead code (mostly JMPs). + last = nil; + for(p=firstp; p; p=p->link) { + if(p->reg == dead) { + if(p->link == P && p->as == ARET && last && last->as != ARET) { + // This is the final ARET, and the code so far doesn't have one. + // Let it stay. + } else { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + } + if(last) + last->link = p; + last = p; + } + last->link = P; + + // pass 4: elide JMP to next instruction. + // only safe if there are no jumps to JMPs anymore. + if(!jmploop) { + last = nil; + for(p=firstp; p; p=p->link) { + if(p->as == AJMP && p->to.type == D_BRANCH && p->to.branch == p->link) { + if(debug['R'] && debug['v']) + print("del %P\n", p); + continue; + } + if(last) + last->link = p; + last = p; + } + last->link = P; + } + + if(debug['R'] && debug['v']) { + print("\n"); + for(p=firstp; p; p=p->link) + print("%P\n", p); + print("\n"); + } +} diff --git a/src/cmd/8l/asm.c b/src/cmd/8l/asm.c index 6c7f96483..0fe4cf112 100644 --- a/src/cmd/8l/asm.c +++ b/src/cmd/8l/asm.c @@ -37,11 +37,10 @@ #include "../ld/macho.h" #include "../ld/pe.h" -#define Dbufslop 100 - char linuxdynld[] = "/lib/ld-linux.so.2"; char freebsddynld[] = "/usr/libexec/ld-elf.so.1"; char openbsddynld[] = "/usr/libexec/ld.so"; +char netbsddynld[] = "/usr/libexec/ld.elf_so"; int32 entryvalue(void) @@ -91,6 +90,7 @@ enum { ElfStrPlt, ElfStrGnuVersion, ElfStrGnuVersionR, + ElfStrNoteNetbsdIdent, NElfStr }; @@ -527,6 +527,8 @@ doelf(void) elfstr[ElfStrText] = addstring(shstrtab, ".text"); elfstr[ElfStrData] = addstring(shstrtab, ".data"); elfstr[ElfStrBss] = addstring(shstrtab, ".bss"); + if(HEADTYPE == Hnetbsd) + elfstr[ElfStrNoteNetbsdIdent] = addstring(shstrtab, ".note.netbsd.ident"); addstring(shstrtab, ".elfdata"); addstring(shstrtab, ".rodata"); addstring(shstrtab, ".gosymtab"); @@ -607,7 +609,7 @@ doelf(void) /* define dynamic elf table */ s = lookup(".dynamic", 0); s->reachable = 1; - s->type = SELFROSECT; + s->type = SELFSECT; // writable /* * .dynamic table @@ -627,6 +629,8 @@ doelf(void) elfwritedynentsymsize(s, DT_PLTRELSZ, lookup(".rel.plt", 0)); elfwritedynentsym(s, DT_JMPREL, lookup(".rel.plt", 0)); + elfwritedynent(s, DT_DEBUG, 0); + // Do not write DT_NULL. elfdynhash will finish it. } } @@ -658,7 +662,7 @@ asmb(void) { int32 v, magic; int a, dynsym; - uint32 symo, startva, machlink; + uint32 symo, startva, dwarfoff, machlink, resoff; ElfEhdr *eh; ElfPhdr *ph, *pph; ElfShdr *sh; @@ -689,8 +693,19 @@ asmb(void) datblk(segdata.vaddr, segdata.filelen); machlink = 0; - if(HEADTYPE == Hdarwin) + if(HEADTYPE == Hdarwin) { + if(debug['v']) + Bprint(&bso, "%5.2f dwarf\n", cputime()); + + dwarfoff = rnd(HEADR+segtext.len, INITRND) + rnd(segdata.filelen, INITRND); + cseek(dwarfoff); + + segdwarf.fileoff = cpos(); + dwarfemitdebugsections(); + segdwarf.filelen = cpos() - segdwarf.fileoff; + machlink = domacholink(); + } if(iself) { /* index of elf text section; needed by asmelfsym, double-checked below */ @@ -701,6 +716,8 @@ asmb(void) if(elfverneed) elftextsh += 2; } + if(HEADTYPE == Hnetbsd) + elftextsh += 1; } symsize = 0; @@ -747,7 +764,7 @@ asmb(void) default: if(iself) { if(debug['v']) - Bprint(&bso, "%5.2f elfsym\n", cputime()); + Bprint(&bso, "%5.2f elfsym\n", cputime()); asmelfsym(); cflush(); cwrite(elfstrdat, elfstrsize); @@ -770,7 +787,6 @@ asmb(void) cflush(); } break; - case Hdarwin: case Hwindows: if(debug['v']) Bprint(&bso, "%5.2f dwarf\n", cputime()); @@ -919,6 +935,7 @@ asmb(void) Elfput: eh = getElfEhdr(); startva = INITTEXT - HEADR; + resoff = ELFRESERVE; /* This null SHdr must appear before all others */ newElfShdr(elfstr[ElfStrEmpty]); @@ -957,12 +974,15 @@ asmb(void) case Hfreebsd: interpreter = freebsddynld; break; + case Hnetbsd: + interpreter = netbsddynld; + break; case Hopenbsd: interpreter = openbsddynld; break; } } - elfinterp(sh, startva, interpreter); + resoff -= elfinterp(sh, startva, resoff, interpreter); ph = newElfPhdr(); ph->type = PT_INTERP; @@ -970,11 +990,24 @@ asmb(void) phsh(ph, sh); } + if(HEADTYPE == Hnetbsd) { + sh = newElfShdr(elfstr[ElfStrNoteNetbsdIdent]); + sh->type = SHT_NOTE; + sh->flags = SHF_ALLOC; + sh->addralign = 4; + resoff -= elfnetbsdsig(sh, startva, resoff); + + ph = newElfPhdr(); + ph->type = PT_NOTE; + ph->flags = PF_R; + phsh(ph, sh); + } + elfphload(&segtext); elfphload(&segdata); /* Dynamic linking sections */ - if (!debug['d']) { /* -d suppresses dynamic loader format */ + if(!debug['d']) { /* -d suppresses dynamic loader format */ /* S headers for dynamic linking */ sh = newElfShdr(elfstr[ElfStrGot]); sh->type = SHT_PROGBITS; @@ -1098,7 +1131,7 @@ asmb(void) for(sect=segdata.sect; sect!=nil; sect=sect->next) elfshbits(sect); - if (!debug['s']) { + if(!debug['s']) { sh = newElfShdr(elfstr[ElfStrSymtab]); sh->type = SHT_SYMTAB; sh->off = symo; @@ -1128,6 +1161,9 @@ asmb(void) case Hfreebsd: eh->ident[EI_OSABI] = ELFOSABI_FREEBSD; break; + case Hnetbsd: + eh->ident[EI_OSABI] = ELFOSABI_NETBSD; + break; case Hopenbsd: eh->ident[EI_OSABI] = ELFOSABI_OPENBSD; break; @@ -1148,8 +1184,10 @@ asmb(void) a += elfwritehdr(); a += elfwritephdrs(); a += elfwriteshdrs(); - cflush(); - if(a+elfwriteinterp() > ELFRESERVE) + a += elfwriteinterp(elfstr[ElfStrInterp]); + if(HEADTYPE == Hnetbsd) + a += elfwritenetbsdsig(elfstr[ElfStrNoteNetbsdIdent]); + if(a > ELFRESERVE) diag("ELFRESERVE too small: %d > %d", a, ELFRESERVE); break; diff --git a/src/cmd/8l/doc.go b/src/cmd/8l/doc.go index de877bb24..edd683823 100644 --- a/src/cmd/8l/doc.go +++ b/src/cmd/8l/doc.go @@ -33,6 +33,8 @@ Options new in this version: Write Linux ELF binaries (default when $GOOS is linux) -Hfreebsd Write FreeBSD ELF binaries (default when $GOOS is freebsd) +-Hnetbsd + Write NetBSD ELF binaries (default when $GOOS is netbsd) -Hopenbsd Write OpenBSD ELF binaries (default when $GOOS is openbsd) -Hwindows diff --git a/src/cmd/8l/l.h b/src/cmd/8l/l.h index a721f384b..b974f464b 100644 --- a/src/cmd/8l/l.h +++ b/src/cmd/8l/l.h @@ -134,6 +134,7 @@ struct Sym int32 dynid; int32 plt; int32 got; + int32 align; // if non-zero, required alignment in bytes Sym* hash; // in hash table Sym* allsym; // in all symbol list Sym* next; // in text or data list diff --git a/src/cmd/8l/obj.c b/src/cmd/8l/obj.c index 297b5bed5..1d0f1ec0f 100644 --- a/src/cmd/8l/obj.c +++ b/src/cmd/8l/obj.c @@ -47,18 +47,19 @@ char *noname = "<none>"; char *thestring = "386"; Header headers[] = { - "garbunix", Hgarbunix, - "unixcoff", Hunixcoff, - "plan9", Hplan9x32, - "msdoscom", Hmsdoscom, - "msdosexe", Hmsdosexe, - "darwin", Hdarwin, - "linux", Hlinux, - "freebsd", Hfreebsd, - "openbsd", Hopenbsd, - "windows", Hwindows, - "windowsgui", Hwindows, - 0, 0 + "garbunix", Hgarbunix, + "unixcoff", Hunixcoff, + "plan9", Hplan9x32, + "msdoscom", Hmsdoscom, + "msdosexe", Hmsdosexe, + "darwin", Hdarwin, + "linux", Hlinux, + "freebsd", Hfreebsd, + "netbsd", Hnetbsd, + "openbsd", Hopenbsd, + "windows", Hwindows, + "windowsgui", Hwindows, + 0, 0 }; /* @@ -70,6 +71,7 @@ Header headers[] = { * -Hdarwin -Tx -Rx is Apple Mach-O * -Hlinux -Tx -Rx is Linux ELF32 * -Hfreebsd -Tx -Rx is FreeBSD ELF32 + * -Hnetbsd -Tx -Rx is NetBSD ELF32 * -Hopenbsd -Tx -Rx is OpenBSD ELF32 * -Hwindows -Tx -Rx is MS Windows PE32 */ @@ -135,6 +137,11 @@ main(int argc, char *argv[]) case 'V': print("%cl version %s\n", thechar, getgoversion()); errorexit(); + case 'X': + // TODO: golang.org/issue/2676 + EARGF(usage()); + EARGF(usage()); + break; } ARGEND if(argc != 1) @@ -211,7 +218,7 @@ main(int argc, char *argv[]) case Hdarwin: /* apple MACH */ /* * OS X system constant - offset from %gs to our TLS. - * Explained in ../../libcgo/darwin_386.c. + * Explained in ../../pkg/runtime/cgo/gcc_darwin_386.c. */ tlsoffset = 0x468; machoinit(); @@ -225,12 +232,13 @@ main(int argc, char *argv[]) break; case Hlinux: /* elf32 executable */ case Hfreebsd: + case Hnetbsd: case Hopenbsd: /* * ELF uses TLS offsets negative from %gs. * Translate 0(GS) and 4(GS) into -8(GS) and -4(GS). - * Also known to ../../pkg/runtime/linux/386/sys.s - * and ../../libcgo/linux_386.c. + * Also known to ../../pkg/runtime/sys_linux_386.s + * and ../../pkg/runtime/cgo/gcc_linux_386.c. */ tlsoffset = -8; elfinit(); @@ -480,7 +488,7 @@ loop: sig = 1729; if(sig != 0){ if(s->sig != 0 && s->sig != sig) - diag("incompatible type signatures" + diag("incompatible type signatures " "%ux(%s) and %ux(%s) for %s", s->sig, s->file, sig, pn, s->name); s->sig = sig; @@ -597,6 +605,10 @@ loop: case ATEXT: s = p->from.sym; if(s->text != nil) { + if(p->from.scale & DUPOK) { + skip = 1; + goto casdef; + } diag("%s: %s: redefinition", pn, s->name); return; } diff --git a/src/cmd/8l/pass.c b/src/cmd/8l/pass.c index 54ea965da..b900a5f79 100644 --- a/src/cmd/8l/pass.c +++ b/src/cmd/8l/pass.c @@ -259,7 +259,7 @@ patch(void) // Convert // op n(GS), reg // to - // MOVL 0x2C(FS), reg + // MOVL 0x14(FS), reg // op n(reg), reg // The purpose of this patch is to fix some accesses // to extern register variables (TLS) on Windows, as @@ -273,7 +273,7 @@ patch(void) q->as = p->as; p->as = AMOVL; p->from.type = D_INDIR+D_FS; - p->from.offset = 0x2C; + p->from.offset = 0x14; } } if(HEADTYPE == Hlinux) { @@ -424,7 +424,7 @@ dostkoff(void) case Hwindows: p->as = AMOVL; p->from.type = D_INDIR+D_FS; - p->from.offset = 0x2c; + p->from.offset = 0x14; p->to.type = D_CX; p = appendp(p); diff --git a/src/cmd/Makefile b/src/cmd/Makefile index 5a37733de..ee82b8311 100644 --- a/src/cmd/Makefile +++ b/src/cmd/Makefile @@ -16,9 +16,7 @@ DIRS=\ cc\ cov\ gc\ - godefs\ gopack\ - gotry\ nm\ prof\ @@ -39,18 +37,16 @@ CLEANDIRS=\ 8g\ 8l\ cgo\ - ebnflint\ godoc\ gofix\ gofmt\ goinstall\ gotest\ - gotype\ goyacc\ - hgpatch\ install: $(patsubst %,%.install,$(DIRS)) clean: $(patsubst %,%.clean,$(CLEANDIRS)) +nuke: $(patsubst %,%.nuke,$(CLEANDIRS)) %.install: @echo @@ -58,12 +54,15 @@ clean: $(patsubst %,%.clean,$(CLEANDIRS)) @echo $(MAKE) -C $* install -gc.install $(O)c.install: cc.install +gc.install 5c.install 6c.install 8c.install: cc.install $(O)g.install: gc.install $(O)a.install $(O)c.install $(O)g.install: $(O)l.install %.clean: $(MAKE) -C $* clean +%.nuke: + $(MAKE) -C $* nuke + echo-dirs: @echo $(DIRS) diff --git a/src/cmd/cc/godefs.c b/src/cmd/cc/godefs.c index 3ba979c8a..4274c5626 100644 --- a/src/cmd/cc/godefs.c +++ b/src/cmd/cc/godefs.c @@ -124,11 +124,11 @@ Uconv(Fmt *fp) if(s && *s) { if(upper) - str[0] = toupper(*s); + str[0] = toupper((uchar)*s); else - str[0] = tolower(*s); + str[0] = tolower((uchar)*s); for(i = 1; i < STRINGSZ && s[i] != 0; i++) - str[i] = tolower(s[i]); + str[i] = tolower((uchar)s[i]); str[i] = 0; } diff --git a/src/cmd/cc/pgen.c b/src/cmd/cc/pgen.c index 0e5e8c059..3a686102f 100644 --- a/src/cmd/cc/pgen.c +++ b/src/cmd/cc/pgen.c @@ -266,7 +266,7 @@ loop: if(cases == C) diag(n, "case/default outside a switch"); if(l == Z) { - cas(); + newcase(); cases->val = 0; cases->def = 1; cases->label = pc; @@ -278,7 +278,7 @@ loop: goto rloop; if(l->op == OCONST) if(typeword[l->type->etype] && l->type->etype != TIND) { - cas(); + newcase(); cases->val = l->vconst; cases->def = 0; cases->label = pc; @@ -293,7 +293,7 @@ loop: complex(l); if(l->type == T) break; - if(!typeword[l->type->etype] || l->type->etype == TIND) { + if(!typechlvp[l->type->etype] || l->type->etype == TIND) { diag(n, "switch expression must be integer"); break; } @@ -303,7 +303,7 @@ loop: cn = cases; cases = C; - cas(); + newcase(); sbc = breakpc; breakpc = pc; @@ -320,15 +320,7 @@ loop: } patch(sp, pc); - regalloc(&nod, l, Z); - /* always signed */ - if(typev[l->type->etype]) - nod.type = types[TVLONG]; - else - nod.type = types[TLONG]; - cgen(l, &nod); - doswit(&nod); - regfree(&nod); + doswit(l); patch(spb, pc); cases = cn; diff --git a/src/cmd/cc/pswt.c b/src/cmd/cc/pswt.c index 0e402dea7..b94035faa 100644 --- a/src/cmd/cc/pswt.c +++ b/src/cmd/cc/pswt.c @@ -92,7 +92,7 @@ doswit(Node *n) } void -cas(void) +newcase(void) { Case *c; diff --git a/src/cmd/cgo/Makefile b/src/cmd/cgo/Makefile index 5458c3e4f..a3f034f7c 100644 --- a/src/cmd/cgo/Makefile +++ b/src/cmd/cgo/Makefile @@ -8,6 +8,7 @@ TARG=cgo GOFILES=\ ast.go\ gcc.go\ + godefs.go\ main.go\ out.go\ util.go\ diff --git a/src/cmd/cgo/ast.go b/src/cmd/cgo/ast.go index 73b7313d6..da6ae4176 100644 --- a/src/cmd/cgo/ast.go +++ b/src/cmd/cgo/ast.go @@ -9,7 +9,6 @@ package main import ( "fmt" "go/ast" - "go/doc" "go/parser" "go/scanner" "go/token" @@ -17,7 +16,7 @@ import ( "strings" ) -func parse(name string, flags uint) *ast.File { +func parse(name string, flags parser.Mode) *ast.File { ast1, err := parser.ParseFile(fset, name, nil, flags) if err != nil { if list, ok := err.(scanner.ErrorList); ok { @@ -71,7 +70,7 @@ func (f *File) ReadGo(name string) { } sawC = true if s.Name != nil { - error(s.Path.Pos(), `cannot rename import "C"`) + error_(s.Path.Pos(), `cannot rename import "C"`) } cg := s.Doc if cg == nil && len(d.Specs) == 1 { @@ -79,12 +78,12 @@ func (f *File) ReadGo(name string) { } if cg != nil { f.Preamble += fmt.Sprintf("#line %d %q\n", sourceLine(cg), name) - f.Preamble += doc.CommentText(cg) + "\n" + f.Preamble += cg.Text() + "\n" } } } if !sawC { - error(token.NoPos, `cannot find import "C"`) + error_(token.NoPos, `cannot find import "C"`) } // In ast2, strip the import "C" line. @@ -128,6 +127,7 @@ func (f *File) ReadGo(name string) { f.walk(ast1, "prog", (*File).saveExport) f.walk(ast2, "prog", (*File).saveExport2) + f.Comments = ast1.Comments f.AST = ast2 } @@ -149,7 +149,7 @@ func (f *File) saveRef(x interface{}, context string) { } goname := sel.Sel.Name if goname == "errno" { - error(sel.Pos(), "cannot refer to errno directly; see documentation") + error_(sel.Pos(), "cannot refer to errno directly; see documentation") return } name := f.Name[goname] @@ -186,11 +186,11 @@ func (f *File) saveExport(x interface{}, context string) { name := strings.TrimSpace(string(c.Text[9:])) if name == "" { - error(c.Pos(), "export missing name") + error_(c.Pos(), "export missing name") } if name != n.Name.Name { - error(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name) + error_(c.Pos(), "export comment has wrong name %q, want %q", name, n.Name.Name) } f.ExpFunc = append(f.ExpFunc, &ExpFunc{ @@ -225,7 +225,7 @@ func (f *File) walk(x interface{}, context string, visit func(*File, interface{} // everything else just recurs default: - error(token.NoPos, "unexpected type %T in walk", x, visit) + error_(token.NoPos, "unexpected type %T in walk", x, visit) panic("unexpected type") case nil: diff --git a/src/cmd/cgo/doc.go b/src/cmd/cgo/doc.go index dc9edd6fd..6282c0bbf 100644 --- a/src/cmd/cgo/doc.go +++ b/src/cmd/cgo/doc.go @@ -59,7 +59,7 @@ struct_, union_, or enum_, as in C.struct_stat. Any C function that returns a value may be called in a multiple assignment context to retrieve both the return value and the -C errno variable as an os.Error. For example: +C errno variable as an error. For example: n, err := C.atoi("abc") @@ -87,6 +87,23 @@ by making copies of the data. In pseudo-Go definitions: // C pointer, length to Go []byte func C.GoBytes(unsafe.Pointer, C.int) []byte +Go functions can be exported for use by C code in the following way: + + //export MyFunction + func MyFunction(arg1, arg2 int, arg3 string) int64 {...} + + //export MyFunction2 + func MyFunction2(arg1, arg2 int, arg3 string) (int64, *C.char) {...} + +They will be available in the C code as: + + extern int64 MyFunction(int arg1, int arg2, GoString arg3); + extern struct MyFunction2_return MyFunction2(int arg1, int arg2, GoString arg3); + +found in _cgo_export.h generated header. Functions with multiple +return values are mapped to functions returning a struct. +Not all Go types can be mapped to C types in a useful way. + Cgo transforms the input file into four output files: two Go source files, a C file for 6c (or 8c or 5c), and a C file for gcc. diff --git a/src/cmd/cgo/gcc.go b/src/cmd/cgo/gcc.go index 04d95f0b9..155eb0440 100644 --- a/src/cmd/cgo/gcc.go +++ b/src/cmd/cgo/gcc.go @@ -14,6 +14,7 @@ import ( "debug/macho" "debug/pe" "encoding/binary" + "errors" "flag" "fmt" "go/ast" @@ -23,6 +24,7 @@ import ( "strconv" "strings" "unicode" + "unicode/utf8" ) var debugDefine = flag.Bool("debug-define", false, "print relevant #defines") @@ -58,6 +60,9 @@ func cname(s string) string { if strings.HasPrefix(s, "enum_") { return "enum " + s[len("enum_"):] } + if strings.HasPrefix(s, "sizeof_") { + return "sizeof(" + cname(s[len("sizeof_"):]) + ")" + } return s } @@ -71,7 +76,7 @@ func (p *Package) ParseFlags(f *File, srcfile string) { NextLine: for _, line := range linesIn { l := strings.TrimSpace(line) - if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(int(l[4])) { + if len(l) < 5 || l[:4] != "#cgo" || !unicode.IsSpace(rune(l[4])) { linesOut = append(linesOut, line) continue } @@ -147,10 +152,10 @@ func (p *Package) addToFlag(flag string, args []string) { // pkgConfig runs pkg-config and extracts --libs and --cflags information // for packages. -func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) { +func pkgConfig(packages []string) (cflags, ldflags []string, err error) { for _, name := range packages { if len(name) == 0 || name[0] == '-' { - return nil, nil, os.NewError(fmt.Sprintf("invalid name: %q", name)) + return nil, nil, errors.New(fmt.Sprintf("invalid name: %q", name)) } } @@ -158,7 +163,7 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) { stdout, stderr, ok := run(nil, args) if !ok { os.Stderr.Write(stderr) - return nil, nil, os.NewError("pkg-config failed") + return nil, nil, errors.New("pkg-config failed") } cflags, err = splitQuoted(string(stdout)) if err != nil { @@ -169,7 +174,7 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) { stdout, stderr, ok = run(nil, args) if !ok { os.Stderr.Write(stderr) - return nil, nil, os.NewError("pkg-config failed") + return nil, nil, errors.New("pkg-config failed") } ldflags, err = splitQuoted(string(stdout)) return @@ -191,30 +196,30 @@ func pkgConfig(packages []string) (cflags, ldflags []string, err os.Error) { // // []string{"a", "b:c d", "ef", `g"`} // -func splitQuoted(s string) (r []string, err os.Error) { +func splitQuoted(s string) (r []string, err error) { var args []string - arg := make([]int, len(s)) + arg := make([]rune, len(s)) escaped := false quoted := false - quote := 0 + quote := '\x00' i := 0 - for _, rune := range s { + for _, r := range s { switch { case escaped: escaped = false - case rune == '\\': + case r == '\\': escaped = true continue case quote != 0: - if rune == quote { + if r == quote { quote = 0 continue } - case rune == '"' || rune == '\'': + case r == '"' || r == '\'': quoted = true - quote = rune + quote = r continue - case unicode.IsSpace(rune): + case unicode.IsSpace(r): if quoted || i > 0 { quoted = false args = append(args, string(arg[:i])) @@ -222,21 +227,21 @@ func splitQuoted(s string) (r []string, err os.Error) { } continue } - arg[i] = rune + arg[i] = r i++ } if quoted || i > 0 { args = append(args, string(arg[:i])) } if quote != 0 { - err = os.NewError("unclosed quote") + err = errors.New("unclosed quote") } else if escaped { - err = os.NewError("unfinished escaping") + err = errors.New("unfinished escaping") } return args, err } -var safeBytes = []byte("+-.,/0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz") +var safeBytes = []byte(`+-.,/0123456789:=ABCDEFGHIJKLMNOPQRSTUVWXYZ\_abcdefghijklmnopqrstuvwxyz`) func safeName(s string) bool { if s == "" { @@ -339,14 +344,22 @@ func (p *Package) guessKinds(f *File) []*Name { if _, err := strconv.Atoi(n.Define); err == nil { ok = true } else if n.Define[0] == '"' || n.Define[0] == '\'' { - _, err := parser.ParseExpr(fset, "", n.Define) - if err == nil { + if _, err := parser.ParseExpr(n.Define); err == nil { ok = true } } if ok { n.Kind = "const" - n.Const = n.Define + // Turn decimal into hex, just for consistency + // with enum-derived constants. Otherwise + // in the cgo -godefs output half the constants + // are in hex and half are in whatever the #define used. + i, err := strconv.ParseInt(n.Define, 0, 64) + if err == nil { + n.Const = fmt.Sprintf("%#x", i) + } else { + n.Const = n.Define + } continue } @@ -420,7 +433,7 @@ func (p *Package) guessKinds(f *File) []*Name { case strings.Contains(line, ": statement with no effect"): what = "not-type" // const or func or var case strings.Contains(line, "undeclared"): - error(token.NoPos, "%s", strings.TrimSpace(line[colon+1:])) + error_(token.NoPos, "%s", strings.TrimSpace(line[colon+1:])) case strings.Contains(line, "is not an integer constant"): isConst[i] = false continue @@ -439,6 +452,11 @@ func (p *Package) guessKinds(f *File) []*Name { for i, b := range isConst { if b { names[i].Kind = "const" + if toSniff[i] != nil && names[i].Const == "" { + j := len(needType) + needType = needType[0 : j+1] + needType[j] = names[i] + } } } for _, n := range toSniff { @@ -448,7 +466,7 @@ func (p *Package) guessKinds(f *File) []*Name { if n.Kind != "" { continue } - error(token.NoPos, "could not determine kind of name for C.%s", n.Go) + error_(token.NoPos, "could not determine kind of name for C.%s", n.Go) } if nerrors > 0 { fatalf("unresolved names") @@ -576,6 +594,9 @@ func (p *Package) loadDWARF(f *File, names []*Name) { var conv typeConv conv.Init(p.PtrSize) for i, n := range names { + if types[i] == nil { + continue + } f, fok := types[i].(*dwarf.FuncType) if n.Kind != "type" && fok { n.Kind = "func" @@ -585,12 +606,12 @@ func (p *Package) loadDWARF(f *File, names []*Name) { if enums[i] != 0 && n.Type.EnumValues != nil { k := fmt.Sprintf("__cgo_enum__%d", i) n.Kind = "const" - n.Const = strconv.Itoa64(n.Type.EnumValues[k]) + n.Const = fmt.Sprintf("%#x", n.Type.EnumValues[k]) // Remove injected enum to ensure the value will deep-compare // equally in future loads of the same constant. - n.Type.EnumValues[k] = 0, false + delete(n.Type.EnumValues, k) } else if n.Kind == "const" && i < len(enumVal) { - n.Const = strconv.Itoa64(enumVal[i]) + n.Const = fmt.Sprintf("%#x", enumVal[i]) } } } @@ -599,7 +620,8 @@ func (p *Package) loadDWARF(f *File, names []*Name) { // rewriteRef rewrites all the C.xxx references in f.AST to refer to the // Go equivalents, now that we have figured out the meaning of all -// the xxx. +// the xxx. In *godefs or *cdefs mode, rewriteRef replaces the names +// with full definitions instead of mangled names. func (p *Package) rewriteRef(f *File) { // Assign mangled names. for _, n := range f.Name { @@ -617,7 +639,7 @@ func (p *Package) rewriteRef(f *File) { // functions are only used in calls. for _, r := range f.Ref { if r.Name.Kind == "const" && r.Name.Const == "" { - error(r.Pos(), "unable to find value of constant C.%s", r.Name.Go) + error_(r.Pos(), "unable to find value of constant C.%s", r.Name.Go) } var expr ast.Expr = ast.NewIdent(r.Name.Mangle) // default switch r.Context { @@ -628,12 +650,12 @@ func (p *Package) rewriteRef(f *File) { expr = r.Name.Type.Go break } - error(r.Pos(), "call of non-function C.%s", r.Name.Go) + error_(r.Pos(), "call of non-function C.%s", r.Name.Go) break } if r.Context == "call2" { if r.Name.FuncType.Result == nil { - error(r.Pos(), "assignment count mismatch: 2 = 0") + error_(r.Pos(), "assignment count mismatch: 2 = 0") } // Invent new Name for the two-result function. n := f.Name["2"+r.Name.Go] @@ -650,7 +672,7 @@ func (p *Package) rewriteRef(f *File) { } case "expr": if r.Name.Kind == "func" { - error(r.Pos(), "must call C.%s", r.Name.Go) + error_(r.Pos(), "must call C.%s", r.Name.Go) } if r.Name.Kind == "type" { // Okay - might be new(T) @@ -662,13 +684,28 @@ func (p *Package) rewriteRef(f *File) { case "type": if r.Name.Kind != "type" { - error(r.Pos(), "expression C.%s used as type", r.Name.Go) + error_(r.Pos(), "expression C.%s used as type", r.Name.Go) + } else if r.Name.Type == nil { + // Use of C.enum_x, C.struct_x or C.union_x without C definition. + // GCC won't raise an error when using pointers to such unknown types. + error_(r.Pos(), "type C.%s: undefined C type '%s'", r.Name.Go, r.Name.C) } else { expr = r.Name.Type.Go } default: if r.Name.Kind == "func" { - error(r.Pos(), "must call C.%s", r.Name.Go) + error_(r.Pos(), "must call C.%s", r.Name.Go) + } + } + if *godefs || *cdefs { + // Substitute definition for mangled type name. + if id, ok := expr.(*ast.Ident); ok { + if t := typedef[id.Name]; t != nil { + expr = t + } + if id.Name == r.Name.Mangle && r.Name.Const != "" { + expr = ast.NewIdent(r.Name.Const) + } } } *r.Expr = expr @@ -696,7 +733,9 @@ func (p *Package) gccMachine() []string { return nil } -var gccTmp = objDir + "_cgo_.o" +func gccTmp() string { + return *objDir + "_cgo_.o" +} // gccCmd returns the gcc command line to use for compiling // the input. @@ -705,7 +744,7 @@ func (p *Package) gccCmd() []string { p.gccName(), "-Wall", // many warnings "-Werror", // warnings are errors - "-o" + gccTmp, // write object to tmp + "-o" + gccTmp(), // write object to tmp "-gdwarf-2", // generate DWARF v2 debugging symbols "-fno-eliminate-unused-debug-types", // gets rid of e.g. untyped enum otherwise "-c", // do not link @@ -722,10 +761,10 @@ func (p *Package) gccCmd() []string { func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte) { runGcc(stdin, p.gccCmd()) - if f, err := macho.Open(gccTmp); err == nil { + if f, err := macho.Open(gccTmp()); err == nil { d, err := f.DWARF() if err != nil { - fatalf("cannot load DWARF output from %s: %v", gccTmp, err) + fatalf("cannot load DWARF output from %s: %v", gccTmp(), err) } var data []byte if f.Symtab != nil { @@ -751,23 +790,23 @@ func (p *Package) gccDebug(stdin []byte) (*dwarf.Data, binary.ByteOrder, []byte) // Can skip debug data block in ELF and PE for now. // The DWARF information is complete. - if f, err := elf.Open(gccTmp); err == nil { + if f, err := elf.Open(gccTmp()); err == nil { d, err := f.DWARF() if err != nil { - fatalf("cannot load DWARF output from %s: %v", gccTmp, err) + fatalf("cannot load DWARF output from %s: %v", gccTmp(), err) } return d, f.ByteOrder, nil } - if f, err := pe.Open(gccTmp); err == nil { + if f, err := pe.Open(gccTmp()); err == nil { d, err := f.DWARF() if err != nil { - fatalf("cannot load DWARF output from %s: %v", gccTmp, err) + fatalf("cannot load DWARF output from %s: %v", gccTmp(), err) } return d, binary.LittleEndian, nil } - fatalf("cannot parse gcc output %s as ELF, Mach-O, PE object", gccTmp) + fatalf("cannot parse gcc output %s as ELF, Mach-O, PE object", gccTmp()) panic("not reached") } @@ -848,6 +887,7 @@ type typeConv struct { var tagGen int var typedef = make(map[string]ast.Expr) +var goIdent = make(map[string]*ast.Ident) func (c *typeConv) Init(ptrSize int64) { c.ptrSize = ptrSize @@ -1113,6 +1153,7 @@ func (c *typeConv) Type(dtype dwarf.Type) *Type { } name := c.Ident("_Ctype_" + dt.Kind + "_" + tag) t.Go = name // publish before recursive calls + goIdent[name.Name] = name switch dt.Kind { case "union", "class": typedef[name.Name] = c.Opaque(t.Size) @@ -1147,7 +1188,8 @@ func (c *typeConv) Type(dtype dwarf.Type) *Type { t.Align = c.ptrSize break } - name := c.Ident("_Ctypedef_" + dt.Name) + name := c.Ident("_Ctype_" + dt.Name) + goIdent[name.Name] = name t.Go = name // publish before recursive call sub := c.Type(dt.Type) t.Size = sub.Size @@ -1155,6 +1197,9 @@ func (c *typeConv) Type(dtype dwarf.Type) *Type { if _, ok := typedef[name.Name]; !ok { typedef[name.Name] = sub.Go } + if *godefs || *cdefs { + t.Go = sub.Go + } case *dwarf.UcharType: if t.Size != 1 { @@ -1198,7 +1243,9 @@ func (c *typeConv) Type(dtype dwarf.Type) *Type { s = strings.Join(strings.Split(s, " "), "") // strip spaces name := c.Ident("_Ctype_" + s) typedef[name.Name] = t.Go - t.Go = name + if !*godefs && !*cdefs { + t.Go = name + } } } @@ -1263,7 +1310,7 @@ func (c *typeConv) FuncType(dtype *dwarf.FuncType) *FuncType { var gr []*ast.Field if _, ok := dtype.ReturnType.(*dwarf.VoidType); !ok && dtype.ReturnType != nil { r = c.Type(dtype.ReturnType) - gr = []*ast.Field{&ast.Field{Type: r.Go}} + gr = []*ast.Field{{Type: r.Go}} } return &FuncType{ Params: p, @@ -1292,7 +1339,7 @@ func (c *typeConv) Opaque(n int64) ast.Expr { func (c *typeConv) intExpr(n int64) ast.Expr { return &ast.BasicLit{ Kind: token.INT, - Value: strconv.Itoa64(n), + Value: strconv.FormatInt(n, 10), } } @@ -1323,38 +1370,61 @@ func (c *typeConv) Struct(dt *dwarf.StructType) (expr *ast.StructType, csyntax s ident[f.Name] = f.Name used[f.Name] = true } - for cid, goid := range ident { - if token.Lookup([]byte(goid)).IsKeyword() { - // Avoid keyword - goid = "_" + goid - // Also avoid existing fields - for _, exist := used[goid]; exist; _, exist = used[goid] { + if !*godefs && !*cdefs { + for cid, goid := range ident { + if token.Lookup(goid).IsKeyword() { + // Avoid keyword goid = "_" + goid - } - used[goid] = true - ident[cid] = goid + // Also avoid existing fields + for _, exist := used[goid]; exist; _, exist = used[goid] { + goid = "_" + goid + } + + used[goid] = true + ident[cid] = goid + } } } + anon := 0 for _, f := range dt.Field { - if f.BitSize > 0 && f.BitSize != f.ByteSize*8 { - continue - } if f.ByteOffset > off { fld = c.pad(fld, f.ByteOffset-off) off = f.ByteOffset } t := c.Type(f.Type) + tgo := t.Go + size := t.Size + + if f.BitSize > 0 { + if f.BitSize%8 != 0 { + continue + } + size = f.BitSize / 8 + name := tgo.(*ast.Ident).String() + if strings.HasPrefix(name, "int") { + name = "int" + } else { + name = "uint" + } + tgo = ast.NewIdent(name + fmt.Sprint(f.BitSize)) + } + n := len(fld) fld = fld[0 : n+1] - - fld[n] = &ast.Field{Names: []*ast.Ident{c.Ident(ident[f.Name])}, Type: t.Go} - off += t.Size + name := f.Name + if name == "" { + name = fmt.Sprintf("anon%d", anon) + anon++ + ident[name] = name + } + fld[n] = &ast.Field{Names: []*ast.Ident{c.Ident(ident[name])}, Type: tgo} + off += size buf.WriteString(t.C.String()) buf.WriteString(" ") - buf.WriteString(f.Name) + buf.WriteString(name) buf.WriteString("; ") if t.Align > align { align = t.Align @@ -1369,6 +1439,96 @@ func (c *typeConv) Struct(dt *dwarf.StructType) (expr *ast.StructType, csyntax s } buf.WriteString("}") csyntax = buf.String() + + if *godefs || *cdefs { + godefsFields(fld) + } expr = &ast.StructType{Fields: &ast.FieldList{List: fld}} return } + +func upper(s string) string { + if s == "" { + return "" + } + r, size := utf8.DecodeRuneInString(s) + if r == '_' { + return "X" + s + } + return string(unicode.ToUpper(r)) + s[size:] +} + +// godefsFields rewrites field names for use in Go or C definitions. +// It strips leading common prefixes (like tv_ in tv_sec, tv_usec) +// converts names to upper case, and rewrites _ into Pad_godefs_n, +// so that all fields are exported. +func godefsFields(fld []*ast.Field) { + prefix := fieldPrefix(fld) + npad := 0 + for _, f := range fld { + for _, n := range f.Names { + if strings.HasPrefix(n.Name, prefix) && n.Name != prefix { + n.Name = n.Name[len(prefix):] + } + if n.Name == "_" { + // Use exported name instead. + n.Name = "Pad_cgo_" + strconv.Itoa(npad) + npad++ + } + if !*cdefs { + n.Name = upper(n.Name) + } + } + p := &f.Type + t := *p + if star, ok := t.(*ast.StarExpr); ok { + star = &ast.StarExpr{X: star.X} + *p = star + p = &star.X + t = *p + } + if id, ok := t.(*ast.Ident); ok { + if id.Name == "unsafe.Pointer" { + *p = ast.NewIdent("*byte") + } + } + } +} + +// fieldPrefix returns the prefix that should be removed from all the +// field names when generating the C or Go code. For generated +// C, we leave the names as is (tv_sec, tv_usec), since that's what +// people are used to seeing in C. For generated Go code, such as +// package syscall's data structures, we drop a common prefix +// (so sec, usec, which will get turned into Sec, Usec for exporting). +func fieldPrefix(fld []*ast.Field) string { + if *cdefs { + return "" + } + prefix := "" + for _, f := range fld { + for _, n := range f.Names { + // Ignore field names that don't have the prefix we're + // looking for. It is common in C headers to have fields + // named, say, _pad in an otherwise prefixed header. + // If the struct has 3 fields tv_sec, tv_usec, _pad1, then we + // still want to remove the tv_ prefix. + // The check for "orig_" here handles orig_eax in the + // x86 ptrace register sets, which otherwise have all fields + // with reg_ prefixes. + if strings.HasPrefix(n.Name, "orig_") || strings.HasPrefix(n.Name, "_") { + continue + } + i := strings.Index(n.Name, "_") + if i < 0 { + continue + } + if prefix == "" { + prefix = n.Name[:i+1] + } else if prefix != n.Name[:i+1] { + return "" + } + } + } + return prefix +} diff --git a/src/cmd/cgo/godefs.go b/src/cmd/cgo/godefs.go new file mode 100644 index 000000000..683872927 --- /dev/null +++ b/src/cmd/cgo/godefs.go @@ -0,0 +1,289 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/printer" + "go/token" + "os" + "strings" +) + +// godefs returns the output for -godefs mode. +func (p *Package) godefs(f *File, srcfile string) string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "// Created by cgo -godefs - DO NOT EDIT\n") + fmt.Fprintf(&buf, "// %s\n", strings.Join(os.Args, " ")) + fmt.Fprintf(&buf, "\n") + + override := make(map[string]string) + + // Allow source file to specify override mappings. + // For example, the socket data structures refer + // to in_addr and in_addr6 structs but we want to be + // able to treat them as byte arrays, so the godefs + // inputs in package syscall say + // + // // +godefs map struct_in_addr [4]byte + // // +godefs map struct_in_addr6 [16]byte + // + for _, g := range f.Comments { + for _, c := range g.List { + i := strings.Index(c.Text, "+godefs map") + if i < 0 { + continue + } + s := strings.TrimSpace(c.Text[i+len("+godefs map"):]) + i = strings.Index(s, " ") + if i < 0 { + fmt.Fprintf(os.Stderr, "invalid +godefs map comment: %s\n", c.Text) + continue + } + override["_Ctype_"+strings.TrimSpace(s[:i])] = strings.TrimSpace(s[i:]) + } + } + for _, n := range f.Name { + if s := override[n.Go]; s != "" { + override[n.Mangle] = s + } + } + + // Otherwise, if the source file says type T C.whatever, + // use "T" as the mangling of C.whatever, + // except in the definition (handled at end of function). + refName := make(map[*ast.Expr]*Name) + for _, r := range f.Ref { + refName[r.Expr] = r.Name + } + for _, d := range f.AST.Decls { + d, ok := d.(*ast.GenDecl) + if !ok || d.Tok != token.TYPE { + continue + } + for _, s := range d.Specs { + s := s.(*ast.TypeSpec) + n := refName[&s.Type] + if n != nil && n.Mangle != "" { + override[n.Mangle] = s.Name.Name + } + } + } + + // Extend overrides using typedefs: + // If we know that C.xxx should format as T + // and xxx is a typedef for yyy, make C.yyy format as T. + for typ, def := range typedef { + if new := override[typ]; new != "" { + if id, ok := def.(*ast.Ident); ok { + override[id.Name] = new + } + } + } + + // Apply overrides. + for old, new := range override { + if id := goIdent[old]; id != nil { + id.Name = new + } + } + + // Any names still using the _C syntax are not going to compile, + // although in general we don't know whether they all made it + // into the file, so we can't warn here. + // + // The most common case is union types, which begin with + // _Ctype_union and for which typedef[name] is a Go byte + // array of the appropriate size (such as [4]byte). + // Substitute those union types with byte arrays. + for name, id := range goIdent { + if id.Name == name && strings.Contains(name, "_Ctype_union") { + if def := typedef[name]; def != nil { + id.Name = gofmt(def) + } + } + } + + printer.Fprint(&buf, fset, f.AST) + + return buf.String() +} + +// cdefs returns the output for -cdefs mode. +// The easiest way to do this is to translate the godefs Go to C. +func (p *Package) cdefs(f *File, srcfile string) string { + godefsOutput := p.godefs(f, srcfile) + + lines := strings.Split(godefsOutput, "\n") + lines[0] = "// Created by cgo -cdefs - DO NOT EDIT" + + for i, line := range lines { + lines[i] = strings.TrimSpace(line) + } + + var out bytes.Buffer + printf := func(format string, args ...interface{}) { fmt.Fprintf(&out, format, args...) } + + didTypedef := false + for i := 0; i < len(lines); i++ { + line := lines[i] + + // Delete + // package x + if strings.HasPrefix(line, "package ") { + continue + } + + // Convert + // const ( + // A = 1 + // B = 2 + // ) + // + // to + // + // enum { + // A = 1, + // B = 2, + // }; + if line == "const (" { + printf("enum {\n") + for i++; i < len(lines) && lines[i] != ")"; i++ { + line = lines[i] + if line != "" { + printf("\t%s,", line) + } + printf("\n") + } + printf("};\n") + continue + } + + // Convert + // const A = 1 + // to + // enum { A = 1 }; + if strings.HasPrefix(line, "const ") { + printf("enum { %s };\n", line[len("const "):]) + continue + } + + // On first type definition, typedef all the structs + // in case there are dependencies between them. + if !didTypedef && strings.HasPrefix(line, "type ") { + didTypedef = true + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "type ") && strings.HasSuffix(line, " struct {") { + s := line[len("type ") : len(line)-len(" struct {")] + printf("typedef struct %s %s;\n", s, s) + } + } + printf("\n") + printf("#pragma pack on\n") + printf("\n") + } + + // Convert + // type T struct { + // X int64 + // Y *int32 + // Z [4]byte + // } + // + // to + // + // struct T { + // int64 X; + // int32 *Y; + // byte Z[4]; + // } + if strings.HasPrefix(line, "type ") && strings.HasSuffix(line, " struct {") { + s := line[len("type ") : len(line)-len(" struct {")] + printf("struct %s {\n", s) + for i++; i < len(lines) && lines[i] != "}"; i++ { + line := lines[i] + if line != "" { + f := strings.Fields(line) + if len(f) != 2 { + fmt.Fprintf(os.Stderr, "cgo: cannot parse struct field: %s\n", line) + nerrors++ + continue + } + printf("\t%s;", cdecl(f[0], f[1])) + } + printf("\n") + } + printf("};\n") + continue + } + + // Convert + // type T int + // to + // typedef int T; + if strings.HasPrefix(line, "type ") { + f := strings.Fields(line[len("type "):]) + if len(f) != 2 { + fmt.Fprintf(os.Stderr, "cgo: cannot parse type definition: %s\n", line) + nerrors++ + continue + } + printf("typedef\t%s;\n", cdecl(f[0], f[1])) + continue + } + + printf("%s\n", line) + } + + if didTypedef { + printf("\n") + printf("#pragma pack off\n") + } + + return out.String() +} + +// cdecl returns the C declaration for the given Go name and type. +// It only handles the specific cases necessary for converting godefs output. +func cdecl(name, typ string) string { + // X *[0]byte -> X *void + if strings.HasPrefix(typ, "*[0]") { + typ = "*void" + } + // X *byte -> *X byte + if strings.HasPrefix(typ, "*") { + name = "*" + name + typ = typ[1:] + } + // X [4]byte -> X[4] byte + if strings.HasPrefix(typ, "[") { + i := strings.Index(typ, "]") + 1 + name = name + typ[:i] + typ = typ[i:] + } + // X T -> T X + // Handle the special case: 'unsafe.Pointer' is 'void *' + if typ == "unsafe.Pointer" { + typ = "void" + name = "*" + name + } + return typ + "\t" + name +} + +var gofmtBuf bytes.Buffer + +// gofmt returns the gofmt-formatted string for an AST node. +func gofmt(n interface{}) string { + gofmtBuf.Reset() + err := printer.Fprint(&gofmtBuf, fset, n) + if err != nil { + return "<" + err.Error() + ">" + } + return gofmtBuf.String() +} diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go index 106698114..f58291237 100644 --- a/src/cmd/cgo/main.go +++ b/src/cmd/cgo/main.go @@ -43,6 +43,7 @@ type Package struct { // A File collects information about a single Go input file. type File struct { AST *ast.File // parsed AST + Comments []*ast.CommentGroup // comments from file Package string // Package name Preamble string // C preamble (doc comment on import "C") Ref []*Ref // all references to C.xxx in AST @@ -122,7 +123,17 @@ var cPrefix string var fset = token.NewFileSet() var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import data for that file") +var dynout = flag.String("dynout", "", "write -dynobj output to this file") +// These flags are for bootstrapping a new Go implementation, +// to generate Go and C headers that match the data layout and +// constant values used in the host's C libraries and system calls. +var godefs = flag.Bool("godefs", false, "for bootstrap: write Go definitions for C file to standard output") +var cdefs = flag.Bool("cdefs", false, "for bootstrap: write C definitions for C file to standard output") +var objDir = flag.String("objdir", "", "object directory") + +var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo") +var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code") var goarch, goos string func main() { @@ -142,6 +153,11 @@ func main() { return } + if *godefs && *cdefs { + fmt.Fprintf(os.Stderr, "cgo: cannot use -cdefs and -godefs together\n") + os.Exit(2) + } + args := flag.Args() if len(args) < 1 { usage() @@ -159,36 +175,9 @@ func main() { usage() } - // Copy it to a new slice so it can grow. - gccOptions := make([]string, i) - copy(gccOptions, args[0:i]) - goFiles := args[i:] - goarch = runtime.GOARCH - if s := os.Getenv("GOARCH"); s != "" { - goarch = s - } - goos = runtime.GOOS - if s := os.Getenv("GOOS"); s != "" { - goos = s - } - ptrSize := ptrSizeMap[goarch] - if ptrSize == 0 { - fatalf("unknown $GOARCH %q", goarch) - } - - // Clear locale variables so gcc emits English errors [sic]. - os.Setenv("LANG", "en_US.UTF-8") - os.Setenv("LC_ALL", "C") - os.Setenv("LC_CTYPE", "C") - - p := &Package{ - PtrSize: ptrSize, - GccOptions: gccOptions, - CgoFlags: make(map[string]string), - Written: make(map[string]bool), - } + p := newPackage(args[:i]) // Need a unique prefix for the global C symbols that // we use to coordinate between gcc and ourselves. @@ -204,7 +193,7 @@ func main() { io.Copy(h, f) f.Close() } - cPrefix = fmt.Sprintf("_%x", h.Sum()[0:6]) + cPrefix = fmt.Sprintf("_%x", h.Sum(nil)[0:6]) fs := make([]*File, len(goFiles)) for i, input := range goFiles { @@ -215,9 +204,13 @@ func main() { fs[i] = f } - // make sure that _obj directory exists, so that we can write - // all the output files there. - os.Mkdir("_obj", 0777) + if *objDir == "" { + // make sure that _obj directory exists, so that we can write + // all the output files there. + os.Mkdir("_obj", 0777) + *objDir = "_obj" + } + *objDir += string(filepath.Separator) for i, input := range goFiles { f := fs[i] @@ -239,23 +232,64 @@ func main() { pkg = filepath.Join(dir, pkg) } p.PackagePath = pkg - p.writeOutput(f, input) - p.Record(f) + if *godefs { + os.Stdout.WriteString(p.godefs(f, input)) + } else if *cdefs { + os.Stdout.WriteString(p.cdefs(f, input)) + } else { + p.writeOutput(f, input) + } } - p.writeDefs() + if !*godefs && !*cdefs { + p.writeDefs() + } if nerrors > 0 { os.Exit(2) } } +// newPackage returns a new Package that will invoke +// gcc with the additional arguments specified in args. +func newPackage(args []string) *Package { + // Copy the gcc options to a new slice so the list + // can grow without overwriting the slice that args is in. + gccOptions := make([]string, len(args)) + copy(gccOptions, args) + + goarch = runtime.GOARCH + if s := os.Getenv("GOARCH"); s != "" { + goarch = s + } + goos = runtime.GOOS + if s := os.Getenv("GOOS"); s != "" { + goos = s + } + ptrSize := ptrSizeMap[goarch] + if ptrSize == 0 { + fatalf("unknown $GOARCH %q", goarch) + } + + // Reset locale variables so gcc emits English errors [sic]. + os.Setenv("LANG", "en_US.UTF-8") + os.Setenv("LC_ALL", "C") + + p := &Package{ + PtrSize: ptrSize, + GccOptions: gccOptions, + CgoFlags: make(map[string]string), + Written: make(map[string]bool), + } + return p +} + // Record what needs to be recorded about f. func (p *Package) Record(f *File) { if p.PackageName == "" { p.PackageName = f.Package } else if p.PackageName != f.Package { - error(token.NoPos, "inconsistent package names: %s, %s", p.PackageName, f.Package) + error_(token.NoPos, "inconsistent package names: %s, %s", p.PackageName, f.Package) } if p.Name == nil { @@ -265,7 +299,7 @@ func (p *Package) Record(f *File) { if p.Name[k] == nil { p.Name[k] = v } else if !reflect.DeepEqual(p.Name[k], v) { - error(token.NoPos, "inconsistent definitions for C.%s", k) + error_(token.NoPos, "inconsistent definitions for C.%s", k) } } } diff --git a/src/cmd/cgo/out.go b/src/cmd/cgo/out.go index 498ab1566..3e25b2099 100644 --- a/src/cmd/cgo/out.go +++ b/src/cmd/cgo/out.go @@ -14,20 +14,17 @@ import ( "go/printer" "go/token" "os" - "path/filepath" "strings" ) -var objDir = "_obj" + string(filepath.Separator) - // writeDefs creates output files to be compiled by 6g, 6c, and gcc. // (The comments here say 6g and 6c but the code applies to the 8 and 5 tools too.) func (p *Package) writeDefs() { - fgo2 := creat(objDir + "_cgo_gotypes.go") - fc := creat(objDir + "_cgo_defun.c") - fm := creat(objDir + "_cgo_main.c") + fgo2 := creat(*objDir + "_cgo_gotypes.go") + fc := creat(*objDir + "_cgo_defun.c") + fm := creat(*objDir + "_cgo_main.c") - fflg := creat(objDir + "_cgo_flags") + fflg := creat(*objDir + "_cgo_flags") for k, v := range p.CgoFlags { fmt.Fprintf(fflg, "_CGO_%s=%s\n", k, v) } @@ -35,7 +32,13 @@ func (p *Package) writeDefs() { // Write C main file for using gcc to resolve imports. fmt.Fprintf(fm, "int main() { return 0; }\n") - fmt.Fprintf(fm, "void crosscall2(void(*fn)(void*, int), void *a, int c) { }\n") + if *importRuntimeCgo { + fmt.Fprintf(fm, "void crosscall2(void(*fn)(void*, int), void *a, int c) { }\n") + } else { + // If we're not importing runtime/cgo, we *are* runtime/cgo, + // which provides crosscall2. We just need a prototype. + fmt.Fprintf(fm, "void crosscall2(void(*fn)(void*, int), void *a, int c);") + } fmt.Fprintf(fm, "void _cgo_allocate(void *a, int c) { }\n") fmt.Fprintf(fm, "void _cgo_panic(void *a, int c) { }\n") @@ -45,19 +48,25 @@ func (p *Package) writeDefs() { fmt.Fprintf(fgo2, "// Created by cgo - DO NOT EDIT\n\n") fmt.Fprintf(fgo2, "package %s\n\n", p.PackageName) fmt.Fprintf(fgo2, "import \"unsafe\"\n\n") - fmt.Fprintf(fgo2, "import \"os\"\n\n") - fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\n\n") + fmt.Fprintf(fgo2, "import \"syscall\"\n\n") + if !*gccgo && *importRuntimeCgo { + fmt.Fprintf(fgo2, "import _ \"runtime/cgo\"\n\n") + } fmt.Fprintf(fgo2, "type _ unsafe.Pointer\n\n") - fmt.Fprintf(fgo2, "func _Cerrno(dst *os.Error, x int) { *dst = os.Errno(x) }\n") + fmt.Fprintf(fgo2, "func _Cerrno(dst *error, x int) { *dst = syscall.Errno(x) }\n") for name, def := range typedef { fmt.Fprintf(fgo2, "type %s ", name) printer.Fprint(fgo2, fset, def) - fmt.Fprintf(fgo2, "\n") + fmt.Fprintf(fgo2, "\n\n") } fmt.Fprintf(fgo2, "type _Ctype_void [0]byte\n") - fmt.Fprintf(fc, cProlog) + if *gccgo { + fmt.Fprintf(fc, cPrologGccgo) + } else { + fmt.Fprintf(fc, cProlog) + } cVars := make(map[string]bool) for _, n := range p.Name { @@ -103,6 +112,15 @@ func (p *Package) writeDefs() { } func dynimport(obj string) { + stdout := os.Stdout + if *dynout != "" { + f, err := os.Create(*dynout) + if err != nil { + fatalf("%s", err) + } + stdout = f + } + if f, err := elf.Open(obj); err == nil { sym, err := f.ImportedSymbols() if err != nil { @@ -113,14 +131,14 @@ func dynimport(obj string) { if s.Version != "" { targ += "@" + s.Version } - fmt.Printf("#pragma dynimport %s %s %q\n", s.Name, targ, s.Library) + fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\n", s.Name, targ, s.Library) } lib, err := f.ImportedLibraries() if err != nil { fatalf("cannot load imported libraries from ELF file %s: %v", obj, err) } for _, l := range lib { - fmt.Printf("#pragma dynimport _ _ %q\n", l) + fmt.Fprintf(stdout, "#pragma dynimport _ _ %q\n", l) } return } @@ -134,14 +152,14 @@ func dynimport(obj string) { if len(s) > 0 && s[0] == '_' { s = s[1:] } - fmt.Printf("#pragma dynimport %s %s %q\n", s, s, "") + fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\n", s, s, "") } lib, err := f.ImportedLibraries() if err != nil { fatalf("cannot load imported libraries from Mach-O file %s: %v", obj, err) } for _, l := range lib { - fmt.Printf("#pragma dynimport _ _ %q\n", l) + fmt.Fprintf(stdout, "#pragma dynimport _ _ %q\n", l) } return } @@ -153,7 +171,7 @@ func dynimport(obj string) { } for _, s := range sym { ss := strings.Split(s, ":") - fmt.Printf("#pragma dynimport %s %s %q\n", ss[0], ss[0], strings.ToLower(ss[1])) + fmt.Fprintf(stdout, "#pragma dynimport %s %s %q\n", ss[0], ss[0], strings.ToLower(ss[1])) } return } @@ -203,7 +221,7 @@ func (p *Package) structType(n *Name) (string, int64) { off += pad } if n.AddError { - fmt.Fprint(&buf, "\t\tvoid *e[2]; /* os.Error */\n") + fmt.Fprint(&buf, "\t\tvoid *e[2]; /* error */\n") off += 2 * p.PtrSize } if off == 0 { @@ -217,9 +235,9 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) { name := n.Go gtype := n.FuncType.Go if n.AddError { - // Add "os.Error" to return type list. + // Add "error" to return type list. // Type list is known to be 0 or 1 element - it's a C function. - err := &ast.Field{Type: ast.NewIdent("os.Error")} + err := &ast.Field{Type: ast.NewIdent("error")} l := gtype.Results.List if len(l) == 0 { l = []*ast.Field{err} @@ -238,13 +256,22 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) { Type: gtype, } printer.Fprint(fgo2, fset, d) - fmt.Fprintf(fgo2, "\n") + if *gccgo { + fmt.Fprintf(fgo2, " __asm__(\"%s\")\n", n.C) + } else { + fmt.Fprintf(fgo2, "\n") + } if name == "CString" || name == "GoString" || name == "GoStringN" || name == "GoBytes" { // The builtins are already defined in the C prolog. return } + // gccgo does not require a wrapper unless an error must be returned. + if *gccgo && !n.AddError { + return + } + var argSize int64 _, argSize = p.structType(n) @@ -276,7 +303,7 @@ func (p *Package) writeDefsFunc(fc, fgo2 *os.File, n *Name) { v[0] = 0; v[1] = 0; } else { - ·_Cerrno(v, e); /* fill in v as os.Error for errno e */ + ·_Cerrno(v, e); /* fill in v as error for errno e */ } }`) } @@ -292,8 +319,8 @@ func (p *Package) writeOutput(f *File, srcfile string) { base = base[0 : len(base)-3] } base = strings.Map(slashToUnderscore, base) - fgo1 := creat(objDir + base + ".cgo1.go") - fgcc := creat(objDir + base + ".cgo2.c") + fgo1 := creat(*objDir + base + ".cgo1.go") + fgcc := creat(*objDir + base + ".cgo2.c") p.GoFiles = append(p.GoFiles, base+".cgo1.go") p.GccFiles = append(p.GccFiles, base+".cgo2.c") @@ -368,8 +395,8 @@ func (p *Package) writeOutputFunc(fgcc *os.File, n *Name) { // Write out the various stubs we need to support functions exported // from Go so that they are callable from C. func (p *Package) writeExports(fgo2, fc, fm *os.File) { - fgcc := creat(objDir + "_cgo_export.c") - fgcch := creat("_cgo_export.h") + fgcc := creat(*objDir + "_cgo_export.c") + fgcch := creat(*objDir + "_cgo_export.h") fmt.Fprintf(fgcch, "/* Created by cgo - DO NOT EDIT. */\n") fmt.Fprintf(fgcch, "%s\n", gccExportHeaderProlog) @@ -501,6 +528,7 @@ func (p *Package) writeExports(fgo2, fc, fm *os.File) { if fn.Recv != nil { goname = "_cgoexpwrap" + cPrefix + "_" + fn.Recv.List[0].Names[0].Name + "_" + goname } + fmt.Fprintf(fc, "#pragma dynexport %s %s\n", goname, goname) fmt.Fprintf(fc, "extern void ·%s();\n", goname) fmt.Fprintf(fc, "\nvoid\n") fmt.Fprintf(fc, "_cgoexp%s_%s(void *a, int32 n)\n", cPrefix, exp.ExpName) @@ -577,22 +605,25 @@ func c(repr string, args ...interface{}) *TypeRepr { // Map predeclared Go types to Type. var goTypes = map[string]*Type{ - "int": &Type{Size: 4, Align: 4, C: c("int")}, - "uint": &Type{Size: 4, Align: 4, C: c("uint")}, - "int8": &Type{Size: 1, Align: 1, C: c("schar")}, - "uint8": &Type{Size: 1, Align: 1, C: c("uchar")}, - "int16": &Type{Size: 2, Align: 2, C: c("short")}, - "uint16": &Type{Size: 2, Align: 2, C: c("ushort")}, - "int32": &Type{Size: 4, Align: 4, C: c("int")}, - "uint32": &Type{Size: 4, Align: 4, C: c("uint")}, - "int64": &Type{Size: 8, Align: 8, C: c("int64")}, - "uint64": &Type{Size: 8, Align: 8, C: c("uint64")}, - "float": &Type{Size: 4, Align: 4, C: c("float")}, - "float32": &Type{Size: 4, Align: 4, C: c("float")}, - "float64": &Type{Size: 8, Align: 8, C: c("double")}, - "complex": &Type{Size: 8, Align: 8, C: c("__complex float")}, - "complex64": &Type{Size: 8, Align: 8, C: c("__complex float")}, - "complex128": &Type{Size: 16, Align: 16, C: c("__complex double")}, + "bool": {Size: 1, Align: 1, C: c("uchar")}, + "byte": {Size: 1, Align: 1, C: c("uchar")}, + "int": {Size: 4, Align: 4, C: c("int")}, + "uint": {Size: 4, Align: 4, C: c("uint")}, + "rune": {Size: 4, Align: 4, C: c("int")}, + "int8": {Size: 1, Align: 1, C: c("schar")}, + "uint8": {Size: 1, Align: 1, C: c("uchar")}, + "int16": {Size: 2, Align: 2, C: c("short")}, + "uint16": {Size: 2, Align: 2, C: c("ushort")}, + "int32": {Size: 4, Align: 4, C: c("int")}, + "uint32": {Size: 4, Align: 4, C: c("uint")}, + "int64": {Size: 8, Align: 8, C: c("int64")}, + "uint64": {Size: 8, Align: 8, C: c("uint64")}, + "float": {Size: 4, Align: 4, C: c("float")}, + "float32": {Size: 4, Align: 4, C: c("float")}, + "float64": {Size: 8, Align: 8, C: c("double")}, + "complex": {Size: 8, Align: 8, C: c("__complex float")}, + "complex64": {Size: 8, Align: 8, C: c("__complex float")}, + "complex128": {Size: 16, Align: 16, C: c("__complex double")}, } // Map an ast type to a Type. @@ -610,7 +641,7 @@ func (p *Package) cgoType(e ast.Expr) *Type { case *ast.FuncType: return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("void*")} case *ast.InterfaceType: - return &Type{Size: 3 * p.PtrSize, Align: p.PtrSize, C: c("GoInterface")} + return &Type{Size: 2 * p.PtrSize, Align: p.PtrSize, C: c("GoInterface")} case *ast.MapType: return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("GoMap")} case *ast.ChanType: @@ -644,13 +675,16 @@ func (p *Package) cgoType(e ast.Expr) *Type { if t.Name == "string" { return &Type{Size: p.PtrSize + 4, Align: p.PtrSize, C: c("GoString")} } + if t.Name == "error" { + return &Type{Size: 2 * p.PtrSize, Align: p.PtrSize, C: c("GoInterface")} + } if r, ok := goTypes[t.Name]; ok { if r.Align > p.PtrSize { r.Align = p.PtrSize } return r } - error(e.Pos(), "unrecognized Go type %s", t.Name) + error_(e.Pos(), "unrecognized Go type %s", t.Name) return &Type{Size: 4, Align: 4, C: c("int")} case *ast.SelectorExpr: id, ok := t.X.(*ast.Ident) @@ -658,7 +692,7 @@ func (p *Package) cgoType(e ast.Expr) *Type { return &Type{Size: p.PtrSize, Align: p.PtrSize, C: c("void*")} } } - error(e.Pos(), "unrecognized Go type %T", e) + error_(e.Pos(), "unrecognized Go type %T", e) return &Type{Size: 4, Align: 4, C: c("int")} } @@ -729,6 +763,42 @@ void } ` +const cPrologGccgo = ` +#include <stdint.h> +#include <string.h> + +struct __go_string { + const unsigned char *__data; + int __length; +}; + +typedef struct __go_open_array { + void* __values; + int __count; + int __capacity; +} Slice; + +struct __go_string __go_byte_array_to_string(const void* p, int len); +struct __go_open_array __go_string_to_byte_array (struct __go_string str); + +const char *CString(struct __go_string s) { + return strndup(s.__data, s.__length); +} + +struct __go_string GoString(char *p) { + return __go_byte_array_to_string(p, strlen(p)); +} + +struct __go_string GoStringN(char *p, int n) { + return __go_byte_array_to_string(p, n); +} + +Slice GoBytes(char *p, int n) { + struct __go_string s = { p, n }; + return __go_string_to_byte_array(s); +} +` + const gccExportHeaderProlog = ` typedef unsigned int uint; typedef signed char schar; diff --git a/src/cmd/cgo/util.go b/src/cmd/cgo/util.go index e79b0e1bf..8a778418d 100644 --- a/src/cmd/cgo/util.go +++ b/src/cmd/cgo/util.go @@ -5,11 +5,11 @@ package main import ( - "exec" "fmt" "go/token" "io/ioutil" "os" + "os/exec" ) // run runs the command argv, feeding in stdin on standard input. @@ -72,7 +72,7 @@ func fatalf(msg string, args ...interface{}) { var nerrors int -func error(pos token.Pos, msg string, args ...interface{}) { +func error_(pos token.Pos, msg string, args ...interface{}) { nerrors++ if pos.IsValid() { fmt.Fprintf(os.Stderr, "%s: ", fset.Position(pos).String()) @@ -102,7 +102,7 @@ func creat(name string) *os.File { return f } -func slashToUnderscore(c int) int { +func slashToUnderscore(c rune) rune { if c == '/' || c == '\\' || c == ':' { c = '_' } diff --git a/src/cmd/cov/Makefile b/src/cmd/cov/Makefile index 62836fcac..c080f4a28 100644 --- a/src/cmd/cov/Makefile +++ b/src/cmd/cov/Makefile @@ -29,6 +29,7 @@ endif install: install-$(NAME) install-linux: install-default install-freebsd: install-default +install-netbsd: install-default install-openbsd: install-default install-windows: install-default diff --git a/src/cmd/cov/main.c b/src/cmd/cov/main.c index ecbabf371..9496632c5 100644 --- a/src/cmd/cov/main.c +++ b/src/cmd/cov/main.c @@ -7,10 +7,8 @@ */ #include <u.h> -#include <time.h> #include <libc.h> #include <bio.h> -#include <ctype.h> #include "tree.h" #include <ureg_amd64.h> @@ -394,7 +392,7 @@ startprocess(char **argv) pid = getpid(); if(ctlproc(pid, "hang") < 0) sysfatal("ctlproc hang: %r"); - execv(argv[0], argv); + exec(argv[0], argv); sysfatal("exec %s: %r", argv[0]); } if(ctlproc(pid, "attached") < 0 || ctlproc(pid, "waitstop") < 0) @@ -454,7 +452,6 @@ main(int argc, char **argv) if(argc == 0) { *--argv = "6.out"; - argc++; } fd = open(argv[0], OREAD); if(fd < 0) diff --git a/src/cmd/ebnflint/Makefile b/src/cmd/ebnflint/Makefile deleted file mode 100644 index 8f030aaef..000000000 --- a/src/cmd/ebnflint/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2009 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include ../../Make.inc - -TARG=ebnflint -GOFILES=\ - ebnflint.go\ - -include ../../Make.cmd - -test: $(TARG) - $(TARG) -start="SourceFile" "$(GOROOT)"/doc/go_spec.html - diff --git a/src/cmd/ebnflint/doc.go b/src/cmd/ebnflint/doc.go deleted file mode 100644 index f35976eea..000000000 --- a/src/cmd/ebnflint/doc.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - -Ebnflint verifies that EBNF productions are consistent and gramatically correct. -It reads them from an HTML document such as the Go specification. - -Grammar productions are grouped in boxes demarcated by the HTML elements - <pre class="ebnf"> - </pre> - - -Usage: - ebnflint [--start production] [file] - -The --start flag specifies the name of the start production for -the grammar; it defaults to "Start". - -*/ -package documentation diff --git a/src/cmd/ebnflint/ebnflint.go b/src/cmd/ebnflint/ebnflint.go deleted file mode 100644 index 009b336f3..000000000 --- a/src/cmd/ebnflint/ebnflint.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "ebnf" - "flag" - "fmt" - "go/scanner" - "go/token" - "io/ioutil" - "os" - "path/filepath" -) - -var fset = token.NewFileSet() -var start = flag.String("start", "Start", "name of start production") - -func usage() { - fmt.Fprintf(os.Stderr, "usage: ebnflint [flags] [filename]\n") - flag.PrintDefaults() - os.Exit(1) -} - -// Markers around EBNF sections in .html files -var ( - open = []byte(`<pre class="ebnf">`) - close = []byte(`</pre>`) -) - -func report(err os.Error) { - scanner.PrintError(os.Stderr, err) - os.Exit(1) -} - -func extractEBNF(src []byte) []byte { - var buf bytes.Buffer - - for { - // i = beginning of EBNF text - i := bytes.Index(src, open) - if i < 0 { - break // no EBNF found - we are done - } - i += len(open) - - // write as many newlines as found in the excluded text - // to maintain correct line numbers in error messages - for _, ch := range src[0:i] { - if ch == '\n' { - buf.WriteByte('\n') - } - } - - // j = end of EBNF text (or end of source) - j := bytes.Index(src[i:], close) // close marker - if j < 0 { - j = len(src) - i - } - j += i - - // copy EBNF text - buf.Write(src[i:j]) - - // advance - src = src[j:] - } - - return buf.Bytes() -} - -func main() { - flag.Parse() - - var ( - filename string - src []byte - err os.Error - ) - switch flag.NArg() { - case 0: - filename = "<stdin>" - src, err = ioutil.ReadAll(os.Stdin) - case 1: - filename = flag.Arg(0) - src, err = ioutil.ReadFile(filename) - default: - usage() - } - if err != nil { - report(err) - } - - if filepath.Ext(filename) == ".html" || bytes.Index(src, open) >= 0 { - src = extractEBNF(src) - } - - grammar, err := ebnf.Parse(fset, filename, src) - if err != nil { - report(err) - } - - if err = ebnf.Verify(fset, grammar, *start); err != nil { - report(err) - } -} diff --git a/src/cmd/gc/Makefile b/src/cmd/gc/Makefile index f7e305178..bb0d01637 100644 --- a/src/cmd/gc/Makefile +++ b/src/cmd/gc/Makefile @@ -24,15 +24,17 @@ OFILES=\ dcl.$O\ esc.$O\ export.$O\ + fmt.$O\ gen.$O\ init.$O\ + inl.$O\ lex.$O\ md5.$O\ mparith1.$O\ mparith2.$O\ mparith3.$O\ obj.$O\ - print.$O\ + order.$O\ range.$O\ reflect.$O\ select.$O\ @@ -44,6 +46,8 @@ OFILES=\ walk.$O\ y1.tab.$O\ +HOST_CFLAGS+=-DGOEXPERIMENT='"$(GOEXPERIMENT)"' + NOINSTALL=1 include ../../Make.clib @@ -60,7 +64,7 @@ subr.$O: yerr.h builtin.c: builtin.c.boot cp builtin.c.boot builtin.c -subr.$O: opnames.h +fmt.$O: opnames.h opnames.h: mkopnames go.h ./mkopnames go.h >opnames.h diff --git a/src/cmd/gc/align.c b/src/cmd/gc/align.c index f316c19e0..6982bbe56 100644 --- a/src/cmd/gc/align.c +++ b/src/cmd/gc/align.c @@ -285,6 +285,9 @@ dowidth(Type *t) break; } + if(widthptr == 4 && w != (int32)w) + yyerror("type %T too large", t); + t->width = w; if(t->align == 0) { if(w > 8 || (w&(w-1)) != 0) @@ -491,12 +494,13 @@ typeinit(void) okforeq[TPTR64] = 1; okforeq[TUNSAFEPTR] = 1; okforeq[TINTER] = 1; - okforeq[TMAP] = 1; okforeq[TCHAN] = 1; - okforeq[TFUNC] = 1; okforeq[TSTRING] = 1; okforeq[TBOOL] = 1; - okforeq[TARRAY] = 1; // refined in typecheck + okforeq[TMAP] = 1; // nil only; refined in typecheck + okforeq[TFUNC] = 1; // nil only; refined in typecheck + okforeq[TARRAY] = 1; // nil slice only; refined in typecheck + okforeq[TSTRUCT] = 1; // it's complicated; refined in typecheck okforcmp[TSTRING] = 1; diff --git a/src/cmd/gc/bisonerrors b/src/cmd/gc/bisonerrors index 5110f5350..0f865d086 100755 --- a/src/cmd/gc/bisonerrors +++ b/src/cmd/gc/bisonerrors @@ -46,24 +46,36 @@ bison && /^state 0/ { grammar = 0; states = 1 } states && /^state / { state = $2 } states { statetext[state] = statetext[state] $0 "\n" } -states && / shift, and go to state/ { +states && / shift/ { n = nshift[state]++ - shift[state,n] = $7 + if($0 ~ /and go to/) + shift[state,n] = $7 # GNU Bison + else + shift[state,n] = $3 # Plan 9 Yacc shifttoken[state,n] = $1 next } -states && / go to state/ { +states && / (go to|goto)/ { n = nshift[state]++ - shift[state,n] = $5 + if($0 ~ /go to/) + shift[state,n] = $5 # GNU Bison + else + shift[state,n] = $3 # Plan 9 Yacc shifttoken[state,n] = $1 next } -states && / reduce using rule/ { +states && / reduce/ { n = nreduce[state]++ - reduce[state,n] = $5 + if($0 ~ /reduce using rule/) + reduce[state,n] = $5 # GNU Bison + else + reduce[state,n] = $3 # Plan 9 yacc reducetoken[state,n] = $1 next -} +} + +# Skip over the summary information printed by Plan 9 yacc. +/nonterminals$/,/^maximum spread/ { next } # First // comment marks the beginning of the pattern file. /^\/\// { bison = 0; grammar = 0; state = 0 } @@ -96,7 +108,8 @@ $1 == "%" { if(found) continue for(j=0; j<nreduce[state]; j++) { - if(reducetoken[state,j] == tok || reducetoken[state,j] == "$default") { + t = reducetoken[state,j] + if(t == tok || t == "$default" || t == ".") { stack[nstack++] = state rule = reduce[state,j] nstack -= rulesize[rule] diff --git a/src/cmd/gc/bits.c b/src/cmd/gc/bits.c index f3b031cc3..c0fd4d85e 100644 --- a/src/cmd/gc/bits.c +++ b/src/cmd/gc/bits.c @@ -151,9 +151,9 @@ Qconv(Fmt *fp) else fmtprint(fp, " "); if(var[i].node == N || var[i].node->sym == S) - fmtprint(fp, "$%lld", var[i].offset); + fmtprint(fp, "$%d", i); else { - fmtprint(fp, var[i].node->sym->name); + fmtprint(fp, "%s", var[i].node->sym->name); if(var[i].offset != 0) fmtprint(fp, "%+lld", (vlong)var[i].offset); } diff --git a/src/cmd/gc/builtin.c.boot b/src/cmd/gc/builtin.c.boot index 190c56008..23d36964a 100644 --- a/src/cmd/gc/builtin.c.boot +++ b/src/cmd/gc/builtin.c.boot @@ -1,114 +1,123 @@ char *runtimeimport = "package runtime\n" "import runtime \"runtime\"\n" - "func \"\".new (? int32) *any\n" - "func \"\".panicindex ()\n" - "func \"\".panicslice ()\n" - "func \"\".throwreturn ()\n" - "func \"\".throwinit ()\n" - "func \"\".panicwrap (? string, ? string, ? string)\n" - "func \"\".panic (? interface { })\n" - "func \"\".recover (? *int32) interface { }\n" - "func \"\".printbool (? bool)\n" - "func \"\".printfloat (? float64)\n" - "func \"\".printint (? int64)\n" - "func \"\".printuint (? uint64)\n" - "func \"\".printcomplex (? complex128)\n" - "func \"\".printstring (? string)\n" - "func \"\".printpointer (? any)\n" - "func \"\".printiface (? any)\n" - "func \"\".printeface (? any)\n" - "func \"\".printslice (? any)\n" - "func \"\".printnl ()\n" - "func \"\".printsp ()\n" - "func \"\".goprintf ()\n" - "func \"\".concatstring ()\n" - "func \"\".append ()\n" - "func \"\".appendslice (typ *uint8, x any, y []any) any\n" - "func \"\".cmpstring (? string, ? string) int\n" - "func \"\".slicestring (? string, ? int, ? int) string\n" - "func \"\".slicestring1 (? string, ? int) string\n" - "func \"\".intstring (? int64) string\n" - "func \"\".slicebytetostring (? []uint8) string\n" - "func \"\".sliceinttostring (? []int) string\n" - "func \"\".stringtoslicebyte (? string) []uint8\n" - "func \"\".stringtosliceint (? string) []int\n" - "func \"\".stringiter (? string, ? int) int\n" - "func \"\".stringiter2 (? string, ? int) (retk int, retv int)\n" - "func \"\".slicecopy (to any, fr any, wid uint32) int\n" - "func \"\".slicestringcopy (to any, fr any) int\n" - "func \"\".convI2E (elem any) any\n" - "func \"\".convI2I (typ *uint8, elem any) any\n" - "func \"\".convT2E (typ *uint8, elem any) any\n" - "func \"\".convT2I (typ *uint8, typ2 *uint8, elem any) any\n" - "func \"\".assertE2E (typ *uint8, iface any) any\n" - "func \"\".assertE2E2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".assertE2I (typ *uint8, iface any) any\n" - "func \"\".assertE2I2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".assertE2T (typ *uint8, iface any) any\n" - "func \"\".assertE2T2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".assertI2E (typ *uint8, iface any) any\n" - "func \"\".assertI2E2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".assertI2I (typ *uint8, iface any) any\n" - "func \"\".assertI2I2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".assertI2T (typ *uint8, iface any) any\n" - "func \"\".assertI2T2 (typ *uint8, iface any) (ret any, ok bool)\n" - "func \"\".ifaceeq (i1 any, i2 any) bool\n" - "func \"\".efaceeq (i1 any, i2 any) bool\n" - "func \"\".ifacethash (i1 any) uint32\n" - "func \"\".efacethash (i1 any) uint32\n" - "func \"\".makemap (mapType *uint8, hint int64) map[any] any\n" - "func \"\".mapaccess1 (mapType *uint8, hmap map[any] any, key any) any\n" - "func \"\".mapaccess2 (mapType *uint8, hmap map[any] any, key any) (val any, pres bool)\n" - "func \"\".mapassign1 (mapType *uint8, hmap map[any] any, key any, val any)\n" - "func \"\".mapassign2 (mapType *uint8, hmap map[any] any, key any, val any, pres bool)\n" - "func \"\".mapiterinit (mapType *uint8, hmap map[any] any, hiter *any)\n" - "func \"\".mapiternext (hiter *any)\n" - "func \"\".mapiter1 (hiter *any) any\n" - "func \"\".mapiter2 (hiter *any) (key any, val any)\n" - "func \"\".makechan (chanType *uint8, hint int64) chan any\n" - "func \"\".chanrecv1 (chanType *uint8, hchan <-chan any) any\n" - "func \"\".chanrecv2 (chanType *uint8, hchan <-chan any) (elem any, received bool)\n" - "func \"\".chansend1 (chanType *uint8, hchan chan<- any, elem any)\n" - "func \"\".closechan (hchan any)\n" - "func \"\".selectnbsend (chanType *uint8, hchan chan<- any, elem any) bool\n" - "func \"\".selectnbrecv (chanType *uint8, elem *any, hchan <-chan any) bool\n" - "func \"\".selectnbrecv2 (chanType *uint8, elem *any, received *bool, hchan <-chan any) bool\n" - "func \"\".newselect (size int) *uint8\n" - "func \"\".selectsend (sel *uint8, hchan chan<- any, elem *any) bool\n" - "func \"\".selectrecv (sel *uint8, hchan <-chan any, elem *any) bool\n" - "func \"\".selectrecv2 (sel *uint8, hchan <-chan any, elem *any, received *bool) bool\n" - "func \"\".selectdefault (sel *uint8) bool\n" - "func \"\".selectgo (sel *uint8)\n" - "func \"\".block ()\n" - "func \"\".makeslice (typ *uint8, nel int64, cap int64) []any\n" - "func \"\".growslice (typ *uint8, old []any, n int64) []any\n" - "func \"\".sliceslice1 (old []any, lb uint64, width uint64) []any\n" - "func \"\".sliceslice (old []any, lb uint64, hb uint64, width uint64) []any\n" - "func \"\".slicearray (old *any, nel uint64, lb uint64, hb uint64, width uint64) []any\n" - "func \"\".closure ()\n" - "func \"\".int64div (? int64, ? int64) int64\n" - "func \"\".uint64div (? uint64, ? uint64) uint64\n" - "func \"\".int64mod (? int64, ? int64) int64\n" - "func \"\".uint64mod (? uint64, ? uint64) uint64\n" - "func \"\".float64toint64 (? float64) int64\n" - "func \"\".float64touint64 (? float64) uint64\n" - "func \"\".int64tofloat64 (? int64) float64\n" - "func \"\".uint64tofloat64 (? uint64) float64\n" - "func \"\".complex128div (num complex128, den complex128) complex128\n" + "func @\"\".new(@\"\".typ *byte) *any\n" + "func @\"\".panicindex()\n" + "func @\"\".panicslice()\n" + "func @\"\".throwreturn()\n" + "func @\"\".throwinit()\n" + "func @\"\".panicwrap(? string, ? string, ? string)\n" + "func @\"\".panic(? interface {})\n" + "func @\"\".recover(? *int32) interface {}\n" + "func @\"\".printbool(? bool)\n" + "func @\"\".printfloat(? float64)\n" + "func @\"\".printint(? int64)\n" + "func @\"\".printuint(? uint64)\n" + "func @\"\".printcomplex(? complex128)\n" + "func @\"\".printstring(? string)\n" + "func @\"\".printpointer(? any)\n" + "func @\"\".printiface(? any)\n" + "func @\"\".printeface(? any)\n" + "func @\"\".printslice(? any)\n" + "func @\"\".printnl()\n" + "func @\"\".printsp()\n" + "func @\"\".goprintf()\n" + "func @\"\".concatstring()\n" + "func @\"\".append()\n" + "func @\"\".appendslice(@\"\".typ *byte, @\"\".x any, @\"\".y []any) any\n" + "func @\"\".appendstr(@\"\".typ *byte, @\"\".x []byte, @\"\".y string) []byte\n" + "func @\"\".cmpstring(? string, ? string) int\n" + "func @\"\".slicestring(? string, ? int, ? int) string\n" + "func @\"\".slicestring1(? string, ? int) string\n" + "func @\"\".intstring(? int64) string\n" + "func @\"\".slicebytetostring(? []byte) string\n" + "func @\"\".slicerunetostring(? []rune) string\n" + "func @\"\".stringtoslicebyte(? string) []byte\n" + "func @\"\".stringtoslicerune(? string) []rune\n" + "func @\"\".stringiter(? string, ? int) int\n" + "func @\"\".stringiter2(? string, ? int) (@\"\".retk int, @\"\".retv rune)\n" + "func @\"\".copy(@\"\".to any, @\"\".fr any, @\"\".wid uint32) int\n" + "func @\"\".slicestringcopy(@\"\".to any, @\"\".fr any) int\n" + "func @\"\".convI2E(@\"\".elem any) any\n" + "func @\"\".convI2I(@\"\".typ *byte, @\"\".elem any) any\n" + "func @\"\".convT2E(@\"\".typ *byte, @\"\".elem any) any\n" + "func @\"\".convT2I(@\"\".typ *byte, @\"\".typ2 *byte, @\"\".elem any) any\n" + "func @\"\".assertE2E(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertE2E2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".assertE2I(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertE2I2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".assertE2T(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertE2T2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".assertI2E(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertI2E2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".assertI2I(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertI2I2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".assertI2T(@\"\".typ *byte, @\"\".iface any) any\n" + "func @\"\".assertI2T2(@\"\".typ *byte, @\"\".iface any) (@\"\".ret any, @\"\".ok bool)\n" + "func @\"\".ifaceeq(@\"\".i1 any, @\"\".i2 any) bool\n" + "func @\"\".efaceeq(@\"\".i1 any, @\"\".i2 any) bool\n" + "func @\"\".ifacethash(@\"\".i1 any) uint32\n" + "func @\"\".efacethash(@\"\".i1 any) uint32\n" + "func @\"\".equal(@\"\".typ *byte, @\"\".x1 any, @\"\".x2 any) bool\n" + "func @\"\".makemap(@\"\".mapType *byte, @\"\".hint int64) map[any]any\n" + "func @\"\".mapaccess1(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".key any) any\n" + "func @\"\".mapaccess2(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".key any) (@\"\".val any, @\"\".pres bool)\n" + "func @\"\".mapassign1(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".key any, @\"\".val any)\n" + "func @\"\".mapassign2(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".key any, @\"\".val any, @\"\".pres bool)\n" + "func @\"\".mapiterinit(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".hiter *any)\n" + "func @\"\".mapdelete(@\"\".mapType *byte, @\"\".hmap map[any]any, @\"\".key any)\n" + "func @\"\".mapiternext(@\"\".hiter *any)\n" + "func @\"\".mapiter1(@\"\".hiter *any) any\n" + "func @\"\".mapiter2(@\"\".hiter *any) (@\"\".key any, @\"\".val any)\n" + "func @\"\".makechan(@\"\".chanType *byte, @\"\".hint int64) chan any\n" + "func @\"\".chanrecv1(@\"\".chanType *byte, @\"\".hchan <-chan any) any\n" + "func @\"\".chanrecv2(@\"\".chanType *byte, @\"\".hchan <-chan any) (@\"\".elem any, @\"\".received bool)\n" + "func @\"\".chansend1(@\"\".chanType *byte, @\"\".hchan chan<- any, @\"\".elem any)\n" + "func @\"\".closechan(@\"\".hchan any)\n" + "func @\"\".selectnbsend(@\"\".chanType *byte, @\"\".hchan chan<- any, @\"\".elem any) bool\n" + "func @\"\".selectnbrecv(@\"\".chanType *byte, @\"\".elem *any, @\"\".hchan <-chan any) bool\n" + "func @\"\".selectnbrecv2(@\"\".chanType *byte, @\"\".elem *any, @\"\".received *bool, @\"\".hchan <-chan any) bool\n" + "func @\"\".newselect(@\"\".size int) *byte\n" + "func @\"\".selectsend(@\"\".sel *byte, @\"\".hchan chan<- any, @\"\".elem *any) bool\n" + "func @\"\".selectrecv(@\"\".sel *byte, @\"\".hchan <-chan any, @\"\".elem *any) bool\n" + "func @\"\".selectrecv2(@\"\".sel *byte, @\"\".hchan <-chan any, @\"\".elem *any, @\"\".received *bool) bool\n" + "func @\"\".selectdefault(@\"\".sel *byte) bool\n" + "func @\"\".selectgo(@\"\".sel *byte)\n" + "func @\"\".block()\n" + "func @\"\".makeslice(@\"\".typ *byte, @\"\".nel int64, @\"\".cap int64) []any\n" + "func @\"\".growslice(@\"\".typ *byte, @\"\".old []any, @\"\".n int64) []any\n" + "func @\"\".sliceslice1(@\"\".old []any, @\"\".lb uint64, @\"\".width uint64) []any\n" + "func @\"\".sliceslice(@\"\".old []any, @\"\".lb uint64, @\"\".hb uint64, @\"\".width uint64) []any\n" + "func @\"\".slicearray(@\"\".old *any, @\"\".nel uint64, @\"\".lb uint64, @\"\".hb uint64, @\"\".width uint64) []any\n" + "func @\"\".closure()\n" + "func @\"\".memequal(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".memequal8(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".memequal16(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".memequal32(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".memequal64(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".memequal128(@\"\".eq *bool, @\"\".size uintptr, @\"\".x *any, @\"\".y *any)\n" + "func @\"\".int64div(? int64, ? int64) int64\n" + "func @\"\".uint64div(? uint64, ? uint64) uint64\n" + "func @\"\".int64mod(? int64, ? int64) int64\n" + "func @\"\".uint64mod(? uint64, ? uint64) uint64\n" + "func @\"\".float64toint64(? float64) int64\n" + "func @\"\".float64touint64(? float64) uint64\n" + "func @\"\".int64tofloat64(? int64) float64\n" + "func @\"\".uint64tofloat64(? uint64) float64\n" + "func @\"\".complex128div(@\"\".num complex128, @\"\".den complex128) complex128\n" "\n" "$$\n"; char *unsafeimport = "package unsafe\n" "import runtime \"runtime\"\n" - "type \"\".Pointer uintptr\n" - "func \"\".Offsetof (? any) uintptr\n" - "func \"\".Sizeof (? any) uintptr\n" - "func \"\".Alignof (? any) uintptr\n" - "func \"\".Typeof (i interface { }) interface { }\n" - "func \"\".Reflect (i interface { }) (typ interface { }, addr \"\".Pointer)\n" - "func \"\".Unreflect (typ interface { }, addr \"\".Pointer) interface { }\n" - "func \"\".New (typ interface { }) \"\".Pointer\n" - "func \"\".NewArray (typ interface { }, n int) \"\".Pointer\n" + "type @\"\".Pointer uintptr\n" + "func @\"\".Offsetof(? any) uintptr\n" + "func @\"\".Sizeof(? any) uintptr\n" + "func @\"\".Alignof(? any) uintptr\n" + "func @\"\".Typeof(@\"\".i interface {}) interface {}\n" + "func @\"\".Reflect(@\"\".i interface {}) (@\"\".typ interface {}, @\"\".addr @\"\".Pointer)\n" + "func @\"\".Unreflect(@\"\".typ interface {}, @\"\".addr @\"\".Pointer) interface {}\n" + "func @\"\".New(@\"\".typ interface {}) @\"\".Pointer\n" + "func @\"\".NewArray(@\"\".typ interface {}, @\"\".n int) @\"\".Pointer\n" "\n" "$$\n"; diff --git a/src/cmd/gc/closure.c b/src/cmd/gc/closure.c index d29e8cbc2..fa44e40fa 100644 --- a/src/cmd/gc/closure.c +++ b/src/cmd/gc/closure.c @@ -192,6 +192,10 @@ walkclosure(Node *func, NodeList **init) Node *xtype, *xfunc, *call, *clos; NodeList *l, *in; + // no closure vars, don't bother wrapping + if(func->cvars == nil) + return makeclosure(func, init, 1)->nname; + /* * wrap body in external function * with extra closure parameters. diff --git a/src/cmd/gc/const.c b/src/cmd/gc/const.c index 135a8102e..01c4f15b3 100644 --- a/src/cmd/gc/const.c +++ b/src/cmd/gc/const.c @@ -108,7 +108,7 @@ convlit1(Node **np, Type *t, int explicit) if(t != T && t->etype == TIDEAL && n->val.ctype != CTINT) n->val = toint(n->val); if(t != T && !isint[t->etype]) { - yyerror("invalid operation: %#N (shift of type %T)", n, t); + yyerror("invalid operation: %N (shift of type %T)", n, t); t = T; } n->type = t; @@ -170,6 +170,7 @@ convlit1(Node **np, Type *t, int explicit) break; case CTINT: + case CTRUNE: case CTFLT: case CTCPLX: ct = n->val.ctype; @@ -179,6 +180,7 @@ convlit1(Node **np, Type *t, int explicit) goto bad; case CTCPLX: case CTFLT: + case CTRUNE: n->val = toint(n->val); // flowthrough case CTINT: @@ -192,6 +194,7 @@ convlit1(Node **np, Type *t, int explicit) goto bad; case CTCPLX: case CTINT: + case CTRUNE: n->val = toflt(n->val); // flowthrough case CTFLT: @@ -206,6 +209,7 @@ convlit1(Node **np, Type *t, int explicit) goto bad; case CTFLT: case CTINT: + case CTRUNE: n->val = tocplx(n->val); break; case CTCPLX: @@ -213,7 +217,7 @@ convlit1(Node **np, Type *t, int explicit) break; } } else - if(et == TSTRING && ct == CTINT && explicit) + if(et == TSTRING && (ct == CTINT || ct == CTRUNE) && explicit) n->val = tostr(n->val); else goto bad; @@ -224,7 +228,7 @@ convlit1(Node **np, Type *t, int explicit) bad: if(!n->diag) { - yyerror("cannot convert %#N to type %T", n, t); + yyerror("cannot convert %N to type %T", n, t); n->diag = 1; } if(isideal(n->type)) { @@ -243,6 +247,7 @@ copyval(Val v) switch(v.ctype) { case CTINT: + case CTRUNE: i = mal(sizeof(*i)); mpmovefixfix(i, v.u.xval); v.u.xval = i; @@ -269,6 +274,7 @@ tocplx(Val v) switch(v.ctype) { case CTINT: + case CTRUNE: c = mal(sizeof(*c)); mpmovefixflt(&c->real, v.u.xval); mpmovecflt(&c->imag, 0.0); @@ -293,6 +299,7 @@ toflt(Val v) switch(v.ctype) { case CTINT: + case CTRUNE: f = mal(sizeof(*f)); mpmovefixflt(f, v.u.xval); v.ctype = CTFLT; @@ -316,6 +323,9 @@ toint(Val v) Mpint *i; switch(v.ctype) { + case CTRUNE: + v.ctype = CTINT; + break; case CTFLT: i = mal(sizeof(*i)); if(mpmovefltfix(i, v.u.fval) < 0) @@ -345,6 +355,7 @@ overflow(Val v, Type *t) return; switch(v.ctype) { case CTINT: + case CTRUNE: if(!isint[t->etype]) fatal("overflow: %T integer constant", t); if(mpcmpfixfix(v.u.xval, minintval[t->etype]) < 0 || @@ -379,6 +390,7 @@ tostr(Val v) switch(v.ctype) { case CTINT: + case CTRUNE: if(mpcmpfixfix(v.u.xval, minintval[TINT]) < 0 || mpcmpfixfix(v.u.xval, maxintval[TINT]) > 0) yyerror("overflow in int -> string"); @@ -415,7 +427,12 @@ consttype(Node *n) int isconst(Node *n, int ct) { - return consttype(n) == ct; + int t; + + t = consttype(n); + // If the caller is asking for CTINT, allow CTRUNE too. + // Makes life easier for back ends. + return t == ct || (ct == CTINT && t == CTRUNE); } /* @@ -424,7 +441,7 @@ isconst(Node *n, int ct) void evconst(Node *n) { - Node *nl, *nr; + Node *nl, *nr, *norig; int32 len; Strlit *str; int wl, wr, lno, et; @@ -518,7 +535,8 @@ evconst(Node *n) n->right = nr; if(nr->type && (issigned[nr->type->etype] || !isint[nr->type->etype])) goto illegal; - nl->val = toint(nl->val); + if(nl->val.ctype != CTRUNE) + nl->val = toint(nl->val); nr->val = toint(nr->val); break; } @@ -540,6 +558,17 @@ evconst(Node *n) v = toflt(v); rv = toflt(rv); } + + // Rune and int turns into rune. + if(v.ctype == CTRUNE && rv.ctype == CTINT) + rv.ctype = CTRUNE; + if(v.ctype == CTINT && rv.ctype == CTRUNE) { + if(n->op == OLSH || n->op == ORSH) + rv.ctype = CTINT; + else + v.ctype = CTRUNE; + } + if(v.ctype != rv.ctype) { // Use of undefined name as constant? if((v.ctype == 0 || rv.ctype == 0) && nerrors > 0) @@ -559,15 +588,19 @@ evconst(Node *n) return; case TUP(OADD, CTINT): + case TUP(OADD, CTRUNE): mpaddfixfix(v.u.xval, rv.u.xval); break; case TUP(OSUB, CTINT): + case TUP(OSUB, CTRUNE): mpsubfixfix(v.u.xval, rv.u.xval); break; case TUP(OMUL, CTINT): + case TUP(OMUL, CTRUNE): mpmulfixfix(v.u.xval, rv.u.xval); break; case TUP(ODIV, CTINT): + case TUP(ODIV, CTRUNE): if(mpcmpfixc(rv.u.xval, 0) == 0) { yyerror("division by zero"); mpmovecfix(v.u.xval, 1); @@ -576,6 +609,7 @@ evconst(Node *n) mpdivfixfix(v.u.xval, rv.u.xval); break; case TUP(OMOD, CTINT): + case TUP(OMOD, CTRUNE): if(mpcmpfixc(rv.u.xval, 0) == 0) { yyerror("division by zero"); mpmovecfix(v.u.xval, 1); @@ -585,21 +619,27 @@ evconst(Node *n) break; case TUP(OLSH, CTINT): + case TUP(OLSH, CTRUNE): mplshfixfix(v.u.xval, rv.u.xval); break; case TUP(ORSH, CTINT): + case TUP(ORSH, CTRUNE): mprshfixfix(v.u.xval, rv.u.xval); break; case TUP(OOR, CTINT): + case TUP(OOR, CTRUNE): mporfixfix(v.u.xval, rv.u.xval); break; case TUP(OAND, CTINT): + case TUP(OAND, CTRUNE): mpandfixfix(v.u.xval, rv.u.xval); break; case TUP(OANDNOT, CTINT): + case TUP(OANDNOT, CTRUNE): mpandnotfixfix(v.u.xval, rv.u.xval); break; case TUP(OXOR, CTINT): + case TUP(OXOR, CTRUNE): mpxorfixfix(v.u.xval, rv.u.xval); break; @@ -649,26 +689,32 @@ evconst(Node *n) goto setfalse; case TUP(OEQ, CTINT): + case TUP(OEQ, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) == 0) goto settrue; goto setfalse; case TUP(ONE, CTINT): + case TUP(ONE, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) != 0) goto settrue; goto setfalse; case TUP(OLT, CTINT): + case TUP(OLT, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) < 0) goto settrue; goto setfalse; case TUP(OLE, CTINT): + case TUP(OLE, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) <= 0) goto settrue; goto setfalse; case TUP(OGE, CTINT): + case TUP(OGE, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) >= 0) goto settrue; goto setfalse; case TUP(OGT, CTINT): + case TUP(OGT, CTRUNE): if(mpcmpfixfix(v.u.xval, rv.u.xval) > 0) goto settrue; goto setfalse; @@ -786,17 +832,21 @@ unary: } // fall through case TUP(OCONV, CTINT): + case TUP(OCONV, CTRUNE): case TUP(OCONV, CTFLT): case TUP(OCONV, CTSTR): convlit1(&nl, n->type, 1); break; case TUP(OPLUS, CTINT): + case TUP(OPLUS, CTRUNE): break; case TUP(OMINUS, CTINT): + case TUP(OMINUS, CTRUNE): mpnegfix(v.u.xval); break; case TUP(OCOM, CTINT): + case TUP(OCOM, CTRUNE): et = Txxx; if(nl->type != T) et = nl->type->etype; @@ -842,8 +892,15 @@ unary: } ret: - // rewrite n in place. + if(n == n->orig) { + // duplicate node for n->orig. + norig = nod(OLITERAL, N, N); + *norig = *n; + } else + norig = n->orig; *n = *nl; + // restore value of n->orig. + n->orig = norig; n->val = v; // check range. @@ -882,6 +939,7 @@ nodlit(Val v) n->type = idealbool; break; case CTINT: + case CTRUNE: case CTFLT: case CTCPLX: n->type = types[TIDEAL]; @@ -939,7 +997,7 @@ defaultlit(Node **np, Type *t) defaultlit(&n->left, t); t = n->left->type; if(t != T && !isint[t->etype]) { - yyerror("invalid operation: %#N (shift of type %T)", n, t); + yyerror("invalid operation: %N (shift of type %T)", n, t); t = T; } n->type = t; @@ -991,7 +1049,7 @@ defaultlit(Node **np, Type *t) n->type = types[TSTRING]; break; } - yyerror("defaultlit: unknown literal: %#N", n); + yyerror("defaultlit: unknown literal: %N", n); break; case CTBOOL: n->type = types[TBOOL]; @@ -1001,6 +1059,9 @@ defaultlit(Node **np, Type *t) case CTINT: n->type = types[TINT]; goto num; + case CTRUNE: + n->type = runetype; + goto num; case CTFLT: n->type = types[TFLOAT64]; goto num; @@ -1065,6 +1126,13 @@ defaultlit2(Node **lp, Node **rp, int force) convlit(rp, types[TFLOAT64]); return; } + + if(isconst(l, CTRUNE) || isconst(r, CTRUNE)) { + convlit(lp, runetype); + convlit(rp, runetype); + return; + } + convlit(lp, types[TINT]); convlit(rp, types[TINT]); } @@ -1101,7 +1169,7 @@ cmpslit(Node *l, Node *r) int smallintconst(Node *n) { - if(n->op == OLITERAL && n->val.ctype == CTINT && n->type != T) + if(n->op == OLITERAL && isconst(n, CTINT) && n->type != T) switch(simtype[n->type->etype]) { case TINT8: case TUINT8: @@ -1203,6 +1271,7 @@ convconst(Node *con, Type *t, Val *val) default: fatal("convconst ctype=%d %lT", val->ctype, t); case CTINT: + case CTRUNE: i = mpgetfix(val->u.xval); break; case CTBOOL: diff --git a/src/cmd/gc/cplx.c b/src/cmd/gc/cplx.c index 52038e71c..dea7bc3bb 100644 --- a/src/cmd/gc/cplx.c +++ b/src/cmd/gc/cplx.c @@ -204,6 +204,8 @@ complexgen(Node *n, Node *res) case OIND: case ONAME: // PHEAP or PPARAMREF var case OCALLFUNC: + case OCALLMETH: + case OCALLINTER: igen(n, &n1, res); complexmove(&n1, res); regfree(&n1); diff --git a/src/cmd/gc/dcl.c b/src/cmd/gc/dcl.c index d8b89b4f3..66edab9b9 100644 --- a/src/cmd/gc/dcl.c +++ b/src/cmd/gc/dcl.c @@ -8,6 +8,7 @@ #include "y.tab.h" static void funcargs(Node*); +static void funcargs2(Type*); static int dflag(void) @@ -174,6 +175,11 @@ declare(Node *n, int ctxt) n->lineno = parserline(); s = n->sym; + + // kludgy: typecheckok means we're past parsing. Eg genwrapper may declare out of package names later. + if(importpkg == nil && !typecheckok && s->pkg != localpkg) + yyerror("cannot declare name %S", s); + gen = 0; if(ctxt == PEXTERN) { externdcl = list(externdcl, n); @@ -415,6 +421,7 @@ oldname(Sym *s) c->funcdepth = funcdepth; c->outer = n->closure; n->closure = c; + n->addrtaken = 1; c->closure = n; c->xoffset = 0; curfn->cvars = list(curfn->cvars, c); @@ -472,7 +479,7 @@ colasdefn(NodeList *left, Node *defn) if(isblank(n)) continue; if(!colasname(n)) { - yyerror("non-name %#N on left side of :=", n); + yyerror("non-name %N on left side of :=", n); nerr++; continue; } @@ -546,13 +553,6 @@ ifacedcl(Node *n) void funchdr(Node *n) { - - if(n->nname != N) { - n->nname->op = ONAME; - declare(n->nname, PFUNC); - n->nname->defn = n; - } - // change the declaration context from extern to auto if(funcdepth == 0 && dclcontext != PEXTERN) fatal("funchdr: dclcontext"); @@ -563,16 +563,19 @@ funchdr(Node *n) n->outer = curfn; curfn = n; + if(n->nname) funcargs(n->nname->ntype); - else + else if (n->ntype) funcargs(n->ntype); + else + funcargs2(n->type); } static void funcargs(Node *nt) { - Node *n; + Node *n, *nn; NodeList *l; int gen; @@ -581,11 +584,11 @@ funcargs(Node *nt) // declare the receiver and in arguments. // no n->defn because type checking of func header - // will fill in the types before we can demand them. + // will not fill in the types until later if(nt->left != N) { n = nt->left; if(n->op != ODCLFIELD) - fatal("funcargs1 %O", n->op); + fatal("funcargs receiver %O", n->op); if(n->left != N) { n->left->op = ONAME; n->left->ntype = n->right; @@ -595,7 +598,7 @@ funcargs(Node *nt) for(l=nt->list; l; l=l->next) { n = l->n; if(n->op != ODCLFIELD) - fatal("funcargs2 %O", n->op); + fatal("funcargs in %O", n->op); if(n->left != N) { n->left->op = ONAME; n->left->ntype = n->right; @@ -608,12 +611,16 @@ funcargs(Node *nt) for(l=nt->rlist; l; l=l->next) { n = l->n; if(n->op != ODCLFIELD) - fatal("funcargs3 %O", n->op); + fatal("funcargs out %O", n->op); if(n->left != N) { n->left->op = ONAME; n->left->ntype = n->right; if(isblank(n->left)) { // Give it a name so we can assign to it during return. + // preserve the original in ->orig + nn = nod(OXXX, N, N); + *nn = *n->left; + n->left = nn; snprint(namebuf, sizeof(namebuf), ".anon%d", gen++); n->left->sym = lookup(namebuf); } @@ -623,6 +630,48 @@ funcargs(Node *nt) } /* + * Same as funcargs, except run over an already constructed TFUNC. + * This happens during import, where the hidden_fndcl rule has + * used functype directly to parse the function's type. + */ +static void +funcargs2(Type *t) +{ + Type *ft; + Node *n; + + if(t->etype != TFUNC) + fatal("funcargs2 %T", t); + + if(t->thistuple) + for(ft=getthisx(t)->type; ft; ft=ft->down) { + if(!ft->nname || !ft->nname->sym) + continue; + n = ft->nname; // no need for newname(ft->nname->sym) + n->type = ft->type; + declare(n, PPARAM); + } + + if(t->intuple) + for(ft=getinargx(t)->type; ft; ft=ft->down) { + if(!ft->nname || !ft->nname->sym) + continue; + n = ft->nname; + n->type = ft->type; + declare(n, PPARAM); + } + + if(t->outtuple) + for(ft=getoutargx(t)->type; ft; ft=ft->down) { + if(!ft->nname || !ft->nname->sym) + continue; + n = ft->nname; + n->type = ft->type; + declare(n, PPARAMOUT); + } +} + +/* * finish the body. * called in auto-declaration context. * returns in extern-declaration context. @@ -649,7 +698,7 @@ typedcl0(Sym *s) { Node *n; - n = dclname(s); + n = newname(s); n->op = OTYPE; declare(n, dclcontext); return n; @@ -669,220 +718,265 @@ typedcl1(Node *n, Node *t, int local) } /* - * typedcl1 but during imports + * structs, functions, and methods. + * they don't belong here, but where do they belong? */ -void -typedcl2(Type *pt, Type *t) + +static void +checkembeddedtype(Type *t) { - Node *n; + if (t == T) + return; - // override declaration in unsafe.go for Pointer. - // there is no way in Go code to define unsafe.Pointer - // so we have to supply it. - if(incannedimport && - strcmp(importpkg->name, "unsafe") == 0 && - strcmp(pt->nod->sym->name, "Pointer") == 0) { - t = types[TUNSAFEPTR]; + if(t->sym == S && isptr[t->etype]) { + t = t->type; + if(t->etype == TINTER) + yyerror("embedded type cannot be a pointer to interface"); } + if(isptr[t->etype]) + yyerror("embedded type cannot be a pointer"); + else if(t->etype == TFORW && t->embedlineno == 0) + t->embedlineno = lineno; +} - if(pt->etype == TFORW) - goto ok; - if(!eqtype(pt->orig, t)) - yyerror("inconsistent definition for type %S during import\n\t%lT\n\t%lT", pt->sym, pt->orig, t); - return; +static Type* +structfield(Node *n) +{ + Type *f; + int lno; + + lno = lineno; + lineno = n->lineno; + + if(n->op != ODCLFIELD) + fatal("structfield: oops %N\n", n); -ok: - n = pt->nod; - copytype(pt->nod, t); - // unzero nod - pt->nod = n; + f = typ(TFIELD); + f->isddd = n->isddd; - pt->sym->lastlineno = parserline(); - declare(n, PEXTERN); + if(n->right != N) { + typecheck(&n->right, Etype); + n->type = n->right->type; + if(n->left != N) + n->left->type = n->type; + if(n->embedded) + checkembeddedtype(n->type); + } + n->right = N; + + f->type = n->type; + if(f->type == T) + f->broke = 1; + + switch(n->val.ctype) { + case CTSTR: + f->note = n->val.u.sval; + break; + default: + yyerror("field annotation must be string"); + // fallthrough + case CTxxx: + f->note = nil; + break; + } - checkwidth(pt); + if(n->left && n->left->op == ONAME) { + f->nname = n->left; + f->embedded = n->embedded; + f->sym = f->nname->sym; + } + + lineno = lno; + return f; } -/* - * structs, functions, and methods. - * they don't belong here, but where do they belong? - */ +static void +checkdupfields(Type *t, char* what) +{ + Type* t1; + int lno; + lno = lineno; + + for( ; t; t=t->down) + if(t->sym && t->nname && !isblank(t->nname)) + for(t1=t->down; t1; t1=t1->down) + if(t1->sym == t->sym) { + lineno = t->nname->lineno; + yyerror("duplicate %s %s", what, t->sym->name); + break; + } + + lineno = lno; +} /* - * turn a parsed struct into a type + * convert a parsed id/type list into + * a type for struct/interface/arglist */ -static Type** -stotype(NodeList *l, int et, Type **t, int funarg) +Type* +tostruct(NodeList *l) +{ + Type *t, *f, **tp; + t = typ(TSTRUCT); + + for(tp = &t->type; l; l=l->next) { + f = structfield(l->n); + + *tp = f; + tp = &f->down; + } + + for(f=t->type; f && !t->broke; f=f->down) + if(f->broke) + t->broke = 1; + + checkdupfields(t->type, "field"); + + if (!t->broke) + checkwidth(t); + + return t; +} + +static Type* +tofunargs(NodeList *l) +{ + Type *t, *f, **tp; + + t = typ(TSTRUCT); + t->funarg = 1; + + for(tp = &t->type; l; l=l->next) { + f = structfield(l->n); + f->funarg = 1; + + // esc.c needs to find f given a PPARAM to add the tag. + if(l->n->left && l->n->left->class == PPARAM) + l->n->left->paramfld = f; + + *tp = f; + tp = &f->down; + } + + for(f=t->type; f && !t->broke; f=f->down) + if(f->broke) + t->broke = 1; + + checkdupfields(t->type, "argument"); + return t; +} + +static Type* +interfacefield(Node *n) { - Type *f, *t1, *t2, **t0; - Strlit *note; + Type *f; int lno; - Node *n, *left; - char *what; - t0 = t; lno = lineno; - what = "field"; - if(et == TINTER) - what = "method"; + lineno = n->lineno; - for(; l; l=l->next) { - n = l->n; - lineno = n->lineno; + if(n->op != ODCLFIELD) + fatal("interfacefield: oops %N\n", n); - if(n->op != ODCLFIELD) - fatal("stotype: oops %N\n", n); - left = n->left; - if(funarg && isblank(left)) - left = N; - if(n->right != N) { - if(et == TINTER && left != N) { - // queue resolution of method type for later. - // right now all we need is the name list. - // avoids cycles for recursive interface types. - n->type = typ(TINTERMETH); - n->type->nname = n->right; - n->right = N; - left->type = n->type; - queuemethod(n); - } else { - typecheck(&n->right, Etype); - n->type = n->right->type; - if(n->type == T) - continue; - if(left != N) - left->type = n->type; - n->right = N; - if(n->embedded && n->type != T) { - t1 = n->type; - if(t1->sym == S && isptr[t1->etype]) { - t1 = t1->type; - if(t1->etype == TINTER) - yyerror("embedded type cannot be a pointer to interface"); - } - if(isptr[t1->etype]) - yyerror("embedded type cannot be a pointer"); - else if(t1->etype == TFORW && t1->embedlineno == 0) - t1->embedlineno = lineno; - } + if (n->val.ctype != CTxxx) + yyerror("interface method cannot have annotation"); + + f = typ(TFIELD); + f->isddd = n->isddd; + + if(n->right != N) { + if(n->left != N) { + // queue resolution of method type for later. + // right now all we need is the name list. + // avoids cycles for recursive interface types. + n->type = typ(TINTERMETH); + n->type->nname = n->right; + n->left->type = n->type; + queuemethod(n); + + if(n->left->op == ONAME) { + f->nname = n->left; + f->embedded = n->embedded; + f->sym = f->nname->sym; + if(importpkg && !exportname(f->sym->name)) + f->sym = pkglookup(f->sym->name, structpkg); } - } - if(n->type == T) { - // assume error already printed - continue; - } + } else { - switch(n->val.ctype) { - case CTSTR: - if(et != TSTRUCT) - yyerror("interface method cannot have annotation"); - note = n->val.u.sval; - break; - default: - if(et != TSTRUCT) - yyerror("interface method cannot have annotation"); - else - yyerror("field annotation must be string"); - case CTxxx: - note = nil; - break; - } + typecheck(&n->right, Etype); + n->type = n->right->type; - if(et == TINTER && left == N) { - // embedded interface - inline the methods - if(n->type->etype != TINTER) { - if(n->type->etype == TFORW) + if(n->embedded) + checkembeddedtype(n->type); + + if(n->type) + switch(n->type->etype) { + case TINTER: + break; + case TFORW: yyerror("interface type loop involving %T", n->type); - else + f->broke = 1; + break; + default: yyerror("interface contains embedded non-interface %T", n->type); - continue; - } - for(t1=n->type->type; t1!=T; t1=t1->down) { - f = typ(TFIELD); - f->type = t1->type; - f->width = BADWIDTH; - f->nname = newname(t1->sym); - f->sym = t1->sym; - for(t2=*t0; t2!=T; t2=t2->down) { - if(t2->sym == f->sym) { - yyerror("duplicate method %s", t2->sym->name); - break; - } - } - *t = f; - t = &f->down; - } - continue; - } - - f = typ(TFIELD); - f->type = n->type; - f->note = note; - f->width = BADWIDTH; - f->isddd = n->isddd; - - // esc.c needs to find f given a PPARAM to add the tag. - if(funarg && n->left && n->left->class == PPARAM) - n->left->paramfld = f; - - if(left != N && left->op == ONAME) { - f->nname = left; - f->embedded = n->embedded; - f->sym = f->nname->sym; - if(importpkg && !exportname(f->sym->name)) - f->sym = pkglookup(f->sym->name, structpkg); - if(f->sym && !isblank(f->nname)) { - for(t1=*t0; t1!=T; t1=t1->down) { - if(t1->sym == f->sym) { - yyerror("duplicate %s %s", what, t1->sym->name); - break; - } + f->broke = 1; + break; } - } } - - *t = f; - t = &f->down; } - *t = T; + n->right = N; + + f->type = n->type; + if(f->type == T) + f->broke = 1; + lineno = lno; - return t; + return f; } Type* -dostruct(NodeList *l, int et) +tointerface(NodeList *l) { - Type *t; - int funarg; + Type *t, *f, **tp, *t1; - /* - * convert a parsed id/type list into - * a type for struct/interface/arglist - */ + t = typ(TINTER); - funarg = 0; - if(et == TFUNC) { - funarg = 1; - et = TSTRUCT; - } - t = typ(et); - t->funarg = funarg; - stotype(l, et, &t->type, funarg); - if(t->type == T && l != nil) { - t->broke = 1; - return t; + tp = &t->type; + for(; l; l=l->next) { + f = interfacefield(l->n); + + if (l->n->left == N && f->type->etype == TINTER) { + // embedded interface, inline methods + for(t1=f->type->type; t1; t1=t1->down) { + f = typ(TFIELD); + f->type = t1->type; + f->broke = t1->broke; + f->sym = t1->sym; + if(f->sym) + f->nname = newname(f->sym); + *tp = f; + tp = &f->down; + } + } else { + *tp = f; + tp = &f->down; + } } - if(et == TINTER) - t = sortinter(t); - if(!funarg) - checkwidth(t); + + for(f=t->type; f && !t->broke; f=f->down) + if(f->broke) + t->broke = 1; + + checkdupfields(t->type, "method"); + t = sortinter(t); + checkwidth(t); + return t; } - Node* embedded(Sym *s) { @@ -899,7 +993,10 @@ embedded(Sym *s) *utfrune(name, CenterDot) = 0; } - n = newname(lookup(name)); + if(exportname(name) || s->pkg == builtinpkg) // old behaviour, tests pass, but is it correct? + n = newname(lookup(name)); + else + n = newname(pkglookup(name, s->pkg)); n = nod(ODCLFIELD, n, oldname(s)); n->embedded = 1; return n; @@ -964,6 +1061,17 @@ checkarglist(NodeList *all, int input) t = n; n = N; } + + // during import l->n->op is OKEY, but l->n->left->sym == S + // means it was a '?', not that it was + // a lone type This doesn't matter for the exported + // declarations, which are parsed by rules that don't + // use checkargs, but can happen for func literals in + // the inline bodies. + // TODO(rsc) this can go when typefmt case TFIELD in exportmode fmt.c prints _ instead of ? + if(importpkg && n->sym == S) + n = N; + if(n != N && n->sym == S) { t = n; n = N; @@ -1037,9 +1145,12 @@ functype(Node *this, NodeList *in, NodeList *out) rcvr = nil; if(this) rcvr = list1(this); - t->type = dostruct(rcvr, TFUNC); - t->type->down = dostruct(out, TFUNC); - t->type->down->down = dostruct(in, TFUNC); + t->type = tofunargs(rcvr); + t->type->down = tofunargs(out); + t->type->down->down = tofunargs(in); + + if (t->type->broke || t->type->down->broke || t->type->down->down->broke) + t->broke = 1; if(this) t->thistuple = 1; @@ -1085,9 +1196,9 @@ methodsym(Sym *nsym, Type *t0, int iface) suffix = "·i"; } if(t0->sym == S && isptr[t0->etype]) - p = smprint("(%#hT).%s%s", t0, nsym->name, suffix); + p = smprint("(%-hT).%s%s", t0, nsym->name, suffix); else - p = smprint("%#hT.%s%s", t0, nsym->name, suffix); + p = smprint("%-hT.%s%s", t0, nsym->name, suffix); s = pkglookup(p, s->pkg); free(p); return s; @@ -1121,11 +1232,16 @@ methodname1(Node *n, Node *t) } if(t->sym == S || isblank(n)) return newname(n->sym); + if(star) p = smprint("(%s%S).%S", star, t->sym, n->sym); else p = smprint("%S.%S", t->sym, n->sym); - n = newname(pkglookup(p, t->sym->pkg)); + + if(exportname(t->sym->name)) + n = newname(lookup(p)); + else + n = newname(pkglookup(p, t->sym->pkg)); free(p); return n; } @@ -1164,6 +1280,8 @@ addmethod(Sym *sf, Type *t, int local) t = t->type; } } + if(t->broke) // rely on typecheck having complained before + return; if(t != T) { if(t->sym == S) { yyerror("invalid receiver type %T (%T is an unnamed type)", pa, t); @@ -1185,8 +1303,6 @@ addmethod(Sym *sf, Type *t, int local) } pa = f; - if(importpkg && !exportname(sf->name)) - sf = pkglookup(sf->name, importpkg); n = nod(ODCLFIELD, newname(sf), N); n->type = t; @@ -1209,10 +1325,16 @@ addmethod(Sym *sf, Type *t, int local) return; } + f = structfield(n); + + // during import unexported method names should be in the type's package + if(importpkg && f->sym && !exportname(f->sym->name) && f->sym->pkg != structpkg) + fatal("imported method name %+S in wrong package %s\n", f->sym, structpkg->name); + if(d == T) - stotype(list1(n), 0, &pa->method, 0); + pa->method = f; else - stotype(list1(n), 0, &d->down, 0); + d->down = f; return; } @@ -1254,6 +1376,3 @@ funccompile(Node *n, int isclosure) funcdepth = 0; dclcontext = PEXTERN; } - - - diff --git a/src/cmd/gc/doc.go b/src/cmd/gc/doc.go index 5bb5e0e14..c704011ef 100644 --- a/src/cmd/gc/doc.go +++ b/src/cmd/gc/doc.go @@ -42,6 +42,8 @@ Flags: show entire file path when printing line numbers in errors -I dir1 -I dir2 add dir1 and dir2 to the list of paths to check for imported packages + -N + disable optimizations -S write assembly language text to standard output -u diff --git a/src/cmd/gc/esc.c b/src/cmd/gc/esc.c index cd1f9770e..7e20457d9 100644 --- a/src/cmd/gc/esc.c +++ b/src/cmd/gc/esc.c @@ -35,6 +35,8 @@ static void escfunc(Node *func); static void esclist(NodeList *l); static void esc(Node *n); +static void escloopdepthlist(NodeList *l); +static void escloopdepth(Node *n); static void escassign(Node *dst, Node *src); static void esccall(Node*); static void escflows(Node *dst, Node *src); @@ -62,6 +64,7 @@ escapes(void) NodeList *l; theSink.op = ONAME; + theSink.orig = &theSink; theSink.class = PEXTERN; theSink.sym = lookup(".sink"); theSink.escloopdepth = -1; @@ -88,7 +91,7 @@ escapes(void) if(debug['m']) { for(l=noesc; l; l=l->next) if(l->n->esc == EscNone) - warnl(l->n->lineno, "%S %#N does not escape", + warnl(l->n->lineno, "%S %hN does not escape", (l->n->curfn && l->n->curfn->nname) ? l->n->curfn->nname->sym : S, l->n); } @@ -138,11 +141,64 @@ escfunc(Node *func) escassign(curfn, n); } + escloopdepthlist(curfn->nbody); esclist(curfn->nbody); curfn = savefn; loopdepth = saveld; } +// Mark labels that have no backjumps to them as not increasing loopdepth. +// Walk hasn't generated (goto|label)->left->sym->label yet, so we'll cheat +// and set it to one of the following two. Then in esc we'll clear it again. +static Label looping; +static Label nonlooping; + +static void +escloopdepthlist(NodeList *l) +{ + for(; l; l=l->next) + escloopdepth(l->n); +} + +static void +escloopdepth(Node *n) +{ + if(n == N) + return; + + escloopdepthlist(n->ninit); + + switch(n->op) { + case OLABEL: + if(!n->left || !n->left->sym) + fatal("esc:label without label: %+N", n); + // Walk will complain about this label being already defined, but that's not until + // after escape analysis. in the future, maybe pull label & goto analysis out of walk and put before esc + // if(n->left->sym->label != nil) + // fatal("escape analysis messed up analyzing label: %+N", n); + n->left->sym->label = &nonlooping; + break; + case OGOTO: + if(!n->left || !n->left->sym) + fatal("esc:goto without label: %+N", n); + // If we come past one that's uninitialized, this must be a (harmless) forward jump + // but if it's set to nonlooping the label must have preceded this goto. + if(n->left->sym->label == &nonlooping) + n->left->sym->label = &looping; + break; + } + + escloopdepth(n->left); + escloopdepth(n->right); + escloopdepthlist(n->list); + escloopdepth(n->ntest); + escloopdepth(n->nincr); + escloopdepthlist(n->nbody); + escloopdepthlist(n->nelse); + escloopdepthlist(n->rlist); + +} + static void esclist(NodeList *l) { @@ -178,7 +234,7 @@ esc(Node *n) loopdepth--; if(debug['m'] > 1) - print("%L:[%d] %#S esc: %#N\n", lineno, loopdepth, + print("%L:[%d] %S esc: %N\n", lineno, loopdepth, (curfn && curfn->nname) ? curfn->nname->sym : S, n); switch(n->op) { @@ -188,9 +244,20 @@ esc(Node *n) n->left->escloopdepth = loopdepth; break; - case OLABEL: // TODO: new loop/scope only if there are backjumps to it. - loopdepth++; - break; + case OLABEL: + if(n->left->sym->label == &nonlooping) { + if(debug['m'] > 1) + print("%L:%N non-looping label\n", lineno, n); + } else if(n->left->sym->label == &looping) { + if(debug['m'] > 1) + print("%L: %N looping label\n", lineno, n); + loopdepth++; + } + // See case OLABEL in escloopdepth above + // else if(n->left->sym->label == nil) + // fatal("escape anaylysis missed or messed up a label: %+N", n); + + n->left->sym->label = nil; case ORANGE: // Everything but fixed array is a dereference. @@ -222,7 +289,6 @@ esc(Node *n) case OAS2RECV: // v, ok = <-ch case OAS2MAPR: // v, ok = m[k] case OAS2DOTTYPE: // v, ok = x.(type) - case OAS2MAPW: // m[k] = x, ok escassign(n->list->n, n->rlist->n); break; @@ -239,6 +305,7 @@ esc(Node *n) case OPROC: // go f(x) - f and x escape escassign(&theSink, n->left->left); + escassign(&theSink, n->left->right); // ODDDARG for call for(ll=n->left->list; ll; ll=ll->next) escassign(&theSink, ll->n); break; @@ -291,6 +358,14 @@ esc(Node *n) for(ll=n->list; ll; ll=ll->next) escassign(n, ll->n->right); break; + + case OPTRLIT: + n->esc = EscNone; // until proven otherwise + noesc = list(noesc, n); + n->escloopdepth = loopdepth; + // Contents make it to memory, lose track. + escassign(&theSink, n->left); + break; case OMAPLIT: n->esc = EscNone; // until proven otherwise @@ -331,7 +406,7 @@ escassign(Node *dst, Node *src) return; if(debug['m'] > 1) - print("%L:[%d] %#S escassign: %hN = %hN\n", lineno, loopdepth, + print("%L:[%d] %S escassign: %hN = %hN\n", lineno, loopdepth, (curfn && curfn->nname) ? curfn->nname->sym : S, dst, src); setlineno(dst); @@ -387,6 +462,7 @@ escassign(Node *dst, Node *src) case ONAME: case OPARAM: case ODDDARG: + case OPTRLIT: case OARRAYLIT: case OMAPLIT: case OSTRUCTLIT: @@ -394,10 +470,14 @@ escassign(Node *dst, Node *src) escflows(dst, src); break; + case ODOT: + // A non-pointer escaping from a struct does not concern us. + if(src->type && !haspointers(src->type)) + break; + // fallthrough case OCONV: case OCONVIFACE: case OCONVNOP: - case ODOT: case ODOTMETH: // treat recv.meth as a value with recv in it, only happens in ODEFER and OPROC // iface.method already leaks iface in esccall, no need to put in extra ODOTINTER edge here case ODOTTYPE: @@ -609,7 +689,7 @@ escflood(Node *dst) } if(debug['m']>1) - print("\nescflood:%d: dst %hN scope:%#S[%d]\n", walkgen, dst, + print("\nescflood:%d: dst %hN scope:%S[%d]\n", walkgen, dst, (dst->curfn && dst->curfn->nname) ? dst->curfn->nname->sym : S, dst->escloopdepth); @@ -630,7 +710,7 @@ escwalk(int level, Node *dst, Node *src) src->walkgen = walkgen; if(debug['m']>1) - print("escwalk: level:%d depth:%d %.*s %hN scope:%#S[%d]\n", + print("escwalk: level:%d depth:%d %.*s %hN scope:%S[%d]\n", level, pdepth, pdepth, "\t\t\t\t\t\t\t\t\t\t", src, (src->curfn && src->curfn->nname) ? src->curfn->nname->sym : S, src->escloopdepth); @@ -647,12 +727,13 @@ escwalk(int level, Node *dst, Node *src) } break; + case OPTRLIT: case OADDR: if(leaks) { src->esc = EscHeap; addrescapes(src->left); if(debug['m']) - warnl(src->lineno, "%#N escapes to heap", src); + warnl(src->lineno, "%hN escapes to heap", src); } escwalk(level-1, dst, src->left); break; @@ -671,7 +752,7 @@ escwalk(int level, Node *dst, Node *src) if(leaks) { src->esc = EscHeap; if(debug['m']) - warnl(src->lineno, "%#N escapes to heap", src); + warnl(src->lineno, "%hN escapes to heap", src); } break; diff --git a/src/cmd/gc/export.c b/src/cmd/gc/export.c index 421afda8b..05fdcbf32 100644 --- a/src/cmd/gc/export.c +++ b/src/cmd/gc/export.c @@ -7,11 +7,9 @@ #include "go.h" #include "y.tab.h" -static void dumpsym(Sym*); -static void dumpexporttype(Sym*); -static void dumpexportvar(Sym*); -static void dumpexportconst(Sym*); +static void dumpexporttype(Type *t); +// Mark n's symbol as exported void exportsym(Node *n) { @@ -27,6 +25,7 @@ exportsym(Node *n) exportlist = list(exportlist, n); } +// Mark n's symbol as package-local static void packagesym(Node *n) { @@ -79,7 +78,7 @@ dumppkg(Pkg *p) { char *suffix; - if(p == nil || p == localpkg || p->exported) + if(p == nil || p == localpkg || p->exported || p == builtinpkg) return; p->exported = 1; suffix = ""; @@ -88,25 +87,84 @@ dumppkg(Pkg *p) Bprint(bout, "\timport %s \"%Z\"%s\n", p->name, p->path, suffix); } +// Look for anything we need for the inline body +static void reexportdep(Node *n); static void -dumpprereq(Type *t) +reexportdeplist(NodeList *ll) { - if(t == T) - return; + for(; ll ;ll=ll->next) + reexportdep(ll->n); +} + +static void +reexportdep(Node *n) +{ + Type *t; - if(t->printed || t == types[t->etype]) + if(!n) return; - t->printed = 1; - if(t->sym != S) { - dumppkg(t->sym->pkg); - if(t->etype != TFIELD) - dumpsym(t->sym); +// print("reexportdep %+hN\n", n); + switch(n->op) { + case ONAME: + switch(n->class&~PHEAP) { + case PFUNC: + // methods will be printed along with their type + if(!n->type || n->type->thistuple > 0) + break; + // fallthrough + case PEXTERN: + if (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg) + exportlist = list(exportlist, n); + } + break; + + + case OLITERAL: + t = n->type; + if(t != types[n->type->etype] && t != idealbool && t != idealstring) { + if(isptr[t->etype]) + t = t->type; + if (t && t->sym && t->sym->def && t->sym->pkg != localpkg && t->sym->pkg != builtinpkg) { +// print("reexport literal type %+hN\n", t->sym->def); + exportlist = list(exportlist, t->sym->def); + } + } + // fallthrough + case OTYPE: + if (n->sym && n->sym->pkg != localpkg && n->sym->pkg != builtinpkg) + exportlist = list(exportlist, n); + break; + + // for operations that need a type when rendered, put the type on the export list. + case OCONV: + case OCONVIFACE: + case OCONVNOP: + case ODOTTYPE: + case OSTRUCTLIT: + case OPTRLIT: + t = n->type; + if(!t->sym && t->type) + t = t->type; + if (t && t->sym && t->sym->def && t->sym->pkg != localpkg && t->sym->pkg != builtinpkg) { +// print("reexport convnop %+hN\n", t->sym->def); + exportlist = list(exportlist, t->sym->def); + } + break; } - dumpprereq(t->type); - dumpprereq(t->down); + + reexportdep(n->left); + reexportdep(n->right); + reexportdeplist(n->list); + reexportdeplist(n->rlist); + reexportdeplist(n->ninit); + reexportdep(n->ntest); + reexportdep(n->nincr); + reexportdeplist(n->nbody); + reexportdeplist(n->nelse); } + static void dumpexportconst(Sym *s) { @@ -119,37 +177,12 @@ dumpexportconst(Sym *s) fatal("dumpexportconst: oconst nil: %S", s); t = n->type; // may or may not be specified - if(t != T) - dumpprereq(t); + dumpexporttype(t); - Bprint(bout, "\t"); - Bprint(bout, "const %#S", s); if(t != T && !isideal(t)) - Bprint(bout, " %#T", t); - Bprint(bout, " = "); - - switch(n->val.ctype) { - default: - fatal("dumpexportconst: unknown ctype: %S %d", s, n->val.ctype); - case CTINT: - Bprint(bout, "%B\n", n->val.u.xval); - break; - case CTBOOL: - if(n->val.u.bval) - Bprint(bout, "true\n"); - else - Bprint(bout, "false\n"); - break; - case CTFLT: - Bprint(bout, "%F\n", n->val.u.fval); - break; - case CTCPLX: - Bprint(bout, "(%F+%F)\n", &n->val.u.cval->real, &n->val.u.cval->imag); - break; - case CTSTR: - Bprint(bout, "\"%Z\"\n", n->val.u.sval); - break; - } + Bprint(bout, "\tconst %#S %#T = %#V\n", s, t, &n->val); + else + Bprint(bout, "\tconst %#S = %#V\n", s, &n->val); } static void @@ -159,38 +192,27 @@ dumpexportvar(Sym *s) Type *t; n = s->def; - typecheck(&n, Erv); + typecheck(&n, Erv|Ecall); if(n == N || n->type == T) { yyerror("variable exported but not defined: %S", s); return; } t = n->type; - dumpprereq(t); - - Bprint(bout, "\t"); - if(t->etype == TFUNC && n->class == PFUNC) - Bprint(bout, "func %#S %#hhT", s, t); - else - Bprint(bout, "var %#S %#T", s, t); - Bprint(bout, "\n"); -} - -static void -dumpexporttype(Sym *s) -{ - Type *t; - - t = s->def->type; - dumpprereq(t); - Bprint(bout, "\t"); - switch (t->etype) { - case TFORW: - yyerror("export of incomplete type %T", t); - return; - } - if(Bprint(bout, "type %#T %l#T\n", t, t) < 0) - fatal("Bprint failed for %T", t); + dumpexporttype(t); + + if(t->etype == TFUNC && n->class == PFUNC) { + if (n->inl) { + // when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet. + // currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package + if(debug['l'] < 2) + typecheckinl(n); + Bprint(bout, "\tfunc %#S%#hT { %#H }\n", s, t, n->inl); + reexportdeplist(n->inl); + } else + Bprint(bout, "\tfunc %#S%#hT\n", s, t); + } else + Bprint(bout, "\tvar %#S %#T\n", s, t); } static int @@ -204,12 +226,57 @@ methcmp(const void *va, const void *vb) } static void -dumpsym(Sym *s) +dumpexporttype(Type *t) { - Type *f, *t; + Type *f; Type **m; int i, n; + if(t == T) + return; + if(t->printed || t == types[t->etype] || t == bytetype || t == runetype || t == errortype) + return; + t->printed = 1; + + if(t->sym != S && t->etype != TFIELD) + dumppkg(t->sym->pkg); + + dumpexporttype(t->type); + dumpexporttype(t->down); + + if (t->sym == S || t->etype == TFIELD) + return; + + n = 0; + for(f=t->method; f!=T; f=f->down) { + dumpexporttype(f); + n++; + } + + m = mal(n*sizeof m[0]); + i = 0; + for(f=t->method; f!=T; f=f->down) + m[i++] = f; + qsort(m, n, sizeof m[0], methcmp); + + Bprint(bout, "\ttype %#S %#lT\n", t->sym, t); + for(i=0; i<n; i++) { + f = m[i]; + if (f->type->nname && f->type->nname->inl) { // nname was set by caninl + // when lazily typechecking inlined bodies, some re-exported ones may not have been typechecked yet. + // currently that can leave unresolved ONONAMEs in import-dot-ed packages in the wrong package + if(debug['l'] < 2) + typecheckinl(f->type->nname); + Bprint(bout, "\tfunc (%#T) %#hhS%#hT { %#H }\n", getthisx(f->type)->type, f->sym, f->type, f->type->nname->inl); + reexportdeplist(f->type->nname->inl); + } else + Bprint(bout, "\tfunc (%#T) %#hhS%#hT\n", getthisx(f->type)->type, f->sym, f->type); + } +} + +static void +dumpsym(Sym *s) +{ if(s->flags & SymExported) return; s->flags |= SymExported; @@ -218,56 +285,31 @@ dumpsym(Sym *s) yyerror("unknown export symbol: %S", s); return; } - +// print("dumpsym %O %+S\n", s->def->op, s); dumppkg(s->pkg); switch(s->def->op) { default: yyerror("unexpected export symbol: %O %S", s->def->op, s); break; + case OLITERAL: dumpexportconst(s); break; + case OTYPE: - t = s->def->type; - n = 0; - for(f=t->method; f!=T; f=f->down) { - dumpprereq(f); - n++; - } - m = mal(n*sizeof m[0]); - i = 0; - for(f=t->method; f!=T; f=f->down) - m[i++] = f; - qsort(m, n, sizeof m[0], methcmp); - - dumpexporttype(s); - for(i=0; i<n; i++) { - f = m[i]; - Bprint(bout, "\tfunc (%#T) %hS %#hhT\n", - f->type->type->type, f->sym, f->type); - } + if(s->def->type->etype == TFORW) + yyerror("export of incomplete type %S", s); + else + dumpexporttype(s->def->type); break; + case ONAME: dumpexportvar(s); break; } } -static void -dumptype(Type *t) -{ - // no need to re-dump type if already exported - if(t->printed) - return; - - // no need to dump type if it's not ours (was imported) - if(t->sym != S && t->sym->def == typenod(t) && !t->local) - return; - - Bprint(bout, "type %#T %l#T\n", t, t); -} - void dumpexport(void) { @@ -277,10 +319,7 @@ dumpexport(void) lno = lineno; - packagequotes = 1; - Bprint(bout, "\n$$ // exports\n"); - - Bprint(bout, " package %s", localpkg->name); + Bprint(bout, "\n$$ // exports\n package %s", localpkg->name); if(safemode) Bprint(bout, " safe"); Bprint(bout, "\n"); @@ -295,15 +334,7 @@ dumpexport(void) dumpsym(l->n->sym); } - Bprint(bout, "\n$$ // local types\n"); - - for(l=typelist; l; l=l->next) { - lineno = l->n->lineno; - dumptype(l->n->type); - } - - Bprint(bout, "\n$$\n"); - packagequotes = 0; + Bprint(bout, "\n$$ // local types\n\n$$\n"); // 6l expects this. (see ld/go.c) lineno = lno; } @@ -346,16 +377,29 @@ pkgtype(Sym *s) s->def = typenod(t); } if(s->def->type == T) - yyerror("pkgtype %lS", s); + yyerror("pkgtype %S", s); return s->def->type; } -static int -mypackage(Sym *s) +void +importimport(Sym *s, Strlit *z) { - // we import all definitions for runtime. - // lowercase ones can only be used by the compiler. - return s->pkg == localpkg || s->pkg == runtimepkg; + // Informational: record package name + // associated with import path, for use in + // human-readable messages. + Pkg *p; + + p = mkpkg(z); + if(p->name == nil) { + p->name = s->name; + pkglookup(s->name, nil)->npkg++; + } else if(strcmp(p->name, s->name) != 0) + yyerror("conflicting names %s and %s for package \"%Z\"", p->name, s->name, p->path); + + if(!incannedimport && myimportpath != nil && strcmp(z->s, myimportpath) == 0) { + yyerror("import \"%Z\": package depends on \"%Z\" (import cycle)", importpkg->path, z); + errorexit(); + } } void @@ -363,19 +407,17 @@ importconst(Sym *s, Type *t, Node *n) { Node *n1; - if(!exportname(s->name) && !mypackage(s)) - return; importsym(s, OLITERAL); convlit(&n, t); - if(s->def != N) { - // TODO: check if already the same. + + if(s->def != N) // TODO: check if already the same. return; - } if(n->op != OLITERAL) { yyerror("expression must be a constant"); return; } + if(n->sym != S) { n1 = nod(OXXX, N, N); *n1 = *n; @@ -389,23 +431,19 @@ importconst(Sym *s, Type *t, Node *n) } void -importvar(Sym *s, Type *t, int ctxt) +importvar(Sym *s, Type *t) { Node *n; - if(!exportname(s->name) && !initname(s->name) && !mypackage(s)) - return; - importsym(s, ONAME); if(s->def != N && s->def->op == ONAME) { if(eqtype(t, s->def->type)) return; - yyerror("inconsistent definition for var %S during import\n\t%T\n\t%T", - s, s->def->type, t); + yyerror("inconsistent definition for var %S during import\n\t%T\n\t%T", s, s->def->type, t); } n = newname(s); n->type = t; - declare(n, ctxt); + declare(n, PEXTERN); if(debug['E']) print("import var %S %lT\n", s, t); @@ -414,17 +452,27 @@ importvar(Sym *s, Type *t, int ctxt) void importtype(Type *pt, Type *t) { - if(pt != T && t != T) - typedcl2(pt, t); + Node *n; + + // override declaration in unsafe.go for Pointer. + // there is no way in Go code to define unsafe.Pointer + // so we have to supply it. + if(incannedimport && + strcmp(importpkg->name, "unsafe") == 0 && + strcmp(pt->nod->sym->name, "Pointer") == 0) { + t = types[TUNSAFEPTR]; + } + + if(pt->etype == TFORW) { + n = pt->nod; + copytype(pt->nod, t); + pt->nod = n; // unzero nod + pt->sym->lastlineno = parserline(); + declare(n, PEXTERN); + checkwidth(pt); + } else if(!eqtype(pt->orig, t)) + yyerror("inconsistent definition for type %S during import\n\t%lT\n\t%lT", pt->sym, pt, t); if(debug['E']) print("import type %T %lT\n", pt, t); } - -void -importmethod(Sym *s, Type *t) -{ - checkwidth(t); - addmethod(s, t, 0); -} - diff --git a/src/cmd/gc/fmt.c b/src/cmd/gc/fmt.c new file mode 100644 index 000000000..31b0a623f --- /dev/null +++ b/src/cmd/gc/fmt.c @@ -0,0 +1,1626 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +#include <u.h> +#include <libc.h> +#include "go.h" +#include "opnames.h" + +// +// Format conversions +// %L int Line numbers +// +// %E int etype values (aka 'Kind') +// +// %O int Node Opcodes +// Flags: "%#O": print go syntax. (automatic unless fmtmode == FDbg) +// +// %J Node* Node details +// Flags: "%hJ" supresses things not relevant until walk. +// +// %V Val* Constant values +// +// %S Sym* Symbols +// Flags: +,- #: mode (see below) +// "%hS" unqualified identifier in any mode +// "%hhS" in export mode: unqualified identifier if exported, qualified if not +// +// %T Type* Types +// Flags: +,- #: mode (see below) +// 'l' definition instead of name. +// 'h' omit "func" and receiver in function types +// 'u' (only in -/Sym mode) print type identifiers wit package name instead of prefix. +// +// %N Node* Nodes +// Flags: +,- #: mode (see below) +// 'h' (only in +/debug mode) suppress recursion +// 'l' (only in Error mode) print "foo (type Bar)" +// +// %H NodeList* NodeLists +// Flags: those of %N +// ',' separate items with ',' instead of ';' +// +// %Z Strlit* String literals +// +// In mparith1.c: +// %B Mpint* Big integers +// %F Mpflt* Big floats +// +// %S, %T and %N obey use the following flags to set the format mode: +enum { + FErr, // error mode (default) + FDbg, // "%+N" debug mode + FExp, // "%#N" export mode + FTypeId, // "%-N" turning-types-into-symbols-mode: identical types give identical strings +}; +static int fmtmode; +static int fmtpkgpfx; // %uT stickyness +// +// E.g. for %S: %+S %#S %-S print an identifier properly qualified for debug/export/internal mode. +// +// The mode flags +, - and # are sticky, meaning they persist through +// recursions of %N, %T and %S, but not the h and l flags. The u flag is +// sticky only on %T recursions and only used in %-/Sym mode. + +// +// Useful format combinations: +// +// %+N %+H multiline recursive debug dump of node/nodelist +// %+hN %+hH non recursive debug dump +// +// %#N %#T export format +// %#lT type definition instead of name +// %#hT omit"func" and receiver in function signature +// +// %lN "foo (type Bar)" for error messages +// +// %-T type identifiers +// %-hT type identifiers without "func" and arg names in type signatures (methodsym) +// %-uT type identifiers with package name instead of prefix (typesym, dcommontype, typehash) +// + + +static int +setfmode(unsigned long *flags) +{ + int fm; + + fm = fmtmode; + if(*flags & FmtSign) + fmtmode = FDbg; + else if(*flags & FmtSharp) + fmtmode = FExp; + else if(*flags & FmtLeft) + fmtmode = FTypeId; + + *flags &= ~(FmtSharp|FmtLeft|FmtSign); + return fm; +} + +// Fmt "%L": Linenumbers +static int +Lconv(Fmt *fp) +{ + struct + { + Hist* incl; /* start of this include file */ + int32 idel; /* delta line number to apply to include */ + Hist* line; /* start of this #line directive */ + int32 ldel; /* delta line number to apply to #line */ + } a[HISTSZ]; + int32 lno, d; + int i, n; + Hist *h; + + lno = va_arg(fp->args, int32); + + n = 0; + for(h=hist; h!=H; h=h->link) { + if(h->offset < 0) + continue; + if(lno < h->line) + break; + if(h->name) { + if(h->offset > 0) { + // #line directive + if(n > 0 && n < HISTSZ) { + a[n-1].line = h; + a[n-1].ldel = h->line - h->offset + 1; + } + } else { + // beginning of file + if(n < HISTSZ) { + a[n].incl = h; + a[n].idel = h->line; + a[n].line = 0; + } + n++; + } + continue; + } + n--; + if(n > 0 && n < HISTSZ) { + d = h->line - a[n].incl->line; + a[n-1].ldel += d; + a[n-1].idel += d; + } + } + + if(n > HISTSZ) + n = HISTSZ; + + for(i=n-1; i>=0; i--) { + if(i != n-1) { + if(fp->flags & ~(FmtWidth|FmtPrec)) + break; + fmtprint(fp, " "); + } + if(debug['L'] || (fp->flags&FmtLong)) + fmtprint(fp, "%s/", pathname); + if(a[i].line) + fmtprint(fp, "%s:%d[%s:%d]", + a[i].line->name, lno-a[i].ldel+1, + a[i].incl->name, lno-a[i].idel+1); + else + fmtprint(fp, "%s:%d", + a[i].incl->name, lno-a[i].idel+1); + lno = a[i].incl->line - 1; // now print out start of this file + } + if(n == 0) + fmtprint(fp, "<epoch>"); + + return 0; +} + +static char* +goopnames[] = +{ + [OADDR] = "&", + [OADD] = "+", + [OADDSTR] = "+", + [OANDAND] = "&&", + [OANDNOT] = "&^", + [OAND] = "&", + [OAPPEND] = "append", + [OAS] = "=", + [OAS2] = "=", + [OBREAK] = "break", + [OCALL] = "function call", // not actual syntax + [OCAP] = "cap", + [OCASE] = "case", + [OCLOSE] = "close", + [OCOMPLEX] = "complex", + [OCOM] = "^", + [OCONTINUE] = "continue", + [OCOPY] = "copy", + [ODEC] = "--", + [ODELETE] = "delete", + [ODEFER] = "defer", + [ODIV] = "/", + [OEQ] = "==", + [OFALL] = "fallthrough", + [OFOR] = "for", + [OGE] = ">=", + [OGOTO] = "goto", + [OGT] = ">", + [OIF] = "if", + [OIMAG] = "imag", + [OINC] = "++", + [OIND] = "*", + [OLEN] = "len", + [OLE] = "<=", + [OLSH] = "<<", + [OLT] = "<", + [OMAKE] = "make", + [OMINUS] = "-", + [OMOD] = "%", + [OMUL] = "*", + [ONEW] = "new", + [ONE] = "!=", + [ONOT] = "!", + [OOROR] = "||", + [OOR] = "|", + [OPANIC] = "panic", + [OPLUS] = "+", + [OPRINTN] = "println", + [OPRINT] = "print", + [ORANGE] = "range", + [OREAL] = "real", + [ORECV] = "<-", + [ORETURN] = "return", + [ORSH] = ">>", + [OSELECT] = "select", + [OSEND] = "<-", + [OSUB] = "-", + [OSWITCH] = "switch", + [OXOR] = "^", +}; + +// Fmt "%O": Node opcodes +static int +Oconv(Fmt *fp) +{ + int o; + + o = va_arg(fp->args, int); + if((fp->flags & FmtSharp) || fmtmode != FDbg) + if(o >= 0 && o < nelem(goopnames) && goopnames[o] != nil) + return fmtstrcpy(fp, goopnames[o]); + + if(o >= 0 && o < nelem(opnames) && opnames[o] != nil) + return fmtstrcpy(fp, opnames[o]); + + return fmtprint(fp, "O-%d", o); +} + +static const char* classnames[] = { + "Pxxx", + "PEXTERN", + "PAUTO", + "PPARAM", + "PPARAMOUT", + "PPARAMREF", + "PFUNC", +}; + +// Fmt "%J": Node details. +static int +Jconv(Fmt *fp) +{ + Node *n; + char *s; + int c; + + n = va_arg(fp->args, Node*); + + c = fp->flags&FmtShort; + + if(!c && n->ullman != 0) + fmtprint(fp, " u(%d)", n->ullman); + + if(!c && n->addable != 0) + fmtprint(fp, " a(%d)", n->addable); + + if(!c && n->vargen != 0) + fmtprint(fp, " g(%d)", n->vargen); + + if(n->lineno != 0) + fmtprint(fp, " l(%d)", n->lineno); + + if(!c && n->xoffset != BADWIDTH) + fmtprint(fp, " x(%lld%+d)", n->xoffset, n->stkdelta); + + if(n->class != 0) { + s = ""; + if(n->class & PHEAP) s = ",heap"; + if((n->class & ~PHEAP) < nelem(classnames)) + fmtprint(fp, " class(%s%s)", classnames[n->class&~PHEAP], s); + else + fmtprint(fp, " class(%d?%s)", n->class&~PHEAP, s); + } + + if(n->colas != 0) + fmtprint(fp, " colas(%d)", n->colas); + + if(n->funcdepth != 0) + fmtprint(fp, " f(%d)", n->funcdepth); + + switch(n->esc) { + case EscUnknown: + break; + case EscHeap: + fmtprint(fp, " esc(h)"); + break; + case EscScope: + fmtprint(fp, " esc(s)"); + break; + case EscNone: + fmtprint(fp, " esc(no)"); + break; + case EscNever: + if(!c) + fmtprint(fp, " esc(N)"); + break; + default: + fmtprint(fp, " esc(%d)", n->esc); + break; + } + + if(n->escloopdepth) + fmtprint(fp, " ld(%d)", n->escloopdepth); + + if(!c && n->typecheck != 0) + fmtprint(fp, " tc(%d)", n->typecheck); + + if(!c && n->dodata != 0) + fmtprint(fp, " dd(%d)", n->dodata); + + if(n->isddd != 0) + fmtprint(fp, " isddd(%d)", n->isddd); + + if(n->implicit != 0) + fmtprint(fp, " implicit(%d)", n->implicit); + + if(n->embedded != 0) + fmtprint(fp, " embedded(%d)", n->embedded); + + if(!c && n->used != 0) + fmtprint(fp, " used(%d)", n->used); + return 0; +} + +// Fmt "%V": Values +static int +Vconv(Fmt *fp) +{ + Val *v; + vlong x; + + v = va_arg(fp->args, Val*); + + switch(v->ctype) { + case CTINT: + return fmtprint(fp, "%B", v->u.xval); + case CTRUNE: + x = mpgetfix(v->u.xval); + if(' ' <= x && x < 0x80 && x != '\\' && x != '\'') + return fmtprint(fp, "'%c'", (int)x); + if(0 <= x && x < (1<<16)) + return fmtprint(fp, "'\\u%04ux'", (int)x); + if(0 <= x && x <= Runemax) + return fmtprint(fp, "'\\U%08llux'", x); + return fmtprint(fp, "('\\x00' + %B)", v->u.xval); + case CTFLT: + if((fp->flags & FmtSharp) || fmtmode == FExp) + return fmtprint(fp, "%F", v->u.fval); + return fmtprint(fp, "%#F", v->u.fval); + case CTCPLX: + if((fp->flags & FmtSharp) || fmtmode == FExp) + return fmtprint(fp, "(%F+%F)", &v->u.cval->real, &v->u.cval->imag); + return fmtprint(fp, "(%#F + %#Fi)", &v->u.cval->real, &v->u.cval->imag); + case CTSTR: + return fmtprint(fp, "\"%Z\"", v->u.sval); + case CTBOOL: + if( v->u.bval) + return fmtstrcpy(fp, "true"); + return fmtstrcpy(fp, "false"); + case CTNIL: + return fmtstrcpy(fp, "nil"); + } + return fmtprint(fp, "<%d>", v->ctype); +} + +// Fmt "%Z": escaped string literals +static int +Zconv(Fmt *fp) +{ + Rune r; + Strlit *sp; + char *s, *se; + int n; + + sp = va_arg(fp->args, Strlit*); + if(sp == nil) + return fmtstrcpy(fp, "<nil>"); + + s = sp->s; + se = s + sp->len; + while(s < se) { + n = chartorune(&r, s); + s += n; + switch(r) { + case Runeerror: + if(n == 1) { + fmtprint(fp, "\\x%02x", (uchar)*(s-1)); + break; + } + // fall through + default: + if(r < ' ') { + fmtprint(fp, "\\x%02x", r); + break; + } + fmtrune(fp, r); + break; + case '\t': + fmtstrcpy(fp, "\\t"); + break; + case '\n': + fmtstrcpy(fp, "\\n"); + break; + case '\"': + case '\\': + fmtrune(fp, '\\'); + fmtrune(fp, r); + break; + } + } + return 0; +} + +/* +s%,%,\n%g +s%\n+%\n%g +s%^[ ]*T%%g +s%,.*%%g +s%.+% [T&] = "&",%g +s%^ ........*\]%&~%g +s%~ %%g +*/ + +static char* +etnames[] = +{ + [TINT] = "INT", + [TUINT] = "UINT", + [TINT8] = "INT8", + [TUINT8] = "UINT8", + [TINT16] = "INT16", + [TUINT16] = "UINT16", + [TINT32] = "INT32", + [TUINT32] = "UINT32", + [TINT64] = "INT64", + [TUINT64] = "UINT64", + [TUINTPTR] = "UINTPTR", + [TFLOAT32] = "FLOAT32", + [TFLOAT64] = "FLOAT64", + [TCOMPLEX64] = "COMPLEX64", + [TCOMPLEX128] = "COMPLEX128", + [TBOOL] = "BOOL", + [TPTR32] = "PTR32", + [TPTR64] = "PTR64", + [TFUNC] = "FUNC", + [TARRAY] = "ARRAY", + [TSTRUCT] = "STRUCT", + [TCHAN] = "CHAN", + [TMAP] = "MAP", + [TINTER] = "INTER", + [TFORW] = "FORW", + [TFIELD] = "FIELD", + [TSTRING] = "STRING", + [TANY] = "ANY", +}; + +// Fmt "%E": etype +static int +Econv(Fmt *fp) +{ + int et; + + et = va_arg(fp->args, int); + if(et >= 0 && et < nelem(etnames) && etnames[et] != nil) + return fmtstrcpy(fp, etnames[et]); + return fmtprint(fp, "E-%d", et); +} + +// Fmt "%S": syms +static int +symfmt(Fmt *fp, Sym *s) +{ + char *p; + + if(s->pkg && !(fp->flags&FmtShort)) { + switch(fmtmode) { + case FErr: // This is for the user + if(s->pkg == localpkg) + return fmtstrcpy(fp, s->name); + // If the name was used by multiple packages, display the full path, + if(s->pkg->name && pkglookup(s->pkg->name, nil)->npkg > 1) + return fmtprint(fp, "\"%Z\".%s", s->pkg->path, s->name); + return fmtprint(fp, "%s.%s", s->pkg->name, s->name); + case FDbg: + return fmtprint(fp, "%s.%s", s->pkg->name, s->name); + case FTypeId: + if(fp->flags&FmtUnsigned) + return fmtprint(fp, "%s.%s", s->pkg->name, s->name); // dcommontype, typehash + return fmtprint(fp, "%s.%s", s->pkg->prefix, s->name); // (methodsym), typesym, weaksym + case FExp: + if(s->pkg != builtinpkg) + return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, s->name); + } + } + + if(fp->flags&FmtByte) { // FmtByte (hh) implies FmtShort (h) + // skip leading "type." in method name + p = utfrrune(s->name, '.'); + if(p) + p++; + else + p = s->name; + + // exportname needs to see the name without the prefix too. + if((fmtmode == FExp && !exportname(p)) || fmtmode == FDbg) + return fmtprint(fp, "@\"%Z\".%s", s->pkg->path, p); + + return fmtstrcpy(fp, p); + } + + return fmtstrcpy(fp, s->name); +} + +static char* +basicnames[] = +{ + [TINT] = "int", + [TUINT] = "uint", + [TINT8] = "int8", + [TUINT8] = "uint8", + [TINT16] = "int16", + [TUINT16] = "uint16", + [TINT32] = "int32", + [TUINT32] = "uint32", + [TINT64] = "int64", + [TUINT64] = "uint64", + [TUINTPTR] = "uintptr", + [TFLOAT32] = "float32", + [TFLOAT64] = "float64", + [TCOMPLEX64] = "complex64", + [TCOMPLEX128] = "complex128", + [TBOOL] = "bool", + [TANY] = "any", + [TSTRING] = "string", + [TNIL] = "nil", + [TIDEAL] = "ideal", + [TBLANK] = "blank", +}; + +static int +typefmt(Fmt *fp, Type *t) +{ + Type *t1; + Sym *s; + + if(t == T) + return fmtstrcpy(fp, "<T>"); + + if (t == bytetype || t == runetype) { + // in %-T mode collapse rune and byte with their originals. + if(fmtmode != FTypeId) + return fmtprint(fp, "%hS", t->sym); + t = types[t->etype]; + } + + if(t == errortype) + return fmtstrcpy(fp, "error"); + + // Unless the 'l' flag was specified, if the type has a name, just print that name. + if(!(fp->flags&FmtLong) && t->sym && t->etype != TFIELD && t != types[t->etype]) { + switch(fmtmode) { + case FTypeId: + if(fp->flags&FmtShort) + return fmtprint(fp, "%hS", t->sym); + if(fp->flags&FmtUnsigned) + return fmtprint(fp, "%uS", t->sym); + // fallthrough + case FExp: + if(t->sym->pkg == localpkg && t->vargen) + return fmtprint(fp, "%S·%d", t->sym, t->vargen); + break; + } + return fmtprint(fp, "%S", t->sym); + } + + if(t->etype < nelem(basicnames) && basicnames[t->etype] != nil) { + if(fmtmode == FErr && (t == idealbool || t == idealstring)) + fmtstrcpy(fp, "ideal "); + return fmtstrcpy(fp, basicnames[t->etype]); + } + + if(fmtmode == FDbg) + fmtprint(fp, "%E-", t->etype); + + switch(t->etype) { + case TPTR32: + case TPTR64: + if(fmtmode == FTypeId && (fp->flags&FmtShort)) + return fmtprint(fp, "*%hT", t->type); + return fmtprint(fp, "*%T", t->type); + + case TARRAY: + if(t->bound >= 0) + return fmtprint(fp, "[%d]%T", (int)t->bound, t->type); + if(t->bound == -100) + return fmtprint(fp, "[...]%T", t->type); + return fmtprint(fp, "[]%T", t->type); + + case TCHAN: + switch(t->chan) { + case Crecv: + return fmtprint(fp, "<-chan %T", t->type); + case Csend: + return fmtprint(fp, "chan<- %T", t->type); + } + + if(t->type != T && t->type->etype == TCHAN && t->type->sym == S && t->type->chan == Crecv) + return fmtprint(fp, "chan (%T)", t->type); + return fmtprint(fp, "chan %T", t->type); + + case TMAP: + return fmtprint(fp, "map[%T]%T", t->down, t->type); + + case TINTER: + fmtstrcpy(fp, "interface {"); + for(t1=t->type; t1!=T; t1=t1->down) + if(exportname(t1->sym->name)) { + if(t1->down) + fmtprint(fp, " %hS%hT;", t1->sym, t1->type); + else + fmtprint(fp, " %hS%hT ", t1->sym, t1->type); + } else { + // non-exported method names must be qualified + if(t1->down) + fmtprint(fp, " %uS%hT;", t1->sym, t1->type); + else + fmtprint(fp, " %uS%hT ", t1->sym, t1->type); + } + fmtstrcpy(fp, "}"); + return 0; + + case TFUNC: + if(fp->flags & FmtShort) { + fmtprint(fp, "%T", getinargx(t)); + } else { + if(t->thistuple) + fmtprint(fp, "method%T func%T", getthisx(t), getinargx(t)); + else + fmtprint(fp, "func%T", getinargx(t)); + } + switch(t->outtuple) { + case 0: + break; + case 1: + if(fmtmode != FExp) { + fmtprint(fp, " %T", getoutargx(t)->type->type); // struct->field->field's type + break; + } + default: + fmtprint(fp, " %T", getoutargx(t)); + break; + } + return 0; + + case TSTRUCT: + if(t->funarg) { + fmtstrcpy(fp, "("); + if(fmtmode == FTypeId || fmtmode == FErr) { // no argument names on function signature, and no "noescape" tags + for(t1=t->type; t1!=T; t1=t1->down) + if(t1->down) + fmtprint(fp, "%hT, ", t1); + else + fmtprint(fp, "%hT", t1); + } else { + for(t1=t->type; t1!=T; t1=t1->down) + if(t1->down) + fmtprint(fp, "%T, ", t1); + else + fmtprint(fp, "%T", t1); + } + fmtstrcpy(fp, ")"); + } else { + fmtstrcpy(fp, "struct {"); + for(t1=t->type; t1!=T; t1=t1->down) + if(t1->down) + fmtprint(fp, " %lT;", t1); + else + fmtprint(fp, " %lT ", t1); + fmtstrcpy(fp, "}"); + } + return 0; + + case TFIELD: + if(!(fp->flags&FmtShort)) { + s = t->sym; + // Take the name from the original, lest we substituted it with .anon%d + if (t->nname && (fmtmode == FErr || fmtmode == FExp)) + s = t->nname->orig->sym; + + if(s != S && !t->embedded) { + if(fp->flags&FmtLong) + fmtprint(fp, "%hhS ", s); // qualify non-exported names (used on structs, not on funarg) + else + fmtprint(fp, "%S ", s); + } else if(fmtmode == FExp) { + // TODO(rsc) this breaks on the eliding of unused arguments in the backend + // when this is fixed, the special case in dcl.c checkarglist can go. + //if(t->funarg) + // fmtstrcpy(fp, "_ "); + //else + fmtstrcpy(fp, "? "); + } + } + + if(t->isddd) + fmtprint(fp, "...%T", t->type->type); + else + fmtprint(fp, "%T", t->type); + + if(!(fp->flags&FmtShort) && t->note) + fmtprint(fp, " \"%Z\"", t->note); + return 0; + + case TFORW: + if(t->sym) + return fmtprint(fp, "undefined %S", t->sym); + return fmtstrcpy(fp, "undefined"); + + case TUNSAFEPTR: + if(fmtmode == FExp) + return fmtprint(fp, "@\"unsafe\".Pointer"); + return fmtprint(fp, "unsafe.Pointer"); + } + + if(fmtmode == FExp) + fatal("missing %E case during export", t->etype); + // Don't know how to handle - fall back to detailed prints. + return fmtprint(fp, "%E <%S> %T", t->etype, t->sym, t->type); +} + +// Statements which may be rendered with a simplestmt as init. +static int +stmtwithinit(int op) +{ + switch(op) { + case OIF: + case OFOR: + case OSWITCH: + return 1; + } + return 0; +} + +static int +stmtfmt(Fmt *f, Node *n) +{ + int complexinit, simpleinit, extrablock; + + // some statements allow for an init, but at most one, + // but we may have an arbitrary number added, eg by typecheck + // and inlining. If it doesn't fit the syntax, emit an enclosing + // block starting with the init statements. + + // if we can just say "for" n->ninit; ... then do so + simpleinit = n->ninit && !n->ninit->next && !n->ninit->n->ninit && stmtwithinit(n->op); + // otherwise, print the inits as separate statements + complexinit = n->ninit && !simpleinit && (fmtmode != FErr); + // but if it was for if/for/switch, put in an extra surrounding block to limit the scope + extrablock = complexinit && stmtwithinit(n->op); + + if(extrablock) + fmtstrcpy(f, "{"); + + if(complexinit) + fmtprint(f, " %H; ", n->ninit); + + switch(n->op){ + case ODCL: + fmtprint(f, "var %S %T", n->left->sym, n->left->type); + break; + + case ODCLFIELD: + if(n->left) + fmtprint(f, "%N %N", n->left, n->right); + else + fmtprint(f, "%N", n->right); + break; + + case OAS: + if(n->colas && !complexinit) + fmtprint(f, "%N := %N", n->left, n->right); + else + fmtprint(f, "%N = %N", n->left, n->right); + break; + + case OASOP: + fmtprint(f, "%N %#O= %N", n->left, n->etype, n->right); + break; + + case OAS2: + if(n->colas && !complexinit) { + fmtprint(f, "%,H := %,H", n->list, n->rlist); + break; + } + // fallthrough + case OAS2DOTTYPE: + case OAS2FUNC: + case OAS2MAPR: + case OAS2RECV: + fmtprint(f, "%,H = %,H", n->list, n->rlist); + break; + + case ORETURN: + fmtprint(f, "return %,H", n->list); + break; + + case OPROC: + fmtprint(f, "go %N", n->left); + break; + + case ODEFER: + fmtprint(f, "defer %N", n->left); + break; + + case OIF: + if(simpleinit) + fmtprint(f, "if %N; %N { %H }", n->ninit->n, n->ntest, n->nbody); + else + fmtprint(f, "if %N { %H }", n->ntest, n->nbody); + if(n->nelse) + fmtprint(f, " else { %H }", n->nelse); + break; + + case OFOR: + if(fmtmode == FErr) { // TODO maybe only if FmtShort, same below + fmtstrcpy(f, "for loop"); + break; + } + + fmtstrcpy(f, "for"); + if(simpleinit) + fmtprint(f, " %N;", n->ninit->n); + else if(n->nincr) + fmtstrcpy(f, " ;"); + + if(n->ntest) + fmtprint(f, " %N", n->ntest); + + if(n->nincr) + fmtprint(f, "; %N", n->nincr); + else if(simpleinit) + fmtstrcpy(f, ";"); + + + fmtprint(f, " { %H }", n->nbody); + break; + + case ORANGE: + if(fmtmode == FErr) { + fmtstrcpy(f, "for loop"); + break; + } + + fmtprint(f, "for %,H = range %N { %H }", n->list, n->right, n->nbody); + break; + + case OSELECT: + case OSWITCH: + if(fmtmode == FErr) { + fmtprint(f, "%O statement", n->op); + break; + } + + fmtprint(f, "%#O", n->op); + if(simpleinit) + fmtprint(f, " %N;", n->ninit->n); + if(n->ntest) + fmtprint(f, "%N", n->ntest); + + fmtprint(f, " { %H }", n->list); + break; + + case OCASE: + case OXCASE: + if(n->list) + fmtprint(f, "case %,H: %H", n->list, n->nbody); + else + fmtprint(f, "default: %H", n->nbody); + break; + + case OBREAK: + case OCONTINUE: + case OGOTO: + case OFALL: + case OXFALL: + if(n->left) + fmtprint(f, "%#O %N", n->op, n->left); + else + fmtprint(f, "%#O", n->op); + break; + + case OEMPTY: + break; + + case OLABEL: + fmtprint(f, "%N: ", n->left); + break; + + } + + if(extrablock) + fmtstrcpy(f, "}"); + + return 0; +} + + +static int opprec[] = { + [OAPPEND] = 8, + [OARRAYBYTESTR] = 8, + [OARRAYLIT] = 8, + [OARRAYRUNESTR] = 8, + [OCALLFUNC] = 8, + [OCALLINTER] = 8, + [OCALLMETH] = 8, + [OCALL] = 8, + [OCAP] = 8, + [OCLOSE] = 8, + [OCONVIFACE] = 8, + [OCONVNOP] = 8, + [OCONV] = 8, + [OCOPY] = 8, + [ODELETE] = 8, + [OLEN] = 8, + [OLITERAL] = 8, + [OMAKESLICE] = 8, + [OMAKE] = 8, + [OMAPLIT] = 8, + [ONAME] = 8, + [ONEW] = 8, + [ONONAME] = 8, + [OPACK] = 8, + [OPANIC] = 8, + [OPAREN] = 8, + [OPRINTN] = 8, + [OPRINT] = 8, + [ORECV] = 8, + [ORUNESTR] = 8, + [OSTRARRAYBYTE] = 8, + [OSTRARRAYRUNE] = 8, + [OSTRUCTLIT] = 8, + [OTARRAY] = 8, + [OTCHAN] = 8, + [OTFUNC] = 8, + [OTINTER] = 8, + [OTMAP] = 8, + [OTPAREN] = 8, + [OTSTRUCT] = 8, + + [OINDEXMAP] = 8, + [OINDEX] = 8, + [OSLICE] = 8, + [OSLICESTR] = 8, + [OSLICEARR] = 8, + [ODOTINTER] = 8, + [ODOTMETH] = 8, + [ODOTPTR] = 8, + [ODOTTYPE2] = 8, + [ODOTTYPE] = 8, + [ODOT] = 8, + [OXDOT] = 8, + + [OPLUS] = 7, + [ONOT] = 7, + [OCOM] = 7, + [OMINUS] = 7, + [OADDR] = 7, + [OIND] = 7, + + [OMUL] = 6, + [ODIV] = 6, + [OMOD] = 6, + [OLSH] = 6, + [ORSH] = 6, + [OAND] = 6, + [OANDNOT] = 6, + + [OADD] = 5, + [OSUB] = 5, + [OOR] = 5, + [OXOR] = 5, + + [OEQ] = 4, + [OLT] = 4, + [OLE] = 4, + [OGE] = 4, + [OGT] = 4, + [ONE] = 4, + [OCMPSTR] = 4, + [OCMPIFACE] = 4, + + [OSEND] = 3, + [OANDAND] = 2, + [OOROR] = 1, + + // Statements handled by stmtfmt + [OAS] = -1, + [OAS2] = -1, + [OAS2DOTTYPE] = -1, + [OAS2FUNC] = -1, + [OAS2MAPR] = -1, + [OAS2RECV] = -1, + [OASOP] = -1, + [OBREAK] = -1, + [OCASE] = -1, + [OCONTINUE] = -1, + [ODCL] = -1, + [ODCLFIELD] = -1, + [ODEFER] = -1, + [OEMPTY] = -1, + [OFALL] = -1, + [OFOR] = -1, + [OIF] = -1, + [OLABEL] = -1, + [OPROC] = -1, + [ORANGE] = -1, + [ORETURN] = -1, + [OSELECT] = -1, + [OSWITCH] = -1, + [OXCASE] = -1, + [OXFALL] = -1, + + [OEND] = 0 +}; + +static int +exprfmt(Fmt *f, Node *n, int prec) +{ + int nprec; + NodeList *l; + Type *t; + + while(n && n->implicit) + n = n->left; + + if(n == N) + return fmtstrcpy(f, "<N>"); + + nprec = opprec[n->op]; + if(n->op == OTYPE && n->sym != S) + nprec = 8; + + if(prec > nprec) + return fmtprint(f, "(%N)", n); + + switch(n->op) { + case OPAREN: + return fmtprint(f, "(%N)", n->left); + + case ODDDARG: + return fmtprint(f, "... argument"); + + case OREGISTER: + return fmtprint(f, "%R", n->val.u.reg); + + case OLITERAL: // this is a bit of a mess + if(fmtmode == FErr && n->sym != S) + return fmtprint(f, "%S", n->sym); + if(n->val.ctype == CTNIL) + n = n->orig; // if this node was a nil decorated with at type, print the original naked nil + if(n->type != types[n->type->etype] && n->type != idealbool && n->type != idealstring) { + // Need parens when type begins with what might + // be misinterpreted as a unary operator: * or <-. + if(isptr[n->type->etype] || (n->type->etype == TCHAN && n->type->chan == Crecv)) + return fmtprint(f, "(%T)(%V)", n->type, &n->val); + else + return fmtprint(f, "%T(%V)", n->type, &n->val); + } + return fmtprint(f, "%V", &n->val); + + case ONAME: + case OPACK: + case ONONAME: + return fmtprint(f, "%S", n->sym); + + case OTYPE: + if(n->type == T && n->sym != S) + return fmtprint(f, "%S", n->sym); + return fmtprint(f, "%T", n->type); + + case OTARRAY: + if(n->left) + return fmtprint(f, "[]%N", n->left); + return fmtprint(f, "[]%N", n->right); // happens before typecheck + + case OTPAREN: + return fmtprint(f, "(%N)", n->left); + + case OTMAP: + return fmtprint(f, "map[%N]%N", n->left, n->right); + + case OTCHAN: + switch(n->etype) { + case Crecv: + return fmtprint(f, "<-chan %N", n->left); + case Csend: + return fmtprint(f, "chan<- %N", n->left); + default: + if(n->left != N && n->left->op == TCHAN && n->left->sym == S && n->left->etype == Crecv) + return fmtprint(f, "chan (%N)", n->left); + else + return fmtprint(f, "chan %N", n->left); + } + + case OTSTRUCT: + return fmtprint(f, "<struct>"); + + case OTINTER: + return fmtprint(f, "<inter>"); + + case OTFUNC: + return fmtprint(f, "<func>"); + + case OCLOSURE: + if(fmtmode == FErr) + return fmtstrcpy(f, "func literal"); + return fmtprint(f, "%T { %H }", n->type, n->nbody); + + case OCOMPLIT: + if(fmtmode == FErr) + return fmtstrcpy(f, "composite literal"); + return fmtprint(f, "%N{ %,H }", n->right, n->list); + + case OPTRLIT: + return fmtprint(f, "&%N", n->left); + + case OSTRUCTLIT: + if (fmtmode == FExp) { // requires special handling of field names + fmtprint(f, "%T{", n->type); + for(l=n->list; l; l=l->next) { + // another special case: if n->left is an embedded field of builtin type, + // it needs to be non-qualified. Can't figure that out in %S, so do it here + if(l->n->left->type->embedded) { + t = l->n->left->type->type; + if(t->sym == S) + t = t->type; + fmtprint(f, " %T:%N", t, l->n->right); + } else + fmtprint(f, " %hhS:%N", l->n->left->sym, l->n->right); + + if(l->next) + fmtstrcpy(f, ","); + else + fmtstrcpy(f, " "); + } + return fmtstrcpy(f, "}"); + } + // fallthrough + + case OARRAYLIT: + case OMAPLIT: + if(fmtmode == FErr) + return fmtprint(f, "%T literal", n->type); + return fmtprint(f, "%T{ %,H }", n->type, n->list); + + case OKEY: + if(n->left && n->right) + return fmtprint(f, "%N:%N", n->left, n->right); + if(!n->left && n->right) + return fmtprint(f, ":%N", n->right); + if(n->left && !n->right) + return fmtprint(f, "%N:", n->left); + return fmtstrcpy(f, ":"); + + case OXDOT: + case ODOT: + case ODOTPTR: + case ODOTINTER: + case ODOTMETH: + exprfmt(f, n->left, nprec); + if(n->right == N || n->right->sym == S) + fmtstrcpy(f, ".<nil>"); + return fmtprint(f, ".%hhS", n->right->sym); + + case ODOTTYPE: + case ODOTTYPE2: + exprfmt(f, n->left, nprec); + if(n->right != N) + return fmtprint(f, ".(%N)", n->right); + return fmtprint(f, ".(%T)", n->type); + + case OINDEX: + case OINDEXMAP: + case OSLICE: + case OSLICESTR: + case OSLICEARR: + exprfmt(f, n->left, nprec); + return fmtprint(f, "[%N]", n->right); + + case OCOPY: + case OCOMPLEX: + return fmtprint(f, "%#O(%N, %N)", n->op, n->left, n->right); + + case OCONV: + case OCONVIFACE: + case OCONVNOP: + case OARRAYBYTESTR: + case OARRAYRUNESTR: + case OSTRARRAYBYTE: + case OSTRARRAYRUNE: + case ORUNESTR: + if(n->type == T || n->type->sym == S) + return fmtprint(f, "(%T)(%N)", n->type, n->left); + if(n->left) + return fmtprint(f, "%T(%N)", n->type, n->left); + return fmtprint(f, "%T(%,H)", n->type, n->list); + + case OREAL: + case OIMAG: + case OAPPEND: + case OCAP: + case OCLOSE: + case ODELETE: + case OLEN: + case OMAKE: + case ONEW: + case OPANIC: + case OPRINT: + case OPRINTN: + if(n->left) + return fmtprint(f, "%#O(%N)", n->op, n->left); + if(n->isddd) + return fmtprint(f, "%#O(%,H...)", n->op, n->list); + return fmtprint(f, "%#O(%,H)", n->op, n->list); + + case OCALL: + case OCALLFUNC: + case OCALLINTER: + case OCALLMETH: + exprfmt(f, n->left, nprec); + if(n->isddd) + return fmtprint(f, "(%,H...)", n->list); + return fmtprint(f, "(%,H)", n->list); + + case OMAKEMAP: + case OMAKECHAN: + case OMAKESLICE: + if(n->list) // pre-typecheck + return fmtprint(f, "make(%T, %,H)", n->type, n->list); + if(n->right) + return fmtprint(f, "make(%T, %N, %N)", n->type, n->left, n->right); + if(n->left) + return fmtprint(f, "make(%T, %N)", n->type, n->left); + return fmtprint(f, "make(%T)", n->type); + + // Unary + case OPLUS: + case OMINUS: + case OADDR: + case OCOM: + case OIND: + case ONOT: + case ORECV: + if(n->left->op == n->op) + fmtprint(f, "%#O ", n->op); + else + fmtprint(f, "%#O", n->op); + return exprfmt(f, n->left, nprec+1); + + // Binary + case OADD: + case OADDSTR: + case OAND: + case OANDAND: + case OANDNOT: + case ODIV: + case OEQ: + case OGE: + case OGT: + case OLE: + case OLT: + case OLSH: + case OMOD: + case OMUL: + case ONE: + case OOR: + case OOROR: + case ORSH: + case OSEND: + case OSUB: + case OXOR: + exprfmt(f, n->left, nprec); + fmtprint(f, " %#O ", n->op); + exprfmt(f, n->right, nprec+1); + return 0; + + case OCMPSTR: + case OCMPIFACE: + exprfmt(f, n->left, nprec); + fmtprint(f, " %#O ", n->etype); + exprfmt(f, n->right, nprec+1); + return 0; + } + + return fmtprint(f, "<node %O>", n->op); +} + +static int +nodefmt(Fmt *f, Node *n) +{ + Type *t; + + t = n->type; + if(n->orig == N) { + n->orig = n; + fatal("node with no orig %N", n); + } + + // we almost always want the original, except in export mode for literals + // this saves the importer some work, and avoids us having to redo some + // special casing for package unsafe + if(fmtmode != FExp || n->op != OLITERAL) + n = n->orig; + + if(f->flags&FmtLong && t != T) { + if(t->etype == TNIL) + return fmtprint(f, "nil"); + else + return fmtprint(f, "%N (type %T)", n, t); + } + + // TODO inlining produces expressions with ninits. we can't print these yet. + + if(opprec[n->op] < 0) + return stmtfmt(f, n); + + return exprfmt(f, n, 0); +} + +static int dumpdepth; + +static void +indent(Fmt *fp) +{ + int i; + + fmtstrcpy(fp, "\n"); + for(i = 0; i < dumpdepth; ++i) + fmtstrcpy(fp, ". "); +} + +static int +nodedump(Fmt *fp, Node *n) +{ + int recur; + + if(n == N) + return 0; + + recur = !(fp->flags&FmtShort); + + if(recur) { + indent(fp); + if(dumpdepth > 10) + return fmtstrcpy(fp, "..."); + + if(n->ninit != nil) { + fmtprint(fp, "%O-init%H", n->op, n->ninit); + indent(fp); + } + } + +// fmtprint(fp, "[%p]", n); + + switch(n->op) { + default: + fmtprint(fp, "%O%J", n->op, n); + break; + case OREGISTER: + fmtprint(fp, "%O-%R%J", n->op, n->val.u.reg, n); + break; + case OLITERAL: + fmtprint(fp, "%O-%V%J", n->op, &n->val, n); + break; + case ONAME: + case ONONAME: + if(n->sym != S) + fmtprint(fp, "%O-%S%J", n->op, n->sym, n); + else + fmtprint(fp, "%O%J", n->op, n); + break; + case OASOP: + fmtprint(fp, "%O-%O%J", n->op, n->etype, n); + break; + case OTYPE: + fmtprint(fp, "%O %S type=%T", n->op, n->sym, n->type); + if(recur && n->type == T && n->ntype) { + indent(fp); + fmtprint(fp, "%O-ntype%N", n->op, n->ntype); + } + break; + } + + if(n->sym != S && n->op != ONAME) + fmtprint(fp, " %S G%d", n->sym, n->vargen); + + if(n->type != T) + fmtprint(fp, " %T", n->type); + + if(recur) { + if(n->left) + fmtprint(fp, "%N", n->left); + if(n->right) + fmtprint(fp, "%N", n->right); + if(n->list) { + indent(fp); + fmtprint(fp, "%O-list%H", n->op, n->list); + } + if(n->rlist) { + indent(fp); + fmtprint(fp, "%O-rlist%H", n->op, n->rlist); + } + if(n->ntest) { + indent(fp); + fmtprint(fp, "%O-test%N", n->op, n->ntest); + } + if(n->nbody) { + indent(fp); + fmtprint(fp, "%O-body%H", n->op, n->nbody); + } + if(n->nelse) { + indent(fp); + fmtprint(fp, "%O-else%H", n->op, n->nelse); + } + if(n->nincr) { + indent(fp); + fmtprint(fp, "%O-incr%N", n->op, n->nincr); + } + } + + return 0; +} + +// Fmt "%S": syms +// Flags: "%hS" suppresses qualifying with package +static int +Sconv(Fmt *fp) +{ + Sym *s; + int r, sm; + unsigned long sf; + + s = va_arg(fp->args, Sym*); + if(s == S) + return fmtstrcpy(fp, "<S>"); + + if(s->name && s->name[0] == '_' && s->name[1] == '\0') + return fmtstrcpy(fp, "_"); + + sf = fp->flags; + sm = setfmode(&fp->flags); + r = symfmt(fp, s); + fp->flags = sf; + fmtmode = sm; + return r; +} + +// Fmt "%T": types. +// Flags: 'l' print definition, not name +// 'h' omit 'func' and receiver from function types, short type names +// 'u' package name, not prefix (FTypeId mode, sticky) +static int +Tconv(Fmt *fp) +{ + Type *t; + int r, sm; + unsigned long sf; + + t = va_arg(fp->args, Type*); + if(t == T) + return fmtstrcpy(fp, "<T>"); + + if(t->trecur > 4) + return fmtstrcpy(fp, "<...>"); + + t->trecur++; + sf = fp->flags; + sm = setfmode(&fp->flags); + + if(fmtmode == FTypeId && (sf&FmtUnsigned)) + fmtpkgpfx++; + if(fmtpkgpfx) + fp->flags |= FmtUnsigned; + + r = typefmt(fp, t); + + if(fmtmode == FTypeId && (sf&FmtUnsigned)) + fmtpkgpfx--; + + fp->flags = sf; + fmtmode = sm; + t->trecur--; + return r; +} + +// Fmt '%N': Nodes. +// Flags: 'l' suffix with "(type %T)" where possible +// '+h' in debug mode, don't recurse, no multiline output +static int +Nconv(Fmt *fp) +{ + Node *n; + int r, sm; + unsigned long sf; + + n = va_arg(fp->args, Node*); + if(n == N) + return fmtstrcpy(fp, "<N>"); + sf = fp->flags; + sm = setfmode(&fp->flags); + + r = -1; + switch(fmtmode) { + case FErr: + case FExp: + r = nodefmt(fp, n); + break; + case FDbg: + dumpdepth++; + r = nodedump(fp, n); + dumpdepth--; + break; + default: + fatal("unhandled %%N mode"); + } + + fp->flags = sf; + fmtmode = sm; + return r; +} + +// Fmt '%H': NodeList. +// Flags: all those of %N plus ',': separate with comma's instead of semicolons. +static int +Hconv(Fmt *fp) +{ + NodeList *l; + int r, sm; + unsigned long sf; + char *sep; + + l = va_arg(fp->args, NodeList*); + + if(l == nil && fmtmode == FDbg) + return fmtstrcpy(fp, "<nil>"); + + sf = fp->flags; + sm = setfmode(&fp->flags); + r = 0; + sep = "; "; + if(fmtmode == FDbg) + sep = "\n"; + else if(fp->flags & FmtComma) + sep = ", "; + + for(;l; l=l->next) { + r += fmtprint(fp, "%N", l->n); + if(l->next) + r += fmtstrcpy(fp, sep); + } + + fp->flags = sf; + fmtmode = sm; + return r; +} + +void +fmtinstallgo(void) +{ + fmtmode = FErr; + fmtinstall('E', Econv); // etype opcodes + fmtinstall('J', Jconv); // all the node flags + fmtinstall('H', Hconv); // node lists + fmtinstall('L', Lconv); // line number + fmtinstall('N', Nconv); // node pointer + fmtinstall('O', Oconv); // node opcodes + fmtinstall('S', Sconv); // sym pointer + fmtinstall('T', Tconv); // type pointer + fmtinstall('V', Vconv); // val pointer + fmtinstall('Z', Zconv); // escaped string + + // These are in mparith1.c + fmtinstall('B', Bconv); // big numbers + fmtinstall('F', Fconv); // big float numbers + +} + +void +dumplist(char *s, NodeList *l) +{ + print("%s\n%+H\n", s, l); +} + +void +dump(char *s, Node *n) +{ + print("%s [%p]\n%+N\n", s, n, n); +} diff --git a/src/cmd/gc/gen.c b/src/cmd/gc/gen.c index a818dbc19..694a10ab5 100644 --- a/src/cmd/gc/gen.c +++ b/src/cmd/gc/gen.c @@ -54,7 +54,7 @@ addrescapes(Node *n) if(n->class == PAUTO && n->esc == EscNever) break; - if(debug['s'] && n->esc != EscUnknown) + if(debug['N'] && n->esc != EscUnknown) fatal("without escape analysis, only PAUTO's should have esc: %N", n); switch(n->class) { @@ -91,10 +91,10 @@ addrescapes(Node *n) snprint(buf, sizeof buf, "&%S", n->sym); n->heapaddr->sym = lookup(buf); n->heapaddr->orig->sym = n->heapaddr->sym; - if(!debug['s']) + if(!debug['N']) n->esc = EscHeap; if(debug['m']) - print("%L: moved to heap: %hN\n", n->lineno, n); + print("%L: moved to heap: %N\n", n->lineno, n); curfn = oldfn; break; } @@ -805,6 +805,7 @@ tempname(Node *nn, Type *t) s = lookup(namebuf); n = nod(ONAME, N, N); n->sym = s; + s->def = n; n->type = t; n->class = PAUTO; n->addable = 1; @@ -825,5 +826,6 @@ temp(Type *t) n = nod(OXXX, N, N); tempname(n, t); + n->sym->def->used = 1; return n; } diff --git a/src/cmd/gc/go.h b/src/cmd/gc/go.h index f72799420..9584bb744 100644 --- a/src/cmd/gc/go.h +++ b/src/cmd/gc/go.h @@ -16,6 +16,12 @@ #undef BUFSIZ +// The parser's maximum stack size. +// We have to use a #define macro here since yacc +// or bison will check for its definition and use +// a potentially smaller value if it is undefined. +#define YYMAXDEPTH 500 + enum { NHUNK = 50000, @@ -23,7 +29,6 @@ enum NSYMB = 500, NHASH = 1024, STRINGSZ = 200, - YYMAXDEPTH = 500, MAXALIGN = 7, UINF = 100, HISTSZ = 10, @@ -32,23 +37,30 @@ enum AUNK = 100, - // these values are known by runtime + // These values are known by runtime. + // The MEMx and NOEQx values must run in parallel. See algtype. AMEM = 0, - ANOEQ, - ASTRING, - AINTER, - ANILINTER, - ASLICE, + AMEM0, AMEM8, AMEM16, AMEM32, AMEM64, AMEM128, + ANOEQ, + ANOEQ0, ANOEQ8, ANOEQ16, ANOEQ32, ANOEQ64, ANOEQ128, + ASTRING, + AINTER, + ANILINTER, + ASLICE, + AFLOAT32, + AFLOAT64, + ACPLX64, + ACPLX128, BADWIDTH = -1000000000, }; @@ -67,33 +79,6 @@ struct Strlit char s[3]; // variable }; -/* - * note this is the runtime representation - * of hashmap iterator. it is probably - * insafe to use it this way, but it puts - * all the changes in one place. - * only flag is referenced from go. - * actual placement does not matter as long - * as the size is >= actual size. - */ -typedef struct Hiter Hiter; -struct Hiter -{ - uchar data[8]; // return val from next - int32 elemsize; // size of elements in table */ - int32 changes; // number of changes observed last time */ - int32 i; // stack pointer in subtable_state */ - uchar last[8]; // last hash value returned */ - uchar h[8]; // the hash table */ - struct - { - uchar sub[8]; // pointer into subtable */ - uchar start[8]; // pointer into start of subtable */ - uchar end[8]; // pointer into end of subtable */ - uchar pad[8]; - } sub[4]; -}; - enum { Mpscale = 29, // safely smaller than bits in a long @@ -135,7 +120,7 @@ struct Val { short reg; // OREGISTER short bval; // bool value CTBOOL - Mpint* xval; // int CTINT + Mpint* xval; // int CTINT, rune CTRUNE Mpflt* fval; // float CTFLT Mpcplx* cval; // float CTCPLX Strlit* sval; // string CTSTR @@ -157,12 +142,12 @@ struct Type uchar printed; uchar embedded; // TFIELD embedded type uchar siggen; - uchar funarg; + uchar funarg; // on TSTRUCT and TFIELD uchar copyany; uchar local; // created in this file uchar deferwidth; uchar broke; - uchar isddd; // TFIELD is ... argument + uchar isddd; // TFIELD is ... argument uchar align; Node* nod; // canonical OTYPE node @@ -266,6 +251,8 @@ struct Node uchar isddd; uchar readonly; uchar implicit; // don't show in printout + uchar addrtaken; // address taken, even if not moved to heap + uchar dupok; // duplicate definitions ok (for func) // most nodes Type* type; @@ -279,6 +266,7 @@ struct Node NodeList* exit; NodeList* cvars; // closure params NodeList* dcl; // autodcl for this func/closure + NodeList* inl; // copy of the body for use in inlining // OLITERAL/OREGISTER Val val; @@ -299,6 +287,9 @@ struct Node Node* outer; // outer PPARAMREF in nested closure Node* closure; // ONAME/PHEAP <-> ONAME/PPARAMREF + // ONAME substitute while inlining + Node* inlvar; + // OPACK Pkg* pkg; @@ -346,9 +337,9 @@ struct NodeList enum { - SymExport = 1<<0, + SymExport = 1<<0, // to be exported SymPackage = 1<<1, - SymExported = 1<<2, + SymExported = 1<<2, // already written out by export SymUniq = 1<<3, SymSiggen = 1<<4, }; @@ -375,10 +366,10 @@ EXTERN Sym* dclstack; struct Pkg { - char* name; - Strlit* path; + char* name; // package name + Strlit* path; // string literal used in import statement Sym* pathsym; - char* prefix; + char* prefix; // escaped path for use in symbol table Pkg* link; char exported; // import line written in export data char direct; // imported directly @@ -422,17 +413,19 @@ enum OAPPEND, OARRAYBYTESTR, OARRAYRUNESTR, OSTRARRAYBYTE, OSTRARRAYRUNE, - OAS, OAS2, OAS2MAPW, OAS2FUNC, OAS2RECV, OAS2MAPR, OAS2DOTTYPE, OASOP, + OAS, OAS2, OAS2FUNC, OAS2RECV, OAS2MAPR, OAS2DOTTYPE, + OASOP, OBAD, OCALL, OCALLFUNC, OCALLMETH, OCALLINTER, OCAP, OCLOSE, OCLOSURE, OCMPIFACE, OCMPSTR, - OCOMPLIT, OMAPLIT, OSTRUCTLIT, OARRAYLIT, + OCOMPLIT, OMAPLIT, OSTRUCTLIT, OARRAYLIT, OPTRLIT, OCONV, OCONVIFACE, OCONVNOP, OCOPY, ODCL, ODCLFUNC, ODCLFIELD, ODCLCONST, ODCLTYPE, + ODELETE, ODOT, ODOTPTR, ODOTMETH, ODOTINTER, OXDOT, ODOTTYPE, ODOTTYPE2, @@ -490,6 +483,7 @@ enum // misc ODDD, ODDDARG, + OINLCALL, // intermediary representation of an inlined call // for back ends OCMP, ODEC, OEXTEND, OINC, OREGISTER, OINDREG, @@ -546,6 +540,7 @@ enum CTxxx, CTINT, + CTRUNE, CTFLT, CTCPLX, CTSTR, @@ -779,6 +774,9 @@ EXTERN Idir* idirs; EXTERN Type* types[NTYPE]; EXTERN Type* idealstring; EXTERN Type* idealbool; +EXTERN Type* bytetype; +EXTERN Type* runetype; +EXTERN Type* errortype; EXTERN uchar simtype[NTYPE]; EXTERN uchar isptr[NTYPE]; EXTERN uchar isforw[NTYPE]; @@ -810,7 +808,7 @@ EXTERN NodeList* xtop; EXTERN NodeList* externdcl; EXTERN NodeList* closures; EXTERN NodeList* exportlist; -EXTERN NodeList* typelist; +EXTERN NodeList* importlist; // imported functions and methods with inlinable bodies EXTERN int dclcontext; // PEXTERN/PAUTO EXTERN int incannedimport; EXTERN int statuniqgen; // name generator for static temps @@ -834,18 +832,13 @@ EXTERN Node* nblank; extern int thechar; extern char* thestring; + EXTERN char* hunk; EXTERN int32 nhunk; EXTERN int32 thunk; -EXTERN int exporting; -EXTERN int erroring; -EXTERN int noargnames; - EXTERN int funcdepth; EXTERN int typecheckok; -EXTERN int packagequotes; -EXTERN int longsymnames; EXTERN int compiling_runtime; /* @@ -930,7 +923,6 @@ void colasdefn(NodeList *left, Node *defn); NodeList* constiter(NodeList *vl, Node *t, NodeList *cl); Node* dclname(Sym *s); void declare(Node *n, int ctxt); -Type* dostruct(NodeList *l, int et); void dumpdcl(char *st); Node* embedded(Sym *s); Node* fakethis(void); @@ -951,9 +943,10 @@ void popdcl(void); void poptodcl(void); void redeclare(Sym *s, char *where); void testdclstack(void); +Type* tointerface(NodeList *l); +Type* tostruct(NodeList *l); Node* typedcl0(Sym *s); Node* typedcl1(Node *n, Node *t, int local); -void typedcl2(Type *pt, Type *t); Node* typenod(Type *t); NodeList* variter(NodeList *vl, Node *t, NodeList *el); @@ -969,14 +962,21 @@ void autoexport(Node *n, int ctxt); void dumpexport(void); int exportname(char *s); void exportsym(Node *n); -void importconst(Sym *s, Type *t, Node *n); -void importmethod(Sym *s, Type *t); -Sym* importsym(Sym *s, int op); -void importtype(Type *pt, Type *t); -void importvar(Sym *s, Type *t, int ctxt); +void importconst(Sym *s, Type *t, Node *n); +void importimport(Sym *s, Strlit *z); +Sym* importsym(Sym *s, int op); +void importtype(Type *pt, Type *t); +void importvar(Sym *s, Type *t); Type* pkgtype(Sym *s); /* + * fmt.c + */ +void fmtinstallgo(void); +void dump(char *s, Node *n); +void dumplist(char *s, NodeList *l); + +/* * gen.c */ void addrescapes(Node *n); @@ -995,7 +995,14 @@ Node* temp(Type*); * init.c */ void fninit(NodeList *n); -Node* renameinit(Node *n); +Sym* renameinit(void); + +/* + * inl.c + */ +void caninl(Node *fn); +void inlcalls(Node *fn); +void typecheckinl(Node *fn); /* * lex.c @@ -1003,6 +1010,7 @@ Node* renameinit(Node *n); void cannedimports(char *file, char *cp); void importfile(Val *f, int line); char* lexname(int lex); +char* expstring(void); void mkpackage(char* pkgname); void unimportfile(void); int32 yylex(void); @@ -1085,10 +1093,9 @@ void ieeedtod(uint64 *ieee, double native); Sym* stringsym(char*, int); /* - * print.c + * order.c */ -void exprfmt(Fmt *f, Node *n, int prec); -void exprlistfmt(Fmt *f, NodeList *l); +void order(Node *fn); /* * range.c @@ -1103,6 +1110,7 @@ void dumptypestructs(void); Type* methodfunc(Type *f, Type*); Node* typename(Type *t); Sym* typesym(Type *t); +Sym* typesymprefix(char *prefix, Type *t); int haspointers(Type *t); /* @@ -1123,19 +1131,12 @@ int stataddr(Node *nam, Node *n); /* * subr.c */ -int Econv(Fmt *fp); -int Jconv(Fmt *fp); -int Lconv(Fmt *fp); -int Nconv(Fmt *fp); -int Oconv(Fmt *fp); -int Sconv(Fmt *fp); -int Tconv(Fmt *fp); -int Tpretty(Fmt *fp, Type *t); -int Zconv(Fmt *fp); Node* adddot(Node *n); int adddot1(Sym *s, Type *t, int d, Type **save, int ignorecase); +void addinit(Node**, NodeList*); Type* aindex(Node *b, Type *t); int algtype(Type *t); +int algtype1(Type *t, Type **bad); void argtype(Node *on, Type *t); Node* assignconv(Node *n, Type *t, char *context); int assignop(Type *src, Type *dst, char **why); @@ -1144,10 +1145,9 @@ int brcom(int a); int brrev(int a); NodeList* concat(NodeList *a, NodeList *b); int convertop(Type *src, Type *dst, char **why); +Node* copyexpr(Node*, Type*, NodeList**); int count(NodeList *l); int cplxsubtype(int et); -void dump(char *s, Node *n); -void dumplist(char *s, NodeList *l); int eqtype(Type *t1, Type *t2); int eqtypenoname(Type *t1, Type *t2); void errorexit(void); @@ -1158,6 +1158,8 @@ void frame(int context); Type* funcfirst(Iter *s, Type *t); Type* funcnext(Iter *s); void genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface); +void genhash(Sym *sym, Type *t); +void geneq(Sym *sym, Type *t); Type** getinarg(Type *t); Type* getinargx(Type *t); Type** getoutarg(Type *t); @@ -1237,7 +1239,6 @@ void walkswitch(Node *sw); /* * typecheck.c */ -int exportassignok(Type *t, char *desc); int islvalue(Node *n); Node* typecheck(Node **np, int top); void typechecklist(NodeList *l, int top); @@ -1250,6 +1251,7 @@ void queuemethod(Node *n); /* * unsafe.c */ +int isunsafebuiltin(Node *n); Node* unsafenmagic(Node *n); /* @@ -1266,6 +1268,7 @@ void walkexprlist(NodeList *l, NodeList **init); void walkexprlistsafe(NodeList *l, NodeList **init); void walkstmt(Node **np); void walkstmtlist(NodeList *l); +Node* conv(Node*, Type*); /* * arch-specific ggen.c/gsubr.c/gobj.c/pgen.c @@ -1335,11 +1338,16 @@ void zname(Biobuf *b, Sym *s, int t); #pragma varargck type "D" Addr* #pragma varargck type "lD" Addr* #pragma varargck type "E" int +#pragma varargck type "E" uint #pragma varargck type "F" Mpflt* +#pragma varargck type "H" NodeList* #pragma varargck type "J" Node* +#pragma varargck type "lL" int +#pragma varargck type "lL" uint #pragma varargck type "L" int #pragma varargck type "L" uint #pragma varargck type "N" Node* +#pragma varargck type "lN" Node* #pragma varargck type "O" uint #pragma varargck type "P" Prog* #pragma varargck type "Q" Bits @@ -1348,5 +1356,6 @@ void zname(Biobuf *b, Sym *s, int t); #pragma varargck type "lS" Sym* #pragma varargck type "T" Type* #pragma varargck type "lT" Type* +#pragma varargck type "V" Val* #pragma varargck type "Y" char* #pragma varargck type "Z" Strlit* diff --git a/src/cmd/gc/go.y b/src/cmd/gc/go.y index 0c007f5f0..de0735425 100644 --- a/src/cmd/gc/go.y +++ b/src/cmd/gc/go.y @@ -31,13 +31,13 @@ static void fixlbrace(int); Type* type; Sym* sym; struct Val val; - int lint; + int i; } // |sed 's/.* //' |9 fmt -l1 |sort |9 fmt -l50 | sed 's/^/%xxx /' %token <val> LLITERAL -%token <lint> LASOP +%token <i> LASOP %token <sym> LBREAK LCASE LCHAN LCOLAS LCONST LCONTINUE LDDD %token <sym> LDEFAULT LDEFER LELSE LFALL LFOR LFUNC LGO LGOTO %token <sym> LIF LIMPORT LINTERFACE LMAP LNAME @@ -47,7 +47,7 @@ static void fixlbrace(int); %token LANDAND LANDNOT LBODY LCOMM LDEC LEQ LGE LGT %token LIGNORE LINC LLE LLSH LLT LNE LOROR LRSH -%type <lint> lbrace import_here +%type <i> lbrace import_here %type <sym> sym packname %type <val> oliteral @@ -56,7 +56,7 @@ static void fixlbrace(int); %type <node> case caseblock %type <node> compound_stmt dotname embed expr complitexpr %type <node> expr_or_type -%type <node> fndcl fnliteral +%type <node> fndcl hidden_fndcl fnliteral %type <node> for_body for_header for_stmt if_header if_stmt else non_dcl_stmt %type <node> interfacedcl keyval labelname name %type <node> name_or_type non_expr_type @@ -66,7 +66,7 @@ static void fixlbrace(int); %type <node> pseudocall range_stmt select_stmt %type <node> simple_stmt %type <node> switch_stmt uexpr -%type <node> xfndcl typedcl +%type <node> xfndcl typedcl start_complit %type <list> xdcl fnbody fnres loop_body dcl_name_list %type <list> new_name_list expr_list keyval_list braced_keyval_list expr_or_type_list xdcl_list @@ -78,12 +78,10 @@ static void fixlbrace(int); %type <node> indcl interfacetype structtype ptrtype %type <node> recvchantype non_recvchantype othertype fnret_type fntype -%type <val> hidden_tag - %type <sym> hidden_importsym hidden_pkg_importsym -%type <node> hidden_constant hidden_literal hidden_dcl -%type <node> hidden_interfacedcl hidden_structdcl hidden_opt_sym +%type <node> hidden_constant hidden_literal hidden_funarg +%type <node> hidden_interfacedcl hidden_structdcl %type <list> hidden_funres %type <list> ohidden_funres @@ -237,7 +235,7 @@ import_here: } import_package: - LPACKAGE sym import_safety ';' + LPACKAGE LNAME import_safety ';' { if(importpkg->name == nil) { importpkg->name = $2->name; @@ -420,18 +418,15 @@ simple_stmt: | expr_list LCOLAS expr_list { if($3->n->op == OTYPESW) { - Node *n; - - n = N; + $$ = nod(OTYPESW, N, $3->n->right); if($3->next != nil) yyerror("expr.(type) must be alone in list"); if($1->next != nil) yyerror("argument count mismatch: %d = %d", count($1), 1); else if($1->n->op != ONAME && $1->n->op != OTYPE && $1->n->op != ONONAME) - yyerror("invalid variable name %#N in type switch", $1->n); + yyerror("invalid variable name %N in type switch", $1->n); else - n = $1->n; - $$ = nod(OTYPESW, n, $3->n->right); + $$->left = dclname($1->n->sym); // it's a colas, so must not re-use an oldname. break; } $$ = colas($1, $3); @@ -450,7 +445,7 @@ simple_stmt: case: LCASE expr_or_type_list ':' { - Node *n; + Node *n, *nn; // will be converted to OCASE // right will point to next case @@ -460,12 +455,13 @@ case: $$->list = $2; if(typesw != N && typesw->right != N && (n=typesw->right->left) != N) { // type switch - declare variable - n = newname(n->sym); - n->used = 1; // TODO(rsc): better job here - declare(n, dclcontext); - $$->nname = n; + nn = newname(n->sym); + declare(nn, dclcontext); + $$->nname = nn; + + // keep track of the instances for reporting unused + nn->defn = typesw->right; } - break; } | LCASE expr_or_type_list '=' expr ':' { @@ -496,16 +492,18 @@ case: } | LDEFAULT ':' { - Node *n; + Node *n, *nn; markdcl(); $$ = nod(OXCASE, N, N); if(typesw != N && typesw->right != N && (n=typesw->right->left) != N) { // type switch - declare variable - n = newname(n->sym); - n->used = 1; // TODO(rsc): better job here - declare(n, dclcontext); - $$->nname = n; + nn = newname(n->sym); + declare(nn, dclcontext); + $$->nname = nn; + + // keep track of the instances for reporting unused + nn->defn = typesw->right; } } @@ -806,7 +804,14 @@ uexpr: } | '&' uexpr { - $$ = nod(OADDR, $2, N); + if($2->op == OCOMPLIT) { + // Special case for &T{...}: turn into (*T){...}. + $$ = $2; + $$->right = nod(OIND, $$->right, N); + $$->right->implicit = 1; + } else { + $$ = nod(OADDR, $2, N); + } } | '+' uexpr { @@ -895,29 +900,35 @@ pexpr_no_paren: $$ = nod(OCALL, $1, N); $$->list = list1($3); } -| comptype lbrace braced_keyval_list '}' +| comptype lbrace start_complit braced_keyval_list '}' { - // composite expression - $$ = nod(OCOMPLIT, N, $1); - $$->list = $3; - + $$ = $3; + $$->right = $1; + $$->list = $4; fixlbrace($2); } -| pexpr_no_paren '{' braced_keyval_list '}' +| pexpr_no_paren '{' start_complit braced_keyval_list '}' { - // composite expression - $$ = nod(OCOMPLIT, N, $1); - $$->list = $3; + $$ = $3; + $$->right = $1; + $$->list = $4; } -| '(' expr_or_type ')' '{' braced_keyval_list '}' +| '(' expr_or_type ')' '{' start_complit braced_keyval_list '}' { yyerror("cannot parenthesize type in composite literal"); - // composite expression - $$ = nod(OCOMPLIT, N, $2); - $$->list = $5; + $$ = $5; + $$->right = $2; + $$->list = $6; } | fnliteral +start_complit: + { + // composite expression. + // make node early so we get the right line number. + $$ = nod(OCOMPLIT, N, N); + } + keyval: expr ':' complitexpr { @@ -926,10 +937,10 @@ keyval: complitexpr: expr -| '{' braced_keyval_list '}' +| '{' start_complit braced_keyval_list '}' { - $$ = nod(OCOMPLIT, N, N); - $$->list = $2; + $$ = $2; + $$->list = $3; } pexpr: @@ -993,6 +1004,26 @@ onew_name: sym: LNAME + { + $$ = $1; + // during imports, unqualified non-exported identifiers are from builtinpkg + if(importpkg != nil && !exportname($1->name)) + $$ = pkglookup($1->name, builtinpkg); + } +| hidden_importsym +| '?' + { + $$ = S; + } + +hidden_importsym: + '@' LLITERAL '.' LNAME + { + if($2.u.sval->len == 0) + $$ = pkglookup($4->name, importpkg); + else + $$ = pkglookup($4->name, mkpkg($2.u.sval)); + } name: sym %prec NotParen @@ -1165,38 +1196,43 @@ xfndcl: } fndcl: - dcl_name '(' oarg_type_list_ocomma ')' fnres + sym '(' oarg_type_list_ocomma ')' fnres { - Node *n; + Node *t; + $$ = N; $3 = checkarglist($3, 1); - $$ = nod(ODCLFUNC, N, N); - $$->nname = $1; - n = nod(OTFUNC, N, N); - n->list = $3; - n->rlist = $5; - if(strcmp($1->sym->name, "init") == 0) { - $$->nname = renameinit($1); + + if(strcmp($1->name, "init") == 0) { + $1 = renameinit(); if($3 != nil || $5 != nil) yyerror("func init must have no arguments and no return values"); } - if(strcmp(localpkg->name, "main") == 0 && strcmp($1->sym->name, "main") == 0) { + if(strcmp(localpkg->name, "main") == 0 && strcmp($1->name, "main") == 0) { if($3 != nil || $5 != nil) yyerror("func main must have no arguments and no return values"); } - // TODO: check if nname already has an ntype - $$->nname->ntype = n; + + t = nod(OTFUNC, N, N); + t->list = $3; + t->rlist = $5; + + $$ = nod(ODCLFUNC, N, N); + $$->nname = newname($1); + $$->nname->defn = $$; + $$->nname->ntype = t; // TODO: check if nname already has an ntype + declare($$->nname, PFUNC); + funchdr($$); } | '(' oarg_type_list_ocomma ')' sym '(' oarg_type_list_ocomma ')' fnres { Node *rcvr, *t; - Node *name; - - name = newname($4); + + $$ = N; $2 = checkarglist($2, 0); $6 = checkarglist($6, 1); - $$ = N; + if($2 == nil) { yyerror("method has no receiver"); break; @@ -1213,15 +1249,59 @@ fndcl: if(rcvr->right->op == OTPAREN || (rcvr->right->op == OIND && rcvr->right->left->op == OTPAREN)) yyerror("cannot parenthesize receiver type"); - $$ = nod(ODCLFUNC, N, N); - $$->nname = methodname1(name, rcvr->right); t = nod(OTFUNC, rcvr, N); t->list = $6; t->rlist = $8; + + $$ = nod(ODCLFUNC, N, N); + $$->shortname = newname($4); + $$->nname = methodname1($$->shortname, rcvr->right); + $$->nname->defn = $$; $$->nname->ntype = t; - $$->shortname = name; + declare($$->nname, PFUNC); + + funchdr($$); + } + +hidden_fndcl: + hidden_pkg_importsym '(' ohidden_funarg_list ')' ohidden_funres + { + Sym *s; + Type *t; + + $$ = N; + + s = $1; + t = functype(N, $3, $5); + + importsym(s, ONAME); + if(s->def != N && s->def->op == ONAME) { + if(eqtype(t, s->def->type)) + break; + yyerror("inconsistent definition for func %S during import\n\t%T\n\t%T", s, s->def->type, t); + } + + $$ = newname(s); + $$->type = t; + declare($$, PFUNC); + funchdr($$); } +| '(' hidden_funarg_list ')' sym '(' ohidden_funarg_list ')' ohidden_funres + { + $$ = methodname1(newname($4), $2->n->right); + $$->type = functype($2->n, $6, $8); + + checkwidth($$->type); + addmethod($4, $$->type, 0); + funchdr($$); + + // inl.c's inlnode in on a dotmeth node expects to find the inlineable body as + // (dotmeth's type)->nname->inl, and dotmeth's type has been pulled + // out by typecheck's lookdot as this $$->ttype. So by providing + // this back link here we avoid special casing there. + $$->type->nname = $$; + } fntype: LFUNC '(' oarg_type_list_ocomma ')' fnres @@ -1538,6 +1618,18 @@ non_dcl_stmt: { $$ = nod(ORETURN, N, N); $$->list = $2; + if($$->list == nil && curfn != N) { + NodeList *l; + + for(l=curfn->dcl; l; l=l->next) { + if(l->n->class == PPARAM) + continue; + if(l->n->class != PPARAMOUT) + break; + if(l->n->sym->def != l->n) + yyerror("%s is shadowed during return", l->n->sym->name); + } + } } stmt_list: @@ -1676,31 +1768,16 @@ oliteral: | LLITERAL /* - * import syntax from header of - * an output package + * import syntax from package header */ hidden_import: - LIMPORT sym LLITERAL ';' + LIMPORT LNAME LLITERAL ';' { - // Informational: record package name - // associated with import path, for use in - // human-readable messages. - Pkg *p; - - p = mkpkg($3.u.sval); - if(p->name == nil) { - p->name = $2->name; - pkglookup($2->name, nil)->npkg++; - } else if(strcmp(p->name, $2->name) != 0) - yyerror("conflicting names %s and %s for package \"%Z\"", p->name, $2->name, p->path); - if(!incannedimport && myimportpath != nil && strcmp($3.u.sval->s, myimportpath) == 0) { - yyerror("import \"%Z\": package depends on \"%Z\" (import cycle)", importpkg->path, $3.u.sval); - errorexit(); - } + importimport($2, $3.u.sval); } | LVAR hidden_pkg_importsym hidden_type ';' { - importvar($2, $3, PEXTERN); + importvar($2, $3); } | LCONST hidden_pkg_importsym '=' hidden_constant ';' { @@ -1714,17 +1791,28 @@ hidden_import: { importtype($2, $3); } -| LFUNC hidden_pkg_importsym '(' ohidden_funarg_list ')' ohidden_funres ';' +| LFUNC hidden_fndcl fnbody ';' { - importvar($2, functype(N, $4, $6), PFUNC); + if($2 == N) + break; + + $2->inl = $3; + + funcbody($2); + importlist = list(importlist, $2); + + if(debug['E']) { + print("import [%Z] func %lN \n", importpkg->path, $2); + if(debug['l'] > 2 && $2->inl) + print("inl body:%+H\n", $2->inl); + } } -| LFUNC '(' hidden_funarg_list ')' sym '(' ohidden_funarg_list ')' ohidden_funres ';' + +hidden_pkg_importsym: + hidden_importsym { - if($3->next != nil || $3->n->op != ODCLFIELD) { - yyerror("bad receiver in method"); - YYERROR; - } - importmethod($5, functype($3->n, $7, $9)); + $$ = $1; + structpkg = $$->pkg; } hidden_pkgtype: @@ -1734,6 +1822,10 @@ hidden_pkgtype: importsym($1, OTYPE); } +/* + * importing types + */ + hidden_type: hidden_type_misc | hidden_type_recv_chan @@ -1772,11 +1864,11 @@ hidden_type_misc: } | LSTRUCT '{' ohidden_structdcl_list '}' { - $$ = dostruct($3, TSTRUCT); + $$ = tostruct($3); } | LINTERFACE '{' ohidden_interfacedcl_list '}' { - $$ = dostruct($3, TINTER); + $$ = tointerface($3); } | '*' hidden_type { @@ -1815,61 +1907,45 @@ hidden_type_func: $$ = functype(nil, $3, $5); } -hidden_opt_sym: - sym - { - $$ = newname($1); - } -| '?' - { - $$ = N; - } - -hidden_dcl: - hidden_opt_sym hidden_type hidden_tag +hidden_funarg: + sym hidden_type oliteral { - $$ = nod(ODCLFIELD, $1, typenod($2)); + $$ = nod(ODCLFIELD, N, typenod($2)); + if($1) + $$->left = newname($1); $$->val = $3; } -| hidden_opt_sym LDDD hidden_type hidden_tag +| sym LDDD hidden_type oliteral { Type *t; - + t = typ(TARRAY); t->bound = -1; t->type = $3; - $$ = nod(ODCLFIELD, $1, typenod(t)); + + $$ = nod(ODCLFIELD, N, typenod(t)); + if($1) + $$->left = newname($1); $$->isddd = 1; $$->val = $4; } hidden_structdcl: - sym hidden_type hidden_tag - { - $$ = nod(ODCLFIELD, newname($1), typenod($2)); - $$->val = $3; - } -| '?' hidden_type hidden_tag + sym hidden_type oliteral { Sym *s; - s = $2->sym; - if(s == S && isptr[$2->etype]) - s = $2->type->sym; - if(s && s->pkg == builtinpkg) - s = lookup(s->name); - $$ = embedded(s); - $$->right = typenod($2); - $$->val = $3; - } - -hidden_tag: - { - $$.ctype = CTxxx; - } -| ':' LLITERAL // extra colon avoids conflict with "" looking like beginning of "".typename - { - $$ = $2; + if($1 != S) { + $$ = nod(ODCLFIELD, newname($1), typenod($2)); + $$->val = $3; + } else { + s = $2->sym; + if(s == S && isptr[$2->etype]) + s = $2->type->sym; + $$ = embedded(s); + $$->right = typenod($2); + $$->val = $3; + } } hidden_interfacedcl: @@ -1877,9 +1953,9 @@ hidden_interfacedcl: { $$ = nod(ODCLFIELD, newname($1), typenod(functype(fakethis(), $3, $5))); } -| hidden_importsym '(' ohidden_funarg_list ')' ohidden_funres +| hidden_type { - $$ = nod(ODCLFIELD, newname($1), typenod(functype(fakethis(), $3, $5))); + $$ = nod(ODCLFIELD, N, typenod($1)); } ohidden_funres: @@ -1898,6 +1974,10 @@ hidden_funres: $$ = list1(nod(ODCLFIELD, N, typenod($1))); } +/* + * importing constants + */ + hidden_literal: LLITERAL { @@ -1908,6 +1988,7 @@ hidden_literal: $$ = nodlit($2); switch($$->val.ctype){ case CTINT: + case CTRUNE: mpnegfix($$->val.u.xval); break; case CTFLT: @@ -1928,37 +2009,23 @@ hidden_constant: hidden_literal | '(' hidden_literal '+' hidden_literal ')' { + if($2->val.ctype == CTRUNE && $4->val.ctype == CTINT) { + $$ = $2; + mpaddfixfix($2->val.u.xval, $4->val.u.xval); + break; + } $$ = nodcplxlit($2->val, $4->val); } -hidden_importsym: - LLITERAL '.' sym - { - Pkg *p; - - if($1.u.sval->len == 0) - p = importpkg; - else - p = mkpkg($1.u.sval); - $$ = pkglookup($3->name, p); - } - -hidden_pkg_importsym: - hidden_importsym - { - $$ = $1; - structpkg = $$->pkg; - } - hidden_import_list: | hidden_import_list hidden_import hidden_funarg_list: - hidden_dcl + hidden_funarg { $$ = list1($1); } -| hidden_funarg_list ',' hidden_dcl +| hidden_funarg_list ',' hidden_funarg { $$ = list($1, $3); } diff --git a/src/cmd/gc/init.c b/src/cmd/gc/init.c index da69e41ae..be402cc0c 100644 --- a/src/cmd/gc/init.c +++ b/src/cmd/gc/init.c @@ -13,21 +13,13 @@ * package and also uncallable, the name, * normally "pkg.init", is altered to "pkg.init·1". */ -Node* -renameinit(Node *n) +Sym* +renameinit(void) { - Sym *s; static int initgen; - s = n->sym; - if(s == S) - return n; - if(strcmp(s->name, "init") != 0) - return n; - snprint(namebuf, sizeof(namebuf), "init·%d", ++initgen); - s = lookup(namebuf); - return newname(s); + return lookup(namebuf); } /* @@ -125,7 +117,9 @@ fninit(NodeList *n) fn = nod(ODCLFUNC, N, N); initsym = lookup(namebuf); fn->nname = newname(initsym); + fn->nname->defn = fn; fn->nname->ntype = nod(OTFUNC, N, N); + declare(fn->nname, PFUNC); funchdr(fn); // (3) diff --git a/src/cmd/gc/inl.c b/src/cmd/gc/inl.c new file mode 100644 index 000000000..ed7a7eb95 --- /dev/null +++ b/src/cmd/gc/inl.c @@ -0,0 +1,780 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// The inlining facility makes 2 passes: first caninl determines which +// functions are suitable for inlining, and for those that are it +// saves a copy of the body. Then inlcalls walks each function body to +// expand calls to inlinable functions. +// +// The debug['l'] flag controls the agressiveness. Note that main() swaps level 0 and 1, +// making 1 the default and -l disable. -ll and more is useful to flush out bugs. +// These additional levels (beyond -l) may be buggy and are not supported. +// 0: disabled +// 1: 40-nodes leaf functions, oneliners, lazy typechecking (default) +// 2: early typechecking of all imported bodies +// 3: +// 4: allow non-leaf functions , (breaks runtime.Caller) +// 5: transitive inlining +// +// At some point this may get another default and become switch-offable with -N. +// +// The debug['m'] flag enables diagnostic output. a single -m is useful for verifying +// which calls get inlined or not, more is for debugging, and may go away at any point. +// +// TODO: +// - inline functions with ... args +// - handle T.meth(f()) with func f() (t T, arg, arg, ) + +#include <u.h> +#include <libc.h> +#include "go.h" + +// Used by caninl. +static Node* inlcopy(Node *n); +static NodeList* inlcopylist(NodeList *ll); +static int ishairy(Node *n, int *budget); +static int ishairylist(NodeList *ll, int *budget); + +// Used by inlcalls +static void inlnodelist(NodeList *l); +static void inlnode(Node **np); +static void mkinlcall(Node **np, Node *fn); +static Node* inlvar(Node *n); +static Node* retvar(Type *n, int i); +static Node* newlabel(void); +static Node* inlsubst(Node *n); +static NodeList* inlsubstlist(NodeList *l); + +static void setlno(Node*, int); + +// Used during inlsubst[list] +static Node *inlfn; // function currently being inlined +static Node *inlretlabel; // target of the goto substituted in place of a return +static NodeList *inlretvars; // temp out variables + +// Lazy typechecking of imported bodies. +// TODO avoid redoing local functions (imporpkg would be wrong) +void +typecheckinl(Node *fn) +{ + Node *savefn; + + if (debug['m']>2) + print("typecheck import [%S] %lN { %#H }\n", fn->sym, fn, fn->inl); + + savefn = curfn; + curfn = fn; + importpkg = fn->sym->pkg; + typechecklist(fn->inl, Etop); + importpkg = nil; + curfn = savefn; +} + +// Caninl determines whether fn is inlineable. Currently that means: +// fn is exactly 1 statement, either a return or an assignment, and +// some temporary constraints marked TODO. If fn is inlineable, saves +// fn->nbody in fn->inl and substitutes it with a copy. +void +caninl(Node *fn) +{ + Node *savefn; + Type *t; + int budget; + + if(fn->op != ODCLFUNC) + fatal("caninl %N", fn); + if(!fn->nname) + fatal("caninl no nname %+N", fn); + + // If fn has no body (is defined outside of Go), cannot inline it. + if(fn->nbody == nil) + return; + + // can't handle ... args yet + for(t=fn->type->type->down->down->type; t; t=t->down) + if(t->isddd) + return; + + budget = 40; // allowed hairyness + if(ishairylist(fn->nbody, &budget)) + return; + + savefn = curfn; + curfn = fn; + + fn->nname->inl = fn->nbody; + fn->nbody = inlcopylist(fn->nname->inl); + + // hack, TODO, check for better way to link method nodes back to the thing with the ->inl + // this is so export can find the body of a method + fn->type->nname = fn->nname; + + if(debug['m'] > 1) + print("%L: can inline %#N as: %#T { %#H }\n", fn->lineno, fn->nname, fn->type, fn->nname->inl); + else if(debug['m']) + print("%L: can inline %N\n", fn->lineno, fn->nname); + + curfn = savefn; +} + +// Look for anything we want to punt on. +static int +ishairylist(NodeList *ll, int* budget) +{ + for(;ll;ll=ll->next) + if(ishairy(ll->n, budget)) + return 1; + return 0; +} + +static int +ishairy(Node *n, int *budget) +{ + if(!n) + return 0; + + // Things that are too hairy, irrespective of the budget + switch(n->op) { + case OCALL: + case OCALLFUNC: + case OCALLINTER: + case OCALLMETH: + if(debug['l'] < 4) + return 1; + break; + + case OCLOSURE: + case ORANGE: + case OFOR: + case OSELECT: + case OSWITCH: + case OPROC: + case ODEFER: + case ODCL: // declares locals as globals b/c of @"". qualification + case ODCLTYPE: // can't print yet + case ODCLCONST: // can't print yet + return 1; + + break; + case OAS: + // x = <N> zero initializing assignments aren't representible in export yet. + // alternatively we may just skip them in printing and hope their DCL printed + // as a var will regenerate it + if(n->right == N) + return 1; + break; + } + + (*budget)--; + + return *budget < 0 || + ishairy(n->left, budget) || + ishairy(n->right, budget) || + ishairylist(n->list, budget) || + ishairylist(n->rlist, budget) || + ishairylist(n->ninit, budget) || + ishairy(n->ntest, budget) || + ishairy(n->nincr, budget) || + ishairylist(n->nbody, budget) || + ishairylist(n->nelse, budget); +} + +// Inlcopy and inlcopylist recursively copy the body of a function. +// Any name-like node of non-local class is marked for re-export by adding it to +// the exportlist. +static NodeList* +inlcopylist(NodeList *ll) +{ + NodeList *l; + + l = nil; + for(; ll; ll=ll->next) + l = list(l, inlcopy(ll->n)); + return l; +} + +static Node* +inlcopy(Node *n) +{ + Node *m; + + if(n == N) + return N; + + switch(n->op) { + case ONAME: + case OTYPE: + case OLITERAL: + return n; + } + + m = nod(OXXX, N, N); + *m = *n; + m->inl = nil; + m->left = inlcopy(n->left); + m->right = inlcopy(n->right); + m->list = inlcopylist(n->list); + m->rlist = inlcopylist(n->rlist); + m->ninit = inlcopylist(n->ninit); + m->ntest = inlcopy(n->ntest); + m->nincr = inlcopy(n->nincr); + m->nbody = inlcopylist(n->nbody); + m->nelse = inlcopylist(n->nelse); + + return m; +} + + +// Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any +// calls made to inlineable functions. This is the external entry point. +void +inlcalls(Node *fn) +{ + Node *savefn; + + savefn = curfn; + curfn = fn; + inlnode(&fn); + if(fn != curfn) + fatal("inlnode replaced curfn"); + curfn = savefn; +} + +// Turn an OINLCALL into a statement. +static void +inlconv2stmt(Node *n) +{ + n->op = OBLOCK; + // n->ninit stays + n->list = n->nbody; + n->nbody = nil; + n->rlist = nil; +} + +// Turn an OINLCALL into a single valued expression. +static void +inlconv2expr(Node **np) +{ + Node *n, *r; + n = *np; + r = n->rlist->n; + addinit(&r, concat(n->ninit, n->nbody)); + *np = r; +} + +// Turn the rlist (with the return values) of the OINLCALL in +// n into an expression list lumping the ninit and body +// containing the inlined statements on the first list element so +// order will be preserved Used in return, oas2func and call +// statements. +static NodeList* +inlconv2list(Node *n) +{ + NodeList *l; + + if(n->op != OINLCALL || n->rlist == nil) + fatal("inlconv2list %+N\n", n); + + l = n->rlist; + addinit(&l->n, concat(n->ninit, n->nbody)); + return l; +} + +static void +inlnodelist(NodeList *l) +{ + for(; l; l=l->next) + inlnode(&l->n); +} + +// inlnode recurses over the tree to find inlineable calls, which will +// be turned into OINLCALLs by mkinlcall. When the recursion comes +// back up will examine left, right, list, rlist, ninit, ntest, nincr, +// nbody and nelse and use one of the 4 inlconv/glue functions above +// to turn the OINLCALL into an expression, a statement, or patch it +// in to this nodes list or rlist as appropriate. +// NOTE it makes no sense to pass the glue functions down the +// recursion to the level where the OINLCALL gets created because they +// have to edit /this/ n, so you'd have to push that one down as well, +// but then you may as well do it here. so this is cleaner and +// shorter and less complicated. +static void +inlnode(Node **np) +{ + Node *n; + NodeList *l; + int lno; + + if(*np == nil) + return; + + n = *np; + + switch(n->op) { + case ODEFER: + case OPROC: + // inhibit inlining of their argument + switch(n->left->op) { + case OCALLFUNC: + case OCALLMETH: + n->left->etype = n->op; + } + + case OCLOSURE: + // TODO do them here instead of in lex.c phase 6b, so escape analysis + // can avoid more heapmoves. + return; + } + + lno = setlineno(n); + + inlnodelist(n->ninit); + for(l=n->ninit; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2stmt(l->n); + + inlnode(&n->left); + if(n->left && n->left->op == OINLCALL) + inlconv2expr(&n->left); + + inlnode(&n->right); + if(n->right && n->right->op == OINLCALL) + inlconv2expr(&n->right); + + inlnodelist(n->list); + switch(n->op) { + case OBLOCK: + for(l=n->list; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2stmt(l->n); + break; + + case ORETURN: + case OCALLFUNC: + case OCALLMETH: + case OCALLINTER: + // if we just replaced arg in f(arg()) or return arg with an inlined call + // and arg returns multiple values, glue as list + if(count(n->list) == 1 && n->list->n->op == OINLCALL && count(n->list->n->rlist) > 1) { + n->list = inlconv2list(n->list->n); + break; + } + + // fallthrough + default: + for(l=n->list; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2expr(&l->n); + } + + inlnodelist(n->rlist); + switch(n->op) { + case OAS2FUNC: + if(n->rlist->n->op == OINLCALL) { + n->rlist = inlconv2list(n->rlist->n); + n->op = OAS2; + n->typecheck = 0; + typecheck(np, Etop); + break; + } + + // fallthrough + default: + for(l=n->rlist; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2expr(&l->n); + + } + + inlnode(&n->ntest); + if(n->ntest && n->ntest->op == OINLCALL) + inlconv2expr(&n->ntest); + + inlnode(&n->nincr); + if(n->nincr && n->nincr->op == OINLCALL) + inlconv2stmt(n->nincr); + + inlnodelist(n->nbody); + for(l=n->nbody; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2stmt(l->n); + + inlnodelist(n->nelse); + for(l=n->nelse; l; l=l->next) + if(l->n->op == OINLCALL) + inlconv2stmt(l->n); + + // with all the branches out of the way, it is now time to + // transmogrify this node itself unless inhibited by the + // switch at the top of this function. + switch(n->op) { + case OCALLFUNC: + case OCALLMETH: + if (n->etype == OPROC || n->etype == ODEFER) + return; + } + + switch(n->op) { + case OCALLFUNC: + if(debug['m']>3) + print("%L:call to func %+N\n", n->lineno, n->left); + if(n->left->inl) // normal case + mkinlcall(np, n->left); + else if(n->left->op == ONAME && n->left->left && n->left->left->op == OTYPE && n->left->right && n->left->right->op == ONAME) // methods called as functions + if(n->left->sym->def) + mkinlcall(np, n->left->sym->def); + break; + + case OCALLMETH: + if(debug['m']>3) + print("%L:call to meth %lN\n", n->lineno, n->left->right); + // typecheck should have resolved ODOTMETH->type, whose nname points to the actual function. + if(n->left->type == T) + fatal("no function type for [%p] %+N\n", n->left, n->left); + + if(n->left->type->nname == N) + fatal("no function definition for [%p] %+T\n", n->left->type, n->left->type); + + mkinlcall(np, n->left->type->nname); + + break; + } + + lineno = lno; +} + +// if *np is a call, and fn is a function with an inlinable body, substitute *np with an OINLCALL. +// On return ninit has the parameter assignments, the nbody is the +// inlined function body and list, rlist contain the input, output +// parameters. +static void +mkinlcall(Node **np, Node *fn) +{ + int i; + Node *n, *call, *saveinlfn, *as, *m; + NodeList *dcl, *ll, *ninit, *body; + Type *t; + + if (fn->inl == nil) + return; + + if (fn == curfn || fn->defn == curfn) + return; + + if(debug['l']<2) + typecheckinl(fn); + + n = *np; + + // Bingo, we have a function node, and it has an inlineable body + if(debug['m']>1) + print("%L: inlining call to %S %#T { %#H }\n", n->lineno, fn->sym, fn->type, fn->inl); + else if(debug['m']) + print("%L: inlining call to %N\n", n->lineno, fn); + + if(debug['m']>2) + print("%L: Before inlining: %+N\n", n->lineno, n); + + saveinlfn = inlfn; + inlfn = fn; + + ninit = n->ninit; + + if (fn->defn) // local function + dcl = fn->defn->dcl; + else // imported function + dcl = fn->dcl; + + inlretvars = nil; + i = 0; + // Make temp names to use instead of the originals + for(ll = dcl; ll; ll=ll->next) + if(ll->n->op == ONAME) { + ll->n->inlvar = inlvar(ll->n); + ninit = list(ninit, nod(ODCL, ll->n->inlvar, N)); // otherwise gen won't emit the allocations for heapallocs + if (ll->n->class == PPARAMOUT) // we rely on the order being correct here + inlretvars = list(inlretvars, ll->n->inlvar); + } + + // anonymous return values, synthesize names for use in assignment that replaces return + if(inlretvars == nil && fn->type->outtuple > 0) + for(t = getoutargx(fn->type)->type; t; t = t->down) { + m = retvar(t, i++); + ninit = list(ninit, nod(ODCL, m, N)); + inlretvars = list(inlretvars, m); + } + + // assign arguments to the parameters' temp names + as = N; + if(fn->type->thistuple) { + t = getthisx(fn->type)->type; + if(t != T && t->nname != N && !isblank(t->nname) && !t->nname->inlvar) + fatal("missing inlvar for %N\n", t->nname); + + if(n->left->op == ODOTMETH) { + if(!n->left->left) + fatal("method call without receiver: %+N", n); + if(t == T) + fatal("method call unknown receiver type: %+N", n); + if(t->nname != N && !isblank(t->nname)) + as = nod(OAS, t->nname->inlvar, n->left->left); + else + as = nod(OAS, temp(t->type), n->left->left); + } else { // non-method call to method + if (!n->list) + fatal("non-method call to method without first arg: %+N", n); + if(t != T && t->nname != N && !isblank(t->nname)) + as = nod(OAS, t->nname->inlvar, n->list->n); + } + + if(as != N) { + typecheck(&as, Etop); + ninit = list(ninit, as); + } + } + + as = nod(OAS2, N, N); + if(fn->type->intuple > 1 && n->list && !n->list->next) { + // TODO check that n->list->n is a call? + // TODO: non-method call to T.meth(f()) where f returns t, args... + as->rlist = n->list; + for(t = getinargx(fn->type)->type; t; t=t->down) { + if(t->nname && !isblank(t->nname)) { + if(!t->nname->inlvar) + fatal("missing inlvar for %N\n", t->nname); + as->list = list(as->list, t->nname->inlvar); + } else { + as->list = list(as->list, temp(t->type)); + } + } + } else { + ll = n->list; + if(fn->type->thistuple && n->left->op != ODOTMETH) // non method call to method + ll=ll->next; // was handled above in if(thistuple) + + for(t = getinargx(fn->type)->type; t && ll; t=t->down) { + if(t->nname && !isblank(t->nname)) { + if(!t->nname->inlvar) + fatal("missing inlvar for %N\n", t->nname); + as->list = list(as->list, t->nname->inlvar); + as->rlist = list(as->rlist, ll->n); + } + ll=ll->next; + } + if(ll || t) + fatal("arg count mismatch: %#T vs %,H\n", getinargx(fn->type), n->list); + } + + if (as->rlist) { + typecheck(&as, Etop); + ninit = list(ninit, as); + } + + // zero the outparams + for(ll = inlretvars; ll; ll=ll->next) { + as = nod(OAS, ll->n, N); + typecheck(&as, Etop); + ninit = list(ninit, as); + } + + inlretlabel = newlabel(); + body = inlsubstlist(fn->inl); + + body = list(body, nod(OGOTO, inlretlabel, N)); // avoid 'not used' when function doesnt have return + body = list(body, nod(OLABEL, inlretlabel, N)); + + typechecklist(body, Etop); + + call = nod(OINLCALL, N, N); + call->ninit = ninit; + call->nbody = body; + call->rlist = inlretvars; + call->type = n->type; + call->typecheck = 1; + + setlno(call, n->lineno); + + *np = call; + + inlfn = saveinlfn; + + // transitive inlining + // TODO do this pre-expansion on fn->inl directly. requires + // either supporting exporting statemetns with complex ninits + // or saving inl and making inlinl + if(debug['l'] >= 5) { + body = fn->inl; + fn->inl = nil; // prevent infinite recursion + inlnodelist(call->nbody); + for(ll=call->nbody; ll; ll=ll->next) + if(ll->n->op == OINLCALL) + inlconv2stmt(ll->n); + fn->inl = body; + } + + if(debug['m']>2) + print("%L: After inlining %+N\n\n", n->lineno, *np); + +} + +// Every time we expand a function we generate a new set of tmpnames, +// PAUTO's in the calling functions, and link them off of the +// PPARAM's, PAUTOS and PPARAMOUTs of the called function. +static Node* +inlvar(Node *var) +{ + Node *n; + + if(debug['m']>3) + print("inlvar %+N\n", var); + + n = newname(var->sym); + n->type = var->type; + n->class = PAUTO; + n->used = 1; + n->curfn = curfn; // the calling function, not the called one + curfn->dcl = list(curfn->dcl, n); + return n; +} + +// Synthesize a variable to store the inlined function's results in. +static Node* +retvar(Type *t, int i) +{ + Node *n; + + snprint(namebuf, sizeof(namebuf), ".r%d", i); + n = newname(lookup(namebuf)); + n->type = t->type; + n->class = PAUTO; + n->used = 1; + n->curfn = curfn; // the calling function, not the called one + curfn->dcl = list(curfn->dcl, n); + return n; +} + +static Node* +newlabel(void) +{ + Node *n; + static int label; + + label++; + snprint(namebuf, sizeof(namebuf), ".inlret%.6d", label); + n = newname(lookup(namebuf)); + n->etype = 1; // flag 'safe' for escape analysis (no backjumps) + return n; +} + +// inlsubst and inlsubstlist recursively copy the body of the saved +// pristine ->inl body of the function while substituting references +// to input/output parameters with ones to the tmpnames, and +// substituting returns with assignments to the output. +static NodeList* +inlsubstlist(NodeList *ll) +{ + NodeList *l; + + l = nil; + for(; ll; ll=ll->next) + l = list(l, inlsubst(ll->n)); + return l; +} + +static Node* +inlsubst(Node *n) +{ + Node *m, *as; + NodeList *ll; + + if(n == N) + return N; + + switch(n->op) { + case ONAME: + if(n->inlvar) { // These will be set during inlnode + if (debug['m']>2) + print ("substituting name %+N -> %+N\n", n, n->inlvar); + return n->inlvar; + } + if (debug['m']>2) + print ("not substituting name %+N\n", n); + return n; + + case OLITERAL: + case OTYPE: + return n; + + case ORETURN: + // Since we don't handle bodies with closures, this return is guaranteed to belong to the current inlined function. + +// dump("Return before substitution", n); + m = nod(OGOTO, inlretlabel, N); + m->ninit = inlsubstlist(n->ninit); + + if(inlretvars && n->list) { + as = nod(OAS2, N, N); + // shallow copy or OINLCALL->rlist will be the same list, and later walk and typecheck may clobber that. + for(ll=inlretvars; ll; ll=ll->next) + as->list = list(as->list, ll->n); + as->rlist = inlsubstlist(n->list); + typecheck(&as, Etop); + m->ninit = list(m->ninit, as); + } + + typechecklist(m->ninit, Etop); + typecheck(&m, Etop); +// dump("Return after substitution", m); + return m; + } + + + m = nod(OXXX, N, N); + *m = *n; + m->ninit = nil; + + if(n->op == OCLOSURE) + fatal("cannot inline function containing closure: %+N", n); + + m->left = inlsubst(n->left); + m->right = inlsubst(n->right); + m->list = inlsubstlist(n->list); + m->rlist = inlsubstlist(n->rlist); + m->ninit = concat(m->ninit, inlsubstlist(n->ninit)); + m->ntest = inlsubst(n->ntest); + m->nincr = inlsubst(n->nincr); + m->nbody = inlsubstlist(n->nbody); + m->nelse = inlsubstlist(n->nelse); + + return m; +} + +// Plaster over linenumbers +static void +setlnolist(NodeList *ll, int lno) +{ + for(;ll;ll=ll->next) + setlno(ll->n, lno); +} + +static void +setlno(Node *n, int lno) +{ + if(!n) + return; + + // don't clobber names, unless they're freshly synthesized + if(n->op != ONAME || n->lineno == 0) + n->lineno = lno; + + setlno(n->left, lno); + setlno(n->right, lno); + setlnolist(n->list, lno); + setlnolist(n->rlist, lno); + setlnolist(n->ninit, lno); + setlno(n->ntest, lno); + setlno(n->nincr, lno); + setlnolist(n->nbody, lno); + setlnolist(n->nelse, lno); +} diff --git a/src/cmd/gc/lex.c b/src/cmd/gc/lex.c index 0290fb131..db6dfc3e1 100644 --- a/src/cmd/gc/lex.c +++ b/src/cmd/gc/lex.c @@ -19,6 +19,7 @@ int yyprev; int yylast; static void lexinit(void); +static void lexinit1(void); static void lexfini(void); static void yytinit(void); static int getc(void); @@ -29,6 +30,61 @@ static void addidir(char*); static int getlinepragma(void); static char *goos, *goarch, *goroot; +// Compiler experiments. +// These are controlled by the GOEXPERIMENT environment +// variable recorded when the compiler is built. +static struct { + char *name; + int *val; +} exper[] = { +// {"rune32", &rune32}, + {nil, nil}, +}; + +static void +addexp(char *s) +{ + int i; + + for(i=0; exper[i].name != nil; i++) { + if(strcmp(exper[i].name, s) == 0) { + *exper[i].val = 1; + return; + } + } + + print("unknown experiment %s\n", s); + exits("unknown experiment"); +} + +static void +setexp(void) +{ + char *f[20]; + int i, nf; + + // The makefile #defines GOEXPERIMENT for us. + nf = getfields(GOEXPERIMENT, f, nelem(f), 1, ","); + for(i=0; i<nf; i++) + addexp(f[i]); +} + +char* +expstring(void) +{ + int i; + static char buf[512]; + + strcpy(buf, "X"); + for(i=0; exper[i].name != nil; i++) + if(*exper[i].val) + seprint(buf+strlen(buf), buf+sizeof buf, ",%s", exper[i].name); + if(strlen(buf) == 1) + strcpy(buf, "X,none"); + buf[1] = ':'; + return buf; +} + // Our own isdigit, isspace, isalpha, isalnum that take care // of EOF and other out of range arguments. static int @@ -82,6 +138,7 @@ usage(void) print(" -N disable optimizer\n"); print(" -S print the assembly language\n"); print(" -V print the compiler version\n"); + print(" -W print the parse tree after typing\n"); print(" -d print declarations\n"); print(" -e no limit on number of errors printed\n"); print(" -f print stack frame structure\n"); @@ -91,9 +148,9 @@ usage(void) print(" -p assumed import path for this code\n"); print(" -s disable escape analysis\n"); print(" -u disable package unsafe\n"); - print(" -w print the parse tree after typing\n"); + print(" -w print type checking details\n"); print(" -x print lex tokens\n"); - exits(0); + exits("usage"); } void @@ -143,6 +200,8 @@ main(int argc, char *argv[]) goroot = getgoroot(); goos = getgoos(); goarch = thestring; + + setexp(); outfile = nil; ARGBEGIN { @@ -169,9 +228,19 @@ main(int argc, char *argv[]) break; case 'V': - print("%cg version %s\n", thechar, getgoversion()); + p = expstring(); + if(strcmp(p, "X:none") == 0) + p = ""; + print("%cg version %s%s%s\n", thechar, getgoversion(), *p ? " " : "", p); exits(0); } ARGEND + + // enable inlining. for now: + // default: inlining on. (debug['l'] == 1) + // -l: inlining off (debug['l'] == 0) + // -ll, -lll: inlining on again, with extra debugging (debug['l'] > 1) + if(debug['l'] <= 1) + debug['l'] = 1 - debug['l']; if(argc < 1) usage(); @@ -193,23 +262,14 @@ main(int argc, char *argv[]) *p = '/'; } - fmtinstall('O', Oconv); // node opcodes - fmtinstall('E', Econv); // etype opcodes - fmtinstall('J', Jconv); // all the node flags - fmtinstall('S', Sconv); // sym pointer - fmtinstall('T', Tconv); // type pointer - fmtinstall('N', Nconv); // node pointer - fmtinstall('Z', Zconv); // escaped string - fmtinstall('L', Lconv); // line number - fmtinstall('B', Bconv); // big numbers - fmtinstall('F', Fconv); // big float numbers - + fmtinstallgo(); betypeinit(); if(widthptr == 0) fatal("betypeinit failed"); lexinit(); typeinit(); + lexinit1(); yytinit(); blockgen = 1; @@ -283,11 +343,37 @@ main(int argc, char *argv[]) if(nsavederrors+nerrors) errorexit(); - // Phase 3b: escape analysis. - if(!debug['s']) + // Phase 4: Inlining + if (debug['l'] > 1) { + // Typecheck imported function bodies if debug['l'] > 1, + // otherwise lazily when used or re-exported. + for(l=importlist; l; l=l->next) + if (l->n->inl) { + saveerrors(); + typecheckinl(l->n); + } + + if(nsavederrors+nerrors) + errorexit(); + } + + if (debug['l']) { + // Find functions that can be inlined and clone them before walk expands them. + for(l=xtop; l; l=l->next) + if(l->n->op == ODCLFUNC) + caninl(l->n); + + // Expand inlineable calls in all functions + for(l=xtop; l; l=l->next) + if(l->n->op == ODCLFUNC) + inlcalls(l->n); + } + + // Phase 5: escape analysis. + if(!debug['N']) escapes(); - // Phase 4: Compile function bodies. + // Phase 6: Compile top level functions. for(l=xtop; l; l=l->next) if(l->n->op == ODCLFUNC) funccompile(l->n, 0); @@ -295,16 +381,18 @@ main(int argc, char *argv[]) if(nsavederrors+nerrors == 0) fninit(xtop); - // Phase 4b: Compile all closures. + // Phase 6b: Compile all closures. while(closures) { l = closures; closures = nil; for(; l; l=l->next) { + if (debug['l']) + inlcalls(l->n); funccompile(l->n, 1); } } - // Phase 5: check external declarations. + // Phase 7: check external declarations. for(l=externdcl; l; l=l->next) if(l->n->op == ONAME) typecheck(&l->n, Erv); @@ -329,18 +417,30 @@ saveerrors(void) nerrors = 0; } +/* + * macro to portably read/write archive header. + * 'cmd' is read/write/Bread/Bwrite, etc. + */ +#define HEADER_IO(cmd, f, h) cmd(f, h.name, sizeof(h.name)) != sizeof(h.name)\ + || cmd(f, h.date, sizeof(h.date)) != sizeof(h.date)\ + || cmd(f, h.uid, sizeof(h.uid)) != sizeof(h.uid)\ + || cmd(f, h.gid, sizeof(h.gid)) != sizeof(h.gid)\ + || cmd(f, h.mode, sizeof(h.mode)) != sizeof(h.mode)\ + || cmd(f, h.size, sizeof(h.size)) != sizeof(h.size)\ + || cmd(f, h.fmag, sizeof(h.fmag)) != sizeof(h.fmag) + static int arsize(Biobuf *b, char *name) { - struct ar_hdr *a; + struct ar_hdr a; - if((a = Brdline(b, '\n')) == nil) - return -1; - if(Blinelen(b) != sizeof(struct ar_hdr)) + if (HEADER_IO(Bread, b, a)) return -1; - if(strncmp(a->name, name, strlen(name)) != 0) + + if(strncmp(a.name, name, strlen(name)) != 0) return -1; - return atoi(a->size); + + return atoi(a.size); } static int @@ -537,7 +637,7 @@ importfile(Val *f, int line) yyerror("import %s: not a go object file", file); errorexit(); } - q = smprint("%s %s %s", getgoos(), thestring, getgoversion()); + q = smprint("%s %s %s %s", getgoos(), thestring, getgoversion(), expstring()); if(strcmp(p+10, q) != 0) { yyerror("import %s: object is [%s] expected [%s]", file, p+10, q); errorexit(); @@ -744,6 +844,8 @@ l0: ncp += ncp; } c = getr(); + if(c == '\r') + continue; if(c == EOF) { yyerror("eof in string"); break; @@ -777,7 +879,7 @@ l0: } yylval.val.u.xval = mal(sizeof(*yylval.val.u.xval)); mpmovecfix(yylval.val.u.xval, v); - yylval.val.ctype = CTINT; + yylval.val.ctype = CTRUNE; DBG("lex: codepoint literal\n"); strcpy(litbuf, "string literal"); return LLITERAL; @@ -1035,7 +1137,7 @@ lx: return c; asop: - yylval.lint = c; // rathole to hold which asop + yylval.i = c; // rathole to hold which asop DBG("lex: TOKEN ASOP %c\n", c); return LASOP; @@ -1273,7 +1375,7 @@ static int getlinepragma(void) { int i, c, n; - char *cp, *ep; + char *cp, *ep, *linep; Hist *h; for(i=0; i<5; i++) { @@ -1284,32 +1386,36 @@ getlinepragma(void) cp = lexbuf; ep = lexbuf+sizeof(lexbuf)-5; + linep = nil; for(;;) { c = getr(); - if(c == '\n' || c == EOF) + if(c == EOF) goto out; + if(c == '\n') + break; if(c == ' ') continue; if(c == ':') - break; + linep = cp; if(cp < ep) *cp++ = c; } *cp = 0; + if(linep == nil || linep >= ep) + goto out; + *linep++ = '\0'; n = 0; - for(;;) { - c = getr(); - if(!yy_isdigit(c)) - break; - n = n*10 + (c-'0'); + for(cp=linep; *cp; cp++) { + if(*cp < '0' || *cp > '9') + goto out; + n = n*10 + *cp - '0'; if(n > 1e8) { yyerror("line number out of range"); errorexit(); } } - - if(c != '\n' || n <= 0) + if(n <= 0) goto out; // try to avoid allocating file name over and over @@ -1360,7 +1466,7 @@ yylex(void) // Track last two tokens returned by yylex. yyprev = yylast; yylast = lx; - return lx; + return lx; } static int @@ -1587,7 +1693,6 @@ static struct "complex128", LNAME, TCOMPLEX128, OXXX, "bool", LNAME, TBOOL, OXXX, - "byte", LNAME, TUINT8, OXXX, "string", LNAME, TSTRING, OXXX, "any", LNAME, TANY, OXXX, @@ -1618,11 +1723,12 @@ static struct "type", LTYPE, Txxx, OXXX, "var", LVAR, Txxx, OXXX, - "append", LNAME, Txxx, OAPPEND, + "append", LNAME, Txxx, OAPPEND, "cap", LNAME, Txxx, OCAP, "close", LNAME, Txxx, OCLOSE, "complex", LNAME, Txxx, OCOMPLEX, "copy", LNAME, Txxx, OCOPY, + "delete", LNAME, Txxx, ODELETE, "imag", LNAME, Txxx, OIMAG, "len", LNAME, Txxx, OLEN, "make", LNAME, Txxx, OMAKE, @@ -1647,6 +1753,7 @@ lexinit(void) Sym *s, *s1; Type *t; int etype; + Val v; /* * initialize basic types array @@ -1675,6 +1782,16 @@ lexinit(void) s1->def = typenod(t); continue; } + + etype = syms[i].op; + if(etype != OXXX) { + s1 = pkglookup(syms[i].name, builtinpkg); + s1->lexical = LNAME; + s1->def = nod(ONAME, N, N); + s1->def->sym = s1; + s1->def->etype = etype; + s1->def->builtin = 1; + } } // logically, the type of a string literal. @@ -1702,6 +1819,77 @@ lexinit(void) types[TBLANK] = typ(TBLANK); s->def->type = types[TBLANK]; nblank = s->def; + + s = pkglookup("_", builtinpkg); + s->block = -100; + s->def = nod(ONAME, N, N); + s->def->sym = s; + types[TBLANK] = typ(TBLANK); + s->def->type = types[TBLANK]; + + types[TNIL] = typ(TNIL); + s = pkglookup("nil", builtinpkg); + v.ctype = CTNIL; + s->def = nodlit(v); + s->def->sym = s; +} + +static void +lexinit1(void) +{ + Sym *s, *s1; + Type *t, *f, *rcvr, *in, *out; + + // t = interface { Error() string } + rcvr = typ(TSTRUCT); + rcvr->type = typ(TFIELD); + rcvr->type->type = ptrto(typ(TSTRUCT)); + rcvr->funarg = 1; + in = typ(TSTRUCT); + in->funarg = 1; + out = typ(TSTRUCT); + out->type = typ(TFIELD); + out->type->type = types[TSTRING]; + out->funarg = 1; + f = typ(TFUNC); + *getthis(f) = rcvr; + *getoutarg(f) = out; + *getinarg(f) = in; + f->thistuple = 1; + f->intuple = 0; + f->outnamed = 0; + f->outtuple = 1; + t = typ(TINTER); + t->type = typ(TFIELD); + t->type->sym = lookup("Error"); + t->type->type = f; + + // error type + s = lookup("error"); + s->lexical = LNAME; + errortype = t; + errortype->sym = s; + s1 = pkglookup("error", builtinpkg); + s1->lexical = LNAME; + s1->def = typenod(errortype); + + // byte alias + s = lookup("byte"); + s->lexical = LNAME; + bytetype = typ(TUINT8); + bytetype->sym = s; + s1 = pkglookup("byte", builtinpkg); + s1->lexical = LNAME; + s1->def = typenod(bytetype); + + // rune alias + s = lookup("rune"); + s->lexical = LNAME; + runetype = typ(TINT32); + runetype->sym = s; + s1 = pkglookup("rune", builtinpkg); + s1->lexical = LNAME; + s1->def = typenod(runetype); } static void @@ -1739,7 +1927,18 @@ lexfini(void) // there's only so much table-driven we can handle. // these are special cases. - types[TNIL] = typ(TNIL); + s = lookup("byte"); + if(s->def == N) + s->def = typenod(bytetype); + + s = lookup("error"); + if(s->def == N) + s->def = typenod(errortype); + + s = lookup("rune"); + if(s->def == N) + s->def = typenod(runetype); + s = lookup("nil"); if(s->def == N) { v.ctype = CTNIL; diff --git a/src/cmd/gc/obj.c b/src/cmd/gc/obj.c index 730b42671..aae566dbb 100644 --- a/src/cmd/gc/obj.c +++ b/src/cmd/gc/obj.c @@ -23,7 +23,7 @@ dumpobj(void) errorexit(); } - Bprint(bout, "go object %s %s %s\n", getgoos(), thestring, getgoversion()); + Bprint(bout, "go object %s %s %s %s\n", getgoos(), thestring, getgoversion(), expstring()); Bprint(bout, " exports automatically generated from\n"); Bprint(bout, " %s in package \"%s\"\n", curio.infile, localpkg->name); dumpexport(); @@ -52,7 +52,7 @@ dumpglobls(void) continue; if(n->type == T) - fatal("external %#N nil type\n", n); + fatal("external %N nil type\n", n); if(n->class == PFUNC) continue; if(n->sym->pkg != localpkg) @@ -267,6 +267,7 @@ stringsym(char *s, int len) if(sym->flags & SymUniq) return sym; sym->flags |= SymUniq; + sym->def = newname(sym); off = 0; diff --git a/src/cmd/gc/order.c b/src/cmd/gc/order.c new file mode 100644 index 000000000..2cab5fb95 --- /dev/null +++ b/src/cmd/gc/order.c @@ -0,0 +1,358 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Rewrite tree to use separate statements to enforce +// order of evaluation. Makes walk easier, because it +// can (after this runs) reorder at will within an expression. + +#include <u.h> +#include <libc.h> +#include "go.h" + +static void orderstmt(Node*, NodeList**); +static void orderstmtlist(NodeList*, NodeList**); +static void orderblock(NodeList **l); +static void orderexpr(Node**, NodeList**); +static void orderexprlist(NodeList*, NodeList**); + +void +order(Node *fn) +{ + orderblock(&fn->nbody); +} + +static void +orderstmtlist(NodeList *l, NodeList **out) +{ + for(; l; l=l->next) + orderstmt(l->n, out); +} + +// Order the block of statements *l onto a new list, +// and then replace *l with that list. +static void +orderblock(NodeList **l) +{ + NodeList *out; + + out = nil; + orderstmtlist(*l, &out); + *l = out; +} + +// Order the side effects in *np and leave them as +// the init list of the final *np. +static void +orderexprinplace(Node **np) +{ + Node *n; + NodeList *out; + + n = *np; + out = nil; + orderexpr(&n, &out); + addinit(&n, out); + *np = n; +} + +// Like orderblock, but applied to a single statement. +static void +orderstmtinplace(Node **np) +{ + Node *n; + NodeList *out; + + n = *np; + out = nil; + orderstmt(n, &out); + *np = liststmt(out); +} + +// Move n's init list to *out. +static void +orderinit(Node *n, NodeList **out) +{ + orderstmtlist(n->ninit, out); + n->ninit = nil; +} + +// Is the list l actually just f() for a multi-value function? +static int +ismulticall(NodeList *l) +{ + Node *n; + + // one arg only + if(l == nil || l->next != nil) + return 0; + n = l->n; + + // must be call + switch(n->op) { + default: + return 0; + case OCALLFUNC: + case OCALLMETH: + case OCALLINTER: + break; + } + + // call must return multiple values + return n->left->type->outtuple > 1; +} + +// n is a multi-value function call. Add t1, t2, .. = n to out +// and return the list t1, t2, ... +static NodeList* +copyret(Node *n, NodeList **out) +{ + Type *t; + Node *tmp, *as; + NodeList *l1, *l2; + Iter tl; + + if(n->type->etype != TSTRUCT || !n->type->funarg) + fatal("copyret %T %d", n->type, n->left->type->outtuple); + + l1 = nil; + l2 = nil; + for(t=structfirst(&tl, &n->type); t; t=structnext(&tl)) { + tmp = temp(t->type); + l1 = list(l1, tmp); + l2 = list(l2, tmp); + } + + as = nod(OAS2, N, N); + as->list = l1; + as->rlist = list1(n); + typecheck(&as, Etop); + orderstmt(as, out); + + return l2; +} + +static void +ordercallargs(NodeList **l, NodeList **out) +{ + if(ismulticall(*l)) { + // return f() where f() is multiple values. + *l = copyret((*l)->n, out); + } else { + orderexprlist(*l, out); + } +} + +static void +ordercall(Node *n, NodeList **out) +{ + orderexpr(&n->left, out); + ordercallargs(&n->list, out); +} + +static void +orderstmt(Node *n, NodeList **out) +{ + int lno; + NodeList *l; + Node *r; + + if(n == N) + return; + + lno = setlineno(n); + + orderinit(n, out); + + switch(n->op) { + default: + fatal("orderstmt %O", n->op); + + case OAS2: + case OAS2DOTTYPE: + case OAS2MAPR: + case OAS: + case OASOP: + case OCLOSE: + case OCOPY: + case ODELETE: + case OPANIC: + case OPRINT: + case OPRINTN: + case ORECOVER: + case ORECV: + case OSEND: + orderexpr(&n->left, out); + orderexpr(&n->right, out); + orderexprlist(n->list, out); + orderexprlist(n->rlist, out); + *out = list(*out, n); + break; + + case OAS2FUNC: + // Special: avoid copy of func call n->rlist->n. + orderexprlist(n->list, out); + ordercall(n->rlist->n, out); + *out = list(*out, n); + break; + + case OAS2RECV: + // Special: avoid copy of receive. + orderexprlist(n->list, out); + orderexpr(&n->rlist->n->left, out); // arg to recv + *out = list(*out, n); + break; + + case OBLOCK: + case OEMPTY: + // Special: does not save n onto out. + orderstmtlist(n->list, out); + break; + + case OBREAK: + case OCONTINUE: + case ODCL: + case ODCLCONST: + case ODCLTYPE: + case OFALL: + case_OFALL: + case OGOTO: + case OLABEL: + // Special: n->left is not an expression; save as is. + *out = list(*out, n); + break; + + case OCALLFUNC: + case OCALLINTER: + case OCALLMETH: + // Special: handle call arguments. + ordercall(n, out); + *out = list(*out, n); + break; + + case ODEFER: + case OPROC: + // Special: order arguments to inner call but not call itself. + ordercall(n->left, out); + *out = list(*out, n); + break; + + case OFOR: + orderexprinplace(&n->ntest); + orderstmtinplace(&n->nincr); + orderblock(&n->nbody); + *out = list(*out, n); + break; + + case OIF: + orderexprinplace(&n->ntest); + orderblock(&n->nbody); + orderblock(&n->nelse); + *out = list(*out, n); + break; + + case ORANGE: + orderexpr(&n->right, out); + for(l=n->list; l; l=l->next) + orderexprinplace(&l->n); + orderblock(&n->nbody); + *out = list(*out, n); + break; + + case ORETURN: + ordercallargs(&n->list, out); + *out = list(*out, n); + break; + + case OSELECT: + for(l=n->list; l; l=l->next) { + if(l->n->op != OXCASE) + fatal("order select case %O", l->n->op); + r = l->n->left; + if(r == nil) + continue; + switch(r->op) { + case OSELRECV: + case OSELRECV2: + orderexprinplace(&r->left); + orderexprinplace(&r->ntest); + orderexpr(&r->right->left, out); + break; + case OSEND: + orderexpr(&r->left, out); + orderexpr(&r->right, out); + break; + } + } + *out = list(*out, n); + break; + + case OSWITCH: + orderexpr(&n->ntest, out); + for(l=n->list; l; l=l->next) { + if(l->n->op != OXCASE) + fatal("order switch case %O", l->n->op); + orderexpr(&l->n->left, &l->n->ninit); + } + *out = list(*out, n); + break; + + case OXFALL: + yyerror("fallthrough statement out of place"); + n->op = OFALL; + goto case_OFALL; + } + + lineno = lno; +} + +static void +orderexprlist(NodeList *l, NodeList **out) +{ + for(; l; l=l->next) + orderexpr(&l->n, out); +} + +static void +orderexpr(Node **np, NodeList **out) +{ + Node *n; + int lno; + + n = *np; + if(n == N) + return; + + lno = setlineno(n); + orderinit(n, out); + + switch(n->op) { + default: + orderexpr(&n->left, out); + orderexpr(&n->right, out); + orderexprlist(n->list, out); + orderexprlist(n->rlist, out); + break; + + case OANDAND: + case OOROR: + orderexpr(&n->left, out); + orderexprinplace(&n->right); + break; + + case OCALLFUNC: + case OCALLMETH: + case OCALLINTER: + ordercall(n, out); + n = copyexpr(n, n->type, out); + break; + + case ORECV: + n = copyexpr(n, n->type, out); + break; + } + + lineno = lno; + + *np = n; +} diff --git a/src/cmd/gc/pgen.c b/src/cmd/gc/pgen.c index d16481b66..8e65ba22d 100644 --- a/src/cmd/gc/pgen.c +++ b/src/cmd/gc/pgen.c @@ -54,7 +54,11 @@ compile(Node *fn) t = structnext(&save); } } - + + order(curfn); + if(nerrors != 0) + goto ret; + hasdefer = 0; walk(curfn); if(nerrors != 0) @@ -70,6 +74,8 @@ compile(Node *fn) nodconst(&nod1, types[TINT32], 0); ptxt = gins(ATEXT, isblank(curfn->nname) ? N : curfn->nname, &nod1); + if(fn->dupok) + ptxt->TEXTFLAG = DUPOK; afunclit(&ptxt->from); ginit(); @@ -117,6 +123,10 @@ compile(Node *fn) if(0) print("allocauto: %lld to %lld\n", oldstksize, (vlong)stksize); + setlineno(curfn); + if(stksize+maxarg > (1ULL<<31)) + yyerror("stack frame too large (>2GB)"); + defframe(ptxt); if(0) diff --git a/src/cmd/gc/print.c b/src/cmd/gc/print.c deleted file mode 100644 index 37e3e7ac0..000000000 --- a/src/cmd/gc/print.c +++ /dev/null @@ -1,486 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include <u.h> -#include <libc.h> -#include "go.h" - -enum -{ - PFIXME = 0, -}; - -void -exprlistfmt(Fmt *f, NodeList *l) -{ - for(; l; l=l->next) { - exprfmt(f, l->n, 0); - if(l->next) - fmtprint(f, ", "); - } -} - -void -exprfmt(Fmt *f, Node *n, int prec) -{ - int nprec; - char *p; - - nprec = 0; - if(n == nil) { - fmtprint(f, "<nil>"); - return; - } - - if(n->implicit) { - exprfmt(f, n->left, prec); - return; - } - - switch(n->op) { - case OAPPEND: - case ONAME: - case ONONAME: - case OPACK: - case OLITERAL: - case ODOT: - case ODOTPTR: - case ODOTINTER: - case ODOTMETH: - case ODOTTYPE: - case ODOTTYPE2: - case OXDOT: - case OARRAYBYTESTR: - case OCAP: - case OCLOSE: - case OCOPY: - case OLEN: - case OMAKE: - case ONEW: - case OPANIC: - case OPRINT: - case OPRINTN: - case OCALL: - case OCALLMETH: - case OCALLINTER: - case OCALLFUNC: - case OCONV: - case OCONVNOP: - case OMAKESLICE: - case ORUNESTR: - case OADDR: - case OCOM: - case OIND: - case OMINUS: - case ONOT: - case OPLUS: - case ORECV: - case OCONVIFACE: - case OTPAREN: - case OINDEX: - case OINDEXMAP: - case OPAREN: - nprec = 7; - break; - - case OMUL: - case ODIV: - case OMOD: - case OLSH: - case ORSH: - case OAND: - case OANDNOT: - nprec = 6; - break; - - case OADD: - case OSUB: - case OOR: - case OXOR: - nprec = 5; - break; - - case OEQ: - case OLT: - case OLE: - case OGE: - case OGT: - case ONE: - nprec = 4; - break; - - case OSEND: - nprec = 3; - break; - - case OANDAND: - nprec = 2; - break; - - case OOROR: - nprec = 1; - break; - - case OTYPE: - if(n->sym != S) - nprec = 7; - break; - } - - if(prec > nprec) - fmtprint(f, "("); - - switch(n->op) { - default: - bad: - fmtprint(f, "(node %O)", n->op); - break; - - case OPAREN: - fmtprint(f, "(%#N)", n->left); - break; - - case ODDDARG: - fmtprint(f, "... argument"); - break; - - case OREGISTER: - fmtprint(f, "%R", n->val.u.reg); - break; - - case OLITERAL: - if(n->sym != S) { - fmtprint(f, "%S", n->sym); - break; - } - switch(n->val.ctype) { - default: - goto bad; - case CTINT: - fmtprint(f, "%B", n->val.u.xval); - break; - case CTBOOL: - if(n->val.u.bval) - fmtprint(f, "true"); - else - fmtprint(f, "false"); - break; - case CTCPLX: - fmtprint(f, "%.17g+%.17gi", - mpgetflt(&n->val.u.cval->real), - mpgetflt(&n->val.u.cval->imag)); - break; - case CTFLT: - fmtprint(f, "%.17g", mpgetflt(n->val.u.fval)); - break; - case CTSTR: - fmtprint(f, "\"%Z\"", n->val.u.sval); - break; - case CTNIL: - fmtprint(f, "nil"); - break; - } - break; - - case ONAME: - case OPACK: - case ONONAME: - fmtprint(f, "%S", n->sym); - break; - - case OTYPE: - if(n->type == T && n->sym != S) { - fmtprint(f, "%S", n->sym); - break; - } - fmtprint(f, "%T", n->type); - break; - - case OTARRAY: - fmtprint(f, "[]"); - exprfmt(f, n->left, PFIXME); - break; - - case OTPAREN: - fmtprint(f, "("); - exprfmt(f, n->left, 0); - fmtprint(f, ")"); - break; - - case OTMAP: - fmtprint(f, "map["); - exprfmt(f, n->left, 0); - fmtprint(f, "] "); - exprfmt(f, n->right, 0); - break; - - case OTCHAN: - if(n->etype == Crecv) - fmtprint(f, "<-"); - fmtprint(f, "chan"); - if(n->etype == Csend) { - fmtprint(f, "<- "); - exprfmt(f, n->left, 0); - } else { - fmtprint(f, " "); - if(n->left->op == OTCHAN && n->left->sym == S && n->left->etype == Crecv) { - fmtprint(f, "("); - exprfmt(f, n->left, 0); - fmtprint(f, ")"); - } else - exprfmt(f, n->left, 0); - } - break; - - case OTSTRUCT: - fmtprint(f, "<struct>"); - break; - - case OTINTER: - fmtprint(f, "<inter>"); - break; - - case OTFUNC: - fmtprint(f, "<func>"); - break; - - case OAS: - exprfmt(f, n->left, 0); - fmtprint(f, " = "); - exprfmt(f, n->right, 0); - break; - - case OASOP: - exprfmt(f, n->left, 0); - fmtprint(f, " %#O= ", n->etype); - exprfmt(f, n->right, 0); - break; - - case OAS2: - case OAS2DOTTYPE: - case OAS2FUNC: - case OAS2MAPR: - case OAS2MAPW: - case OAS2RECV: - exprlistfmt(f, n->list); - fmtprint(f, " = "); - exprlistfmt(f, n->rlist); - break; - - case OADD: - case OANDAND: - case OANDNOT: - case ODIV: - case OEQ: - case OGE: - case OGT: - case OLE: - case OLT: - case OLSH: - case OMOD: - case OMUL: - case ONE: - case OOR: - case OOROR: - case ORSH: - case OSEND: - case OSUB: - case OXOR: - exprfmt(f, n->left, nprec); - fmtprint(f, " %#O ", n->op); - exprfmt(f, n->right, nprec+1); - break; - - case OADDR: - case OCOM: - case OIND: - case OMINUS: - case ONOT: - case OPLUS: - case ORECV: - fmtprint(f, "%#O", n->op); - if((n->op == OMINUS || n->op == OPLUS) && n->left->op == n->op) - fmtprint(f, " "); - exprfmt(f, n->left, 0); - break; - - case OCLOSURE: - fmtprint(f, "func literal"); - break; - - case OCOMPLIT: - fmtprint(f, "composite literal"); - break; - - case OARRAYLIT: - if(isslice(n->type)) - fmtprint(f, "slice literal"); - else - fmtprint(f, "array literal"); - break; - - case OMAPLIT: - fmtprint(f, "map literal"); - break; - - case OSTRUCTLIT: - fmtprint(f, "struct literal"); - break; - - case OXDOT: - case ODOT: - case ODOTPTR: - case ODOTINTER: - case ODOTMETH: - exprfmt(f, n->left, 7); - if(n->right == N || n->right->sym == S) - fmtprint(f, ".<nil>"); - else { - // skip leading type· in method name - p = utfrrune(n->right->sym->name, 0xb7); - if(p) - p+=2; - else - p = n->right->sym->name; - fmtprint(f, ".%s", p); - } - break; - - case ODOTTYPE: - case ODOTTYPE2: - exprfmt(f, n->left, 7); - fmtprint(f, ".("); - if(n->right != N) - exprfmt(f, n->right, 0); - else - fmtprint(f, "%T", n->type); - fmtprint(f, ")"); - break; - - case OINDEX: - case OINDEXMAP: - exprfmt(f, n->left, 7); - fmtprint(f, "["); - exprfmt(f, n->right, 0); - fmtprint(f, "]"); - break; - - case OSLICE: - case OSLICESTR: - case OSLICEARR: - exprfmt(f, n->left, 7); - fmtprint(f, "["); - if(n->right->left != N) - exprfmt(f, n->right->left, 0); - fmtprint(f, ":"); - if(n->right->right != N) - exprfmt(f, n->right->right, 0); - fmtprint(f, "]"); - break; - - case OCALL: - case OCALLFUNC: - case OCALLINTER: - case OCALLMETH: - exprfmt(f, n->left, 7); - fmtprint(f, "("); - exprlistfmt(f, n->list); - if(n->isddd) - fmtprint(f, "..."); - fmtprint(f, ")"); - break; - - case OCOMPLEX: - fmtprint(f, "complex("); - exprfmt(f, n->left, 0); - fmtprint(f, ", "); - exprfmt(f, n->right, 0); - fmtprint(f, ")"); - break; - - case OREAL: - fmtprint(f, "real("); - exprfmt(f, n->left, 0); - fmtprint(f, ")"); - break; - - case OIMAG: - fmtprint(f, "imag("); - exprfmt(f, n->left, 0); - fmtprint(f, ")"); - break; - - case OCONV: - case OCONVIFACE: - case OCONVNOP: - case OARRAYBYTESTR: - case OSTRARRAYBYTE: - case ORUNESTR: - if(n->type == T || n->type->sym == S) - fmtprint(f, "(%T)(", n->type); - else - fmtprint(f, "%T(", n->type); - if(n->left == N) - exprlistfmt(f, n->list); - else - exprfmt(f, n->left, 0); - fmtprint(f, ")"); - break; - - case OAPPEND: - case OCAP: - case OCLOSE: - case OLEN: - case OCOPY: - case OMAKE: - case ONEW: - case OPANIC: - case OPRINT: - case OPRINTN: - fmtprint(f, "%#O(", n->op); - if(n->left) - exprfmt(f, n->left, 0); - else - exprlistfmt(f, n->list); - fmtprint(f, ")"); - break; - - case OMAKESLICE: - fmtprint(f, "make(%#T, ", n->type); - exprfmt(f, n->left, 0); - if(count(n->list) > 2) { - fmtprint(f, ", "); - exprfmt(f, n->right, 0); - } - fmtprint(f, ")"); - break; - - case OMAKEMAP: - case OMAKECHAN: - fmtprint(f, "make(%#T)", n->type); - break; - - // Some statements - - case ODCL: - fmtprint(f, "var %S %#T", n->left->sym, n->left->type); - break; - - case ORETURN: - fmtprint(f, "return "); - exprlistfmt(f, n->list); - break; - - case OPROC: - fmtprint(f, "go %#N", n->left); - break; - - case ODEFER: - fmtprint(f, "defer %#N", n->left); - break; - } - - if(prec > nprec) - fmtprint(f, ")"); -} diff --git a/src/cmd/gc/range.c b/src/cmd/gc/range.c index 5cbafd895..9bcd833a7 100644 --- a/src/cmd/gc/range.c +++ b/src/cmd/gc/range.c @@ -32,7 +32,7 @@ typecheckrange(Node *n) switch(t->etype) { default: - yyerror("cannot range over %+N", n->right); + yyerror("cannot range over %lN", n->right); goto out; case TARRAY: @@ -46,6 +46,10 @@ typecheckrange(Node *n) break; case TCHAN: + if(!(t->chan & Crecv)) { + yyerror("invalid operation: range %N (receive from send-only type %T)", n->right, n->right->type); + goto out; + } t1 = t->type; t2 = nil; if(count(n->list) == 2) @@ -54,7 +58,7 @@ typecheckrange(Node *n) case TSTRING: t1 = types[TINT]; - t2 = types[TINT]; + t2 = runetype; break; } @@ -71,12 +75,12 @@ typecheckrange(Node *n) if(v1->defn == n) v1->type = t1; else if(v1->type != T && assignop(t1, v1->type, &why) == 0) - yyerror("cannot assign type %T to %+N in range%s", t1, v1, why); + yyerror("cannot assign type %T to %lN in range%s", t1, v1, why); if(v2) { if(v2->defn == n) v2->type = t2; else if(v2->type != T && assignop(t2, v2->type, &why) == 0) - yyerror("cannot assign type %T to %+N in range%s", t2, v2, why); + yyerror("cannot assign type %T to %lN in range%s", t2, v2, why); } out: @@ -163,7 +167,9 @@ walkrange(Node *n) case TMAP: th = typ(TARRAY); th->type = ptrto(types[TUINT8]); - th->bound = (sizeof(struct Hiter) + widthptr - 1) / widthptr; + // see ../../pkg/runtime/hashmap.h:/hash_iter + // Size in words. + th->bound = 5 + 4*3 + 4*4/widthptr; hit = temp(th); fn = syslook("mapiterinit", 1); @@ -216,7 +222,7 @@ walkrange(Node *n) if(v2 == N) a = nod(OAS, hv1, mkcall("stringiter", types[TINT], nil, ha, hv1)); else { - hv2 = temp(types[TINT]); + hv2 = temp(runetype); a = nod(OAS2, N, N); a->list = list(list1(hv1), hv2); fn = syslook("stringiter2", 0); diff --git a/src/cmd/gc/reflect.c b/src/cmd/gc/reflect.c index ca7d08e51..49aca0906 100644 --- a/src/cmd/gc/reflect.c +++ b/src/cmd/gc/reflect.c @@ -13,6 +13,7 @@ static NodeList* signatlist; static Sym* dtypesym(Type*); static Sym* weaktypesym(Type*); +static Sym* dalgsym(Type*); static int sigcmp(Sig *a, Sig *b) @@ -158,10 +159,13 @@ methods(Type *t) // generating code if necessary. a = nil; for(f=mt->xmethod; f; f=f->down) { - if(f->type->etype != TFUNC) - continue; if(f->etype != TFIELD) - fatal("methods: not field"); + fatal("methods: not field %T", f); + if (f->type->etype != TFUNC || f->type->thistuple == 0) + fatal("non-method on %T method %S %T\n", mt, f->sym, f); + if (!getthisx(f->type)->type) + fatal("receiver with no type on %T method %S %T\n", mt, f->sym, f); + method = f->sym; if(method == nil) continue; @@ -353,7 +357,7 @@ dextratype(Sym *sym, int off, Type *t, int ptroff) s = sym; if(t->sym) { ot = dgostringptr(s, ot, t->sym->name); - if(t != types[t->etype]) + if(t != types[t->etype] && t != errortype) ot = dgopkgpath(s, ot, t->sym->pkg); else ot = dgostringptr(s, ot, nil); @@ -550,12 +554,20 @@ haspointers(Type *t) static int dcommontype(Sym *s, int ot, Type *t) { - int i; - Sym *sptr; + int i, alg, sizeofAlg; + Sym *sptr, *algsym; + static Sym *algarray; char *p; + sizeofAlg = 4*widthptr; + if(algarray == nil) + algarray = pkglookup("algarray", runtimepkg); + alg = algtype(t); + algsym = S; + if(alg < 0) + algsym = dalgsym(t); + dowidth(t); - if(t->sym != nil && !isptr[t->etype]) sptr = dtypesym(ptrto(t)); else @@ -583,7 +595,7 @@ dcommontype(Sym *s, int ot, Type *t) // } ot = duintptr(s, ot, t->width); ot = duint32(s, ot, typehash(t)); - ot = duint8(s, ot, algtype(t)); + ot = duint8(s, ot, 0); // unused ot = duint8(s, ot, t->align); // align ot = duint8(s, ot, t->align); // fieldAlign i = kinds[t->etype]; @@ -592,9 +604,12 @@ dcommontype(Sym *s, int ot, Type *t) if(!haspointers(t)) i |= KindNoPointers; ot = duint8(s, ot, i); // kind - longsymnames = 1; - p = smprint("%-T", t); - longsymnames = 0; + if(alg >= 0) + ot = dsymptr(s, ot, algarray, alg*sizeofAlg); + else + ot = dsymptr(s, ot, algsym, 0); + p = smprint("%-uT", t); + //print("dcommontype: %s\n", p); ot = dgostringptr(s, ot, p); // string free(p); @@ -614,8 +629,22 @@ typesym(Type *t) char *p; Sym *s; - p = smprint("%#-T", t); + p = smprint("%-T", t); + s = pkglookup(p, typepkg); + //print("typesym: %s -> %+S\n", p, s); + free(p); + return s; +} + +Sym* +typesymprefix(char *prefix, Type *t) +{ + char *p; + Sym *s; + + p = smprint("%s.%-T", prefix, t); s = pkglookup(p, typepkg); + //print("algsym: %s -> %+S\n", p, s); free(p); return s; } @@ -662,8 +691,9 @@ weaktypesym(Type *t) weak->prefix = "weak.type"; // not weak%2etype } - p = smprint("%#-T", t); + p = smprint("%-T", t); s = pkglookup(p, weak); + //print("weaktypesym: %s -> %+S\n", p, s); free(p); return s; } @@ -692,8 +722,13 @@ dtypesym(Type *t) tbase = t->type; dupok = tbase->sym == S; - if(compiling_runtime && tbase == types[tbase->etype]) // int, float, etc + if(compiling_runtime && + (tbase == types[tbase->etype] || + tbase == bytetype || + tbase == runetype || + tbase == errortype)) { // int, float, etc goto ok; + } // named types from other files are defined only by those files if(tbase->sym && !tbase->local) @@ -902,9 +937,56 @@ dumptypestructs(void) dtypesym(ptrto(types[i])); dtypesym(ptrto(types[TSTRING])); dtypesym(ptrto(types[TUNSAFEPTR])); + + // emit type structs for error and func(error) string. + // The latter is the type of an auto-generated wrapper. + dtypesym(ptrto(errortype)); + dtypesym(functype(nil, + list1(nod(ODCLFIELD, N, typenod(errortype))), + list1(nod(ODCLFIELD, N, typenod(types[TSTRING]))))); // add paths for runtime and main, which 6l imports implicitly. dimportpath(runtimepkg); dimportpath(mkpkg(strlit("main"))); } } + +Sym* +dalgsym(Type *t) +{ + int ot; + Sym *s, *hash, *eq; + char buf[100]; + + // dalgsym is only called for a type that needs an algorithm table, + // which implies that the type is comparable (or else it would use ANOEQ). + + s = typesymprefix(".alg", t); + hash = typesymprefix(".hash", t); + genhash(hash, t); + eq = typesymprefix(".eq", t); + geneq(eq, t); + + // ../../pkg/runtime/runtime.h:/Alg + ot = 0; + ot = dsymptr(s, ot, hash, 0); + ot = dsymptr(s, ot, eq, 0); + ot = dsymptr(s, ot, pkglookup("memprint", runtimepkg), 0); + switch(t->width) { + default: + ot = dsymptr(s, ot, pkglookup("memcopy", runtimepkg), 0); + break; + case 1: + case 2: + case 4: + case 8: + case 16: + snprint(buf, sizeof buf, "memcopy%d", (int)t->width*8); + ot = dsymptr(s, ot, pkglookup(buf, runtimepkg), 0); + break; + } + + ggloblsym(s, ot, 1); + return s; +} + diff --git a/src/cmd/gc/runtime.go b/src/cmd/gc/runtime.go index 549f7abe3..000b2328f 100644 --- a/src/cmd/gc/runtime.go +++ b/src/cmd/gc/runtime.go @@ -6,11 +6,13 @@ // to update builtin.c.boot. This is not done automatically // to avoid depending on having a working compiler binary. +// +build ignore + package PACKAGE // emitted by compiler, not referred to by go programs -func new(int32) *any +func new(typ *byte) *any func panicindex() func panicslice() func throwreturn() @@ -40,18 +42,19 @@ func concatstring() // filled in by compiler: Type*, int n, Slice, ... func append() func appendslice(typ *byte, x any, y []any) any +func appendstr(typ *byte, x []byte, y string) []byte func cmpstring(string, string) int func slicestring(string, int, int) string func slicestring1(string, int) string func intstring(int64) string func slicebytetostring([]byte) string -func sliceinttostring([]int) string +func slicerunetostring([]rune) string func stringtoslicebyte(string) []byte -func stringtosliceint(string) []int +func stringtoslicerune(string) []rune func stringiter(string, int) int -func stringiter2(string, int) (retk int, retv int) -func slicecopy(to any, fr any, wid uint32) int +func stringiter2(string, int) (retk int, retv rune) +func copy(to any, fr any, wid uint32) int func slicestringcopy(to any, fr any) int // interface conversions @@ -79,6 +82,8 @@ func efaceeq(i1 any, i2 any) (ret bool) func ifacethash(i1 any) (ret uint32) func efacethash(i1 any) (ret uint32) +func equal(typ *byte, x1, x2 any) (ret bool) + // *byte is really *runtime.Type func makemap(mapType *byte, hint int64) (hmap map[any]any) func mapaccess1(mapType *byte, hmap map[any]any, key any) (val any) @@ -86,6 +91,7 @@ func mapaccess2(mapType *byte, hmap map[any]any, key any) (val any, pres bool) func mapassign1(mapType *byte, hmap map[any]any, key any, val any) func mapassign2(mapType *byte, hmap map[any]any, key any, val any, pres bool) func mapiterinit(mapType *byte, hmap map[any]any, hiter *any) +func mapdelete(mapType *byte, hmap map[any]any, key any) func mapiternext(hiter *any) func mapiter1(hiter *any) (key any) func mapiter2(hiter *any) (key any, val any) @@ -117,6 +123,13 @@ func slicearray(old *any, nel uint64, lb uint64, hb uint64, width uint64) (ary [ func closure() // has args, but compiler fills in +func memequal(eq *bool, size uintptr, x, y *any) +func memequal8(eq *bool, size uintptr, x, y *any) +func memequal16(eq *bool, size uintptr, x, y *any) +func memequal32(eq *bool, size uintptr, x, y *any) +func memequal64(eq *bool, size uintptr, x, y *any) +func memequal128(eq *bool, size uintptr, x, y *any) + // only used on 32-bit func int64div(int64, int64) int64 func uint64div(uint64, uint64) uint64 diff --git a/src/cmd/gc/sinit.c b/src/cmd/gc/sinit.c index 4550577a4..0cf21e2bb 100644 --- a/src/cmd/gc/sinit.c +++ b/src/cmd/gc/sinit.c @@ -154,6 +154,10 @@ init2(Node *n, NodeList **out) { if(n == N || n->initorder == InitDone) return; + + if(n->op == ONAME && n->ninit) + fatal("name %S with ninit: %+N\n", n->sym, n); + init1(n, out); init2(n->left, out); init2(n->right, out); @@ -262,6 +266,14 @@ staticcopy(Node *l, Node *r, NodeList **out) case ONAME: gdata(l, r, l->type->width); return 1; + } + break; + + case OPTRLIT: + switch(r->left->op) { + default: + //dump("not static addr", r); + break; case OARRAYLIT: case OSTRUCTLIT: case OMAPLIT: @@ -294,18 +306,18 @@ staticcopy(Node *l, Node *r, NodeList **out) n1.type = e->expr->type; if(e->expr->op == OLITERAL) gdata(&n1, e->expr, n1.type->width); - else if(staticassign(&n1, e->expr, out)) { - // Done - } else { - // Requires computation, but we're - // copying someone else's computation. + else { ll = nod(OXXX, N, N); *ll = n1; - rr = nod(OXXX, N, N); - *rr = *orig; - rr->type = ll->type; - rr->xoffset += e->xoffset; - *out = list(*out, nod(OAS, ll, rr)); + if(!staticassign(ll, e->expr, out)) { + // Requires computation, but we're + // copying someone else's computation. + rr = nod(OXXX, N, N); + *rr = *orig; + rr->type = ll->type; + rr->xoffset += e->xoffset; + *out = list(*out, nod(OAS, ll, rr)); + } } } return 1; @@ -347,7 +359,14 @@ staticassign(Node *l, Node *r, NodeList **out) case ONAME: gdata(l, r, l->type->width); return 1; - + } + + case OPTRLIT: + switch(r->left->op) { + default: + //dump("not static ptrlit", r); + break; + case OARRAYLIT: case OMAPLIT: case OSTRUCTLIT: @@ -392,12 +411,11 @@ staticassign(Node *l, Node *r, NodeList **out) n1.type = e->expr->type; if(e->expr->op == OLITERAL) gdata(&n1, e->expr, n1.type->width); - else if(staticassign(&n1, e->expr, out)) { - // done - } else { + else { a = nod(OXXX, N, N); *a = n1; - *out = list(*out, nod(OAS, a, e->expr)); + if(!staticassign(a, e->expr, out)) + *out = list(*out, nod(OAS, a, e->expr)); } } return 1; @@ -693,9 +711,10 @@ slicelit(int ctxt, Node *n, Node *var, NodeList **init) // set auto to point at new temp or heap (3 assign) if(n->esc == EscNone) { - a = temp(t); - *init = list(*init, nod(OAS, a, N)); // zero new temp - a = nod(OADDR, a, N); + a = nod(OAS, temp(t), N); + typecheck(&a, Etop); + *init = list(*init, a); // zero new temp + a = nod(OADDR, a->left, N); } else { a = nod(ONEW, N, N); a->list = list1(typenod(t)); @@ -918,6 +937,19 @@ anylit(int ctxt, Node *n, Node *var, NodeList **init) default: fatal("anylit: not lit"); + case OPTRLIT: + if(!isptr[t->etype]) + fatal("anylit: not ptr"); + + a = nod(OAS, var, callnew(t->type)); + typecheck(&a, Etop); + *init = list(*init, a); + + var = nod(OIND, var, N); + typecheck(&var, Erv | Easgn); + anylit(ctxt, n->left, var, init); + break; + case OSTRUCTLIT: if(t->etype != TSTRUCT) fatal("anylit: not struct"); @@ -1313,6 +1345,7 @@ iszero(Node *n) return n->val.u.bval == 0; case CTINT: + case CTRUNE: return mpcmpfixc(n->val.u.xval, 0) == 0; case CTFLT: diff --git a/src/cmd/gc/subr.c b/src/cmd/gc/subr.c index b450b9b0e..64a007077 100644 --- a/src/cmd/gc/subr.c +++ b/src/cmd/gc/subr.c @@ -7,11 +7,8 @@ #include "go.h" #include "md5.h" #include "y.tab.h" -#include "opnames.h" #include "yerr.h" -static void dodump(Node*, int); - typedef struct Error Error; struct Error { @@ -47,12 +44,10 @@ adderr(int line, char *fmt, va_list arg) Fmt f; Error *p; - erroring++; fmtstrinit(&f); fmtprint(&f, "%L: ", line); fmtvprint(&f, fmt, arg); fmtprint(&f, "\n"); - erroring--; if(nerr >= merr) { if(merr == 0) @@ -124,7 +119,7 @@ yyerrorl(int line, char *fmt, ...) hcrash(); nerrors++; - if(nerrors >= 10 && !debug['e']) { + if(nsavederrors+nerrors >= 10 && !debug['e']) { flusherrors(); print("%L: too many errors\n", line); errorexit(); @@ -192,7 +187,7 @@ yyerror(char *fmt, ...) hcrash(); nerrors++; - if(nerrors >= 10 && !debug['e']) { + if(nsavederrors+nerrors >= 10 && !debug['e']) { flusherrors(); print("%L: too many errors\n", parserline()); errorexit(); @@ -500,45 +495,118 @@ nod(int op, Node *nleft, Node *nright) } int +algtype1(Type *t, Type **bad) +{ + int a, ret; + Type *t1; + + if(bad) + *bad = T; + + switch(t->etype) { + case TINT8: + case TUINT8: + case TINT16: + case TUINT16: + case TINT32: + case TUINT32: + case TINT64: + case TUINT64: + case TINT: + case TUINT: + case TUINTPTR: + case TBOOL: + case TPTR32: + case TPTR64: + case TCHAN: + case TUNSAFEPTR: + return AMEM; + + case TFUNC: + case TMAP: + if(bad) + *bad = t; + return ANOEQ; + + case TFLOAT32: + return AFLOAT32; + + case TFLOAT64: + return AFLOAT64; + + case TCOMPLEX64: + return ACPLX64; + + case TCOMPLEX128: + return ACPLX128; + + case TSTRING: + return ASTRING; + + case TINTER: + if(isnilinter(t)) + return ANILINTER; + return AINTER; + + case TARRAY: + if(isslice(t)) { + if(bad) + *bad = t; + return ANOEQ; + } + if(t->bound == 0) + return AMEM; + a = algtype1(t->type, bad); + if(a == ANOEQ || a == AMEM) { + if(a == ANOEQ && bad) + *bad = t; + return a; + } + return -1; // needs special compare + + case TSTRUCT: + if(t->type != T && t->type->down == T) { + // One-field struct is same as that one field alone. + return algtype1(t->type->type, bad); + } + ret = AMEM; + for(t1=t->type; t1!=T; t1=t1->down) { + a = algtype1(t1->type, bad); + if(a == ANOEQ) + return ANOEQ; // not comparable + if(a != AMEM) + ret = -1; // needs special compare + } + return ret; + } + + fatal("algtype1: unexpected type %T", t); + return 0; +} + +int algtype(Type *t) { int a; - - if(issimple[t->etype] || isptr[t->etype] || - t->etype == TCHAN || t->etype == TFUNC || t->etype == TMAP) { - if(t->width == 1) - a = AMEM8; - else if(t->width == 2) - a = AMEM16; - else if(t->width == 4) - a = AMEM32; - else if(t->width == 8) - a = AMEM64; - else if(t->width == 16) - a = AMEM128; - else - a = AMEM; // just bytes (int, ptr, etc) - } else if(t->etype == TSTRING) - a = ASTRING; // string - else if(isnilinter(t)) - a = ANILINTER; // nil interface - else if(t->etype == TINTER) - a = AINTER; // interface - else if(isslice(t)) - a = ASLICE; // slice - else { - if(t->width == 1) - a = ANOEQ8; - else if(t->width == 2) - a = ANOEQ16; - else if(t->width == 4) - a = ANOEQ32; - else if(t->width == 8) - a = ANOEQ64; - else if(t->width == 16) - a = ANOEQ128; - else - a = ANOEQ; // just bytes, but no hash/eq + + a = algtype1(t, nil); + if(a == AMEM || a == ANOEQ) { + if(isslice(t)) + return ASLICE; + switch(t->width) { + case 0: + return a + AMEM0 - AMEM; + case 1: + return a + AMEM8 - AMEM; + case 2: + return a + AMEM16 - AMEM; + case 4: + return a + AMEM32 - AMEM; + case 8: + return a + AMEM64 - AMEM; + case 16: + return a + AMEM128 - AMEM; + } } return a; } @@ -550,9 +618,12 @@ maptype(Type *key, Type *val) if(key != nil) { switch(key->etype) { - case TARRAY: - case TSTRUCT: - yyerror("invalid map key type %T", key); + default: + if(algtype1(key, nil) == ANOEQ) + yyerror("invalid map key type %T", key); + break; + case TANY: + // will be resolved later. break; case TFORW: // map[key] used during definition of key. @@ -713,6 +784,7 @@ aindex(Node *b, Type *t) yyerror("array bound must be an integer expression"); break; case CTINT: + case CTRUNE: bound = mpgetfix(b->val.u.xval); if(bound < 0) yyerror("array bound must be non negative"); @@ -727,910 +799,6 @@ aindex(Node *b, Type *t) return r; } -static void -indent(int dep) -{ - int i; - - for(i=0; i<dep; i++) - print(". "); -} - -static void -dodumplist(NodeList *l, int dep) -{ - for(; l; l=l->next) - dodump(l->n, dep); -} - -static void -dodump(Node *n, int dep) -{ - if(n == N) - return; - - indent(dep); - if(dep > 10) { - print("...\n"); - return; - } - - if(n->ninit != nil) { - print("%O-init\n", n->op); - dodumplist(n->ninit, dep+1); - indent(dep); - } - - switch(n->op) { - default: - print("%N\n", n); - dodump(n->left, dep+1); - dodump(n->right, dep+1); - break; - - case OTYPE: - print("%O %S type=%T\n", n->op, n->sym, n->type); - if(n->type == T && n->ntype) { - indent(dep); - print("%O-ntype\n", n->op); - dodump(n->ntype, dep+1); - } - break; - - case OIF: - print("%O%J\n", n->op, n); - dodump(n->ntest, dep+1); - if(n->nbody != nil) { - indent(dep); - print("%O-then\n", n->op); - dodumplist(n->nbody, dep+1); - } - if(n->nelse != nil) { - indent(dep); - print("%O-else\n", n->op); - dodumplist(n->nelse, dep+1); - } - break; - - case OSELECT: - print("%O%J\n", n->op, n); - dodumplist(n->nbody, dep+1); - break; - - case OSWITCH: - case OFOR: - print("%O%J\n", n->op, n); - dodump(n->ntest, dep+1); - - if(n->nbody != nil) { - indent(dep); - print("%O-body\n", n->op); - dodumplist(n->nbody, dep+1); - } - - if(n->nincr != N) { - indent(dep); - print("%O-incr\n", n->op); - dodump(n->nincr, dep+1); - } - break; - - case OCASE: - // the right side points to label of the body - if(n->right != N && n->right->op == OGOTO && n->right->left->op == ONAME) - print("%O%J GOTO %N\n", n->op, n, n->right->left); - else - print("%O%J\n", n->op, n); - dodump(n->left, dep+1); - break; - - case OXCASE: - print("%N\n", n); - dodump(n->left, dep+1); - dodump(n->right, dep+1); - indent(dep); - print("%O-nbody\n", n->op); - dodumplist(n->nbody, dep+1); - break; - } - - if(0 && n->ntype != nil) { - indent(dep); - print("%O-ntype\n", n->op); - dodump(n->ntype, dep+1); - } - if(n->list != nil) { - indent(dep); - print("%O-list\n", n->op); - dodumplist(n->list, dep+1); - } - if(n->rlist != nil) { - indent(dep); - print("%O-rlist\n", n->op); - dodumplist(n->rlist, dep+1); - } - if(n->op != OIF && n->nbody != nil) { - indent(dep); - print("%O-nbody\n", n->op); - dodumplist(n->nbody, dep+1); - } -} - -void -dumplist(char *s, NodeList *l) -{ - print("%s\n", s); - dodumplist(l, 1); -} - -void -dump(char *s, Node *n) -{ - print("%s [%p]\n", s, n); - dodump(n, 1); -} - -static char* -goopnames[] = -{ - [OADDR] = "&", - [OADD] = "+", - [OANDAND] = "&&", - [OANDNOT] = "&^", - [OAND] = "&", - [OAPPEND] = "append", - [OAS] = "=", - [OAS2] = "=", - [OBREAK] = "break", - [OCALL] = "function call", - [OCAP] = "cap", - [OCASE] = "case", - [OCLOSE] = "close", - [OCOMPLEX] = "complex", - [OCOM] = "^", - [OCONTINUE] = "continue", - [OCOPY] = "copy", - [ODEC] = "--", - [ODEFER] = "defer", - [ODIV] = "/", - [OEQ] = "==", - [OFALL] = "fallthrough", - [OFOR] = "for", - [OGE] = ">=", - [OGOTO] = "goto", - [OGT] = ">", - [OIF] = "if", - [OIMAG] = "imag", - [OINC] = "++", - [OIND] = "*", - [OLEN] = "len", - [OLE] = "<=", - [OLSH] = "<<", - [OLT] = "<", - [OMAKE] = "make", - [OMINUS] = "-", - [OMOD] = "%", - [OMUL] = "*", - [ONEW] = "new", - [ONE] = "!=", - [ONOT] = "!", - [OOROR] = "||", - [OOR] = "|", - [OPANIC] = "panic", - [OPLUS] = "+", - [OPRINTN] = "println", - [OPRINT] = "print", - [ORANGE] = "range", - [OREAL] = "real", - [ORECV] = "<-", - [ORETURN] = "return", - [ORSH] = ">>", - [OSELECT] = "select", - [OSEND] = "<-", - [OSUB] = "-", - [OSWITCH] = "switch", - [OXOR] = "^", -}; - -int -Oconv(Fmt *fp) -{ - int o; - - o = va_arg(fp->args, int); - if((fp->flags & FmtSharp) && o >= 0 && o < nelem(goopnames) && goopnames[o] != nil) - return fmtstrcpy(fp, goopnames[o]); - if(o < 0 || o >= nelem(opnames) || opnames[o] == nil) - return fmtprint(fp, "O-%d", o); - return fmtstrcpy(fp, opnames[o]); -} - -int -Lconv(Fmt *fp) -{ - struct - { - Hist* incl; /* start of this include file */ - int32 idel; /* delta line number to apply to include */ - Hist* line; /* start of this #line directive */ - int32 ldel; /* delta line number to apply to #line */ - } a[HISTSZ]; - int32 lno, d; - int i, n; - Hist *h; - - lno = va_arg(fp->args, int32); - - n = 0; - for(h=hist; h!=H; h=h->link) { - if(h->offset < 0) - continue; - if(lno < h->line) - break; - if(h->name) { - if(h->offset > 0) { - // #line directive - if(n > 0 && n < HISTSZ) { - a[n-1].line = h; - a[n-1].ldel = h->line - h->offset + 1; - } - } else { - // beginning of file - if(n < HISTSZ) { - a[n].incl = h; - a[n].idel = h->line; - a[n].line = 0; - } - n++; - } - continue; - } - n--; - if(n > 0 && n < HISTSZ) { - d = h->line - a[n].incl->line; - a[n-1].ldel += d; - a[n-1].idel += d; - } - } - - if(n > HISTSZ) - n = HISTSZ; - - for(i=n-1; i>=0; i--) { - if(i != n-1) { - if(fp->flags & ~(FmtWidth|FmtPrec)) - break; - fmtprint(fp, " "); - } - if(debug['L']) - fmtprint(fp, "%s/", pathname); - if(a[i].line) - fmtprint(fp, "%s:%d[%s:%d]", - a[i].line->name, lno-a[i].ldel+1, - a[i].incl->name, lno-a[i].idel+1); - else - fmtprint(fp, "%s:%d", - a[i].incl->name, lno-a[i].idel+1); - lno = a[i].incl->line - 1; // now print out start of this file - } - if(n == 0) - fmtprint(fp, "<epoch>"); - - return 0; -} - -/* -s%,%,\n%g -s%\n+%\n%g -s%^[ ]*T%%g -s%,.*%%g -s%.+% [T&] = "&",%g -s%^ ........*\]%&~%g -s%~ %%g -*/ - -static char* -etnames[] = -{ - [TINT] = "INT", - [TUINT] = "UINT", - [TINT8] = "INT8", - [TUINT8] = "UINT8", - [TINT16] = "INT16", - [TUINT16] = "UINT16", - [TINT32] = "INT32", - [TUINT32] = "UINT32", - [TINT64] = "INT64", - [TUINT64] = "UINT64", - [TUINTPTR] = "UINTPTR", - [TFLOAT32] = "FLOAT32", - [TFLOAT64] = "FLOAT64", - [TCOMPLEX64] = "COMPLEX64", - [TCOMPLEX128] = "COMPLEX128", - [TBOOL] = "BOOL", - [TPTR32] = "PTR32", - [TPTR64] = "PTR64", - [TFUNC] = "FUNC", - [TARRAY] = "ARRAY", - [TSTRUCT] = "STRUCT", - [TCHAN] = "CHAN", - [TMAP] = "MAP", - [TINTER] = "INTER", - [TFORW] = "FORW", - [TFIELD] = "FIELD", - [TSTRING] = "STRING", - [TANY] = "ANY", -}; - -int -Econv(Fmt *fp) -{ - int et; - - et = va_arg(fp->args, int); - if(et < 0 || et >= nelem(etnames) || etnames[et] == nil) - return fmtprint(fp, "E-%d", et); - return fmtstrcpy(fp, etnames[et]); -} - -static const char* classnames[] = { - "Pxxx", - "PEXTERN", - "PAUTO", - "PPARAM", - "PPARAMOUT", - "PPARAMREF", - "PFUNC", -}; - -int -Jconv(Fmt *fp) -{ - Node *n; - char *s; - int c; - - n = va_arg(fp->args, Node*); - - c = fp->flags&FmtShort; - - if(!c && n->ullman != 0) - fmtprint(fp, " u(%d)", n->ullman); - - if(!c && n->addable != 0) - fmtprint(fp, " a(%d)", n->addable); - - if(!c && n->vargen != 0) - fmtprint(fp, " g(%d)", n->vargen); - - if(n->lineno != 0) - fmtprint(fp, " l(%d)", n->lineno); - - if(!c && n->xoffset != BADWIDTH) - fmtprint(fp, " x(%lld%+d)", n->xoffset, n->stkdelta); - - if(n->class != 0) { - s = ""; - if(n->class & PHEAP) s = ",heap"; - if((n->class & ~PHEAP) < nelem(classnames)) - fmtprint(fp, " class(%s%s)", classnames[n->class&~PHEAP], s); - else - fmtprint(fp, " class(%d?%s)", n->class&~PHEAP, s); - } - - if(n->colas != 0) - fmtprint(fp, " colas(%d)", n->colas); - - if(n->funcdepth != 0) - fmtprint(fp, " f(%d)", n->funcdepth); - - switch(n->esc) { - case EscUnknown: - break; - case EscHeap: - fmtprint(fp, " esc(h)"); - break; - case EscScope: - fmtprint(fp, " esc(s)"); - break; - case EscNone: - fmtprint(fp, " esc(no)"); - break; - case EscNever: - if(!c) - fmtprint(fp, " esc(N)"); - break; - default: - fmtprint(fp, " esc(%d)", n->esc); - break; - } - - if(n->escloopdepth) - fmtprint(fp, " ld(%d)", n->escloopdepth); - - if(!c && n->typecheck != 0) - fmtprint(fp, " tc(%d)", n->typecheck); - - if(!c && n->dodata != 0) - fmtprint(fp, " dd(%d)", n->dodata); - - if(n->isddd != 0) - fmtprint(fp, " isddd(%d)", n->isddd); - - if(n->implicit != 0) - fmtprint(fp, " implicit(%d)", n->implicit); - - if(!c && n->used != 0) - fmtprint(fp, " used(%d)", n->used); - return 0; -} - -int -Sconv(Fmt *fp) -{ - Sym *s; - - s = va_arg(fp->args, Sym*); - if(s == S) { - fmtstrcpy(fp, "<S>"); - return 0; - } - - if(fp->flags & FmtShort) - goto shrt; - - if(exporting || (fp->flags & FmtSharp)) { - if(packagequotes) - fmtprint(fp, "\"%Z\"", s->pkg->path); - else - fmtprint(fp, "%s", s->pkg->prefix); - fmtprint(fp, ".%s", s->name); - return 0; - } - - if(s->pkg && s->pkg != localpkg || longsymnames || (fp->flags & FmtLong)) { - // This one is for the user. If the package name - // was used by multiple packages, give the full - // import path to disambiguate. - if(erroring && pkglookup(s->pkg->name, nil)->npkg > 1) { - fmtprint(fp, "\"%Z\".%s", s->pkg->path, s->name); - return 0; - } - fmtprint(fp, "%s.%s", s->pkg->name, s->name); - return 0; - } - -shrt: - fmtstrcpy(fp, s->name); - return 0; -} - -static char* -basicnames[] = -{ - [TINT] = "int", - [TUINT] = "uint", - [TINT8] = "int8", - [TUINT8] = "uint8", - [TINT16] = "int16", - [TUINT16] = "uint16", - [TINT32] = "int32", - [TUINT32] = "uint32", - [TINT64] = "int64", - [TUINT64] = "uint64", - [TUINTPTR] = "uintptr", - [TFLOAT32] = "float32", - [TFLOAT64] = "float64", - [TCOMPLEX64] = "complex64", - [TCOMPLEX128] = "complex128", - [TBOOL] = "bool", - [TANY] = "any", - [TSTRING] = "string", - [TNIL] = "nil", - [TIDEAL] = "ideal", - [TBLANK] = "blank", -}; - -int -Tpretty(Fmt *fp, Type *t) -{ - Type *t1; - Sym *s; - - if(0 && debug['r']) { - debug['r'] = 0; - fmtprint(fp, "%T (orig=%T)", t, t->orig); - debug['r'] = 1; - return 0; - } - - if(t->etype != TFIELD - && t->sym != S - && !(fp->flags&FmtLong)) { - s = t->sym; - if(t == types[t->etype] && t->etype != TUNSAFEPTR) - return fmtprint(fp, "%s", s->name); - if(exporting) { - if(fp->flags & FmtShort) - fmtprint(fp, "%hS", s); - else - fmtprint(fp, "%S", s); - if(s->pkg != localpkg) - return 0; - if(t->vargen) - fmtprint(fp, "·%d", t->vargen); - return 0; - } - return fmtprint(fp, "%S", s); - } - - if(t->etype < nelem(basicnames) && basicnames[t->etype] != nil) { - if(isideal(t) && t->etype != TIDEAL && t->etype != TNIL) - fmtprint(fp, "ideal "); - return fmtprint(fp, "%s", basicnames[t->etype]); - } - - switch(t->etype) { - case TPTR32: - case TPTR64: - if(fp->flags&FmtShort) // pass flag thru for methodsym - return fmtprint(fp, "*%hT", t->type); - return fmtprint(fp, "*%T", t->type); - - case TCHAN: - switch(t->chan) { - case Crecv: - return fmtprint(fp, "<-chan %T", t->type); - case Csend: - return fmtprint(fp, "chan<- %T", t->type); - } - if(t->type != T && t->type->etype == TCHAN && t->type->sym == S && t->type->chan == Crecv) - return fmtprint(fp, "chan (%T)", t->type); - return fmtprint(fp, "chan %T", t->type); - - case TMAP: - return fmtprint(fp, "map[%T] %T", t->down, t->type); - - case TFUNC: - // t->type is method struct - // t->type->down is result struct - // t->type->down->down is arg struct - if(t->thistuple && !(fp->flags&FmtSharp) && !(fp->flags&FmtShort)) { - fmtprint(fp, "method("); - for(t1=getthisx(t)->type; t1; t1=t1->down) { - fmtprint(fp, "%T", t1); - if(t1->down) - fmtprint(fp, ", "); - } - fmtprint(fp, ")"); - } - - if(!(fp->flags&FmtByte)) - fmtprint(fp, "func"); - fmtprint(fp, "("); - for(t1=getinargx(t)->type; t1; t1=t1->down) { - if(noargnames && t1->etype == TFIELD) { - if(t1->isddd) - fmtprint(fp, "...%T", t1->type->type); - else - fmtprint(fp, "%T", t1->type); - } else - fmtprint(fp, "%T", t1); - if(t1->down) - fmtprint(fp, ", "); - } - fmtprint(fp, ")"); - switch(t->outtuple) { - case 0: - break; - case 1: - t1 = getoutargx(t)->type; - if(t1 == T) { - // failure to typecheck earlier; don't know the type - fmtprint(fp, " ?unknown-type?"); - break; - } - if(t1->etype == TFIELD) - t1 = t1->type; - fmtprint(fp, " %T", t1); - break; - default: - t1 = getoutargx(t)->type; - fmtprint(fp, " ("); - for(; t1; t1=t1->down) { - if(noargnames && t1->etype == TFIELD) - fmtprint(fp, "%T", t1->type); - else - fmtprint(fp, "%T", t1); - if(t1->down) - fmtprint(fp, ", "); - } - fmtprint(fp, ")"); - break; - } - return 0; - - case TARRAY: - if(t->bound >= 0) - return fmtprint(fp, "[%d]%T", (int)t->bound, t->type); - if(t->bound == -100) - return fmtprint(fp, "[...]%T", t->type); - return fmtprint(fp, "[]%T", t->type); - - case TINTER: - fmtprint(fp, "interface {"); - for(t1=t->type; t1!=T; t1=t1->down) { - fmtprint(fp, " "); - if(exportname(t1->sym->name)) - fmtprint(fp, "%hS", t1->sym); - else - fmtprint(fp, "%S", t1->sym); - fmtprint(fp, "%hhT", t1->type); - if(t1->down) - fmtprint(fp, ";"); - } - return fmtprint(fp, " }"); - - case TSTRUCT: - if(t->funarg) { - fmtprint(fp, "("); - for(t1=t->type; t1!=T; t1=t1->down) { - fmtprint(fp, "%T", t1); - if(t1->down) - fmtprint(fp, ", "); - } - return fmtprint(fp, ")"); - } - fmtprint(fp, "struct {"); - for(t1=t->type; t1!=T; t1=t1->down) { - fmtprint(fp, " %T", t1); - if(t1->down) - fmtprint(fp, ";"); - } - return fmtprint(fp, " }"); - - case TFIELD: - if(t->sym == S || t->embedded) { - if(exporting) - fmtprint(fp, "? "); - } else - fmtprint(fp, "%hS ", t->sym); - if(t->isddd) - fmtprint(fp, "...%T", t->type->type); - else - fmtprint(fp, "%T", t->type); - if(t->note) { - fmtprint(fp, " "); - if(exporting) - fmtprint(fp, ":"); - fmtprint(fp, "\"%Z\"", t->note); - } - return 0; - - case TFORW: - if(exporting) - yyerror("undefined type %S", t->sym); - if(t->sym) - return fmtprint(fp, "undefined %S", t->sym); - return fmtprint(fp, "undefined"); - - case TUNSAFEPTR: - if(exporting) - return fmtprint(fp, "\"unsafe\".Pointer"); - return fmtprint(fp, "unsafe.Pointer"); - } - - // Don't know how to handle - fall back to detailed prints. - return -1; -} - -int -Tconv(Fmt *fp) -{ - Type *t, *t1; - int r, et, sharp, minus; - - sharp = (fp->flags & FmtSharp); - minus = (fp->flags & FmtLeft); - fp->flags &= ~(FmtSharp|FmtLeft); - - t = va_arg(fp->args, Type*); - if(t == T) - return fmtstrcpy(fp, "<T>"); - - t->trecur++; - if(t->trecur > 5) { - fmtprint(fp, "..."); - goto out; - } - - if(!debug['t']) { - if(sharp) - exporting++; - if(minus) - noargnames++; - r = Tpretty(fp, t); - if(sharp) - exporting--; - if(minus) - noargnames--; - if(r >= 0) { - t->trecur--; - return 0; - } - } - - if(sharp || exporting) - fatal("missing %E case during export", t->etype); - - et = t->etype; - fmtprint(fp, "%E ", et); - if(t->sym != S) - fmtprint(fp, "<%S>", t->sym); - - switch(et) { - default: - if(t->type != T) - fmtprint(fp, " %T", t->type); - break; - - case TFIELD: - fmtprint(fp, "%T", t->type); - break; - - case TFUNC: - if(fp->flags & FmtLong) - fmtprint(fp, "%d%d%d(%lT,%lT)%lT", - t->thistuple, t->intuple, t->outtuple, - t->type, t->type->down->down, t->type->down); - else - fmtprint(fp, "%d%d%d(%T,%T)%T", - t->thistuple, t->intuple, t->outtuple, - t->type, t->type->down->down, t->type->down); - break; - - case TINTER: - fmtprint(fp, "{"); - if(fp->flags & FmtLong) - for(t1=t->type; t1!=T; t1=t1->down) - fmtprint(fp, "%lT;", t1); - fmtprint(fp, "}"); - break; - - case TSTRUCT: - fmtprint(fp, "{"); - if(fp->flags & FmtLong) - for(t1=t->type; t1!=T; t1=t1->down) - fmtprint(fp, "%lT;", t1); - fmtprint(fp, "}"); - break; - - case TMAP: - fmtprint(fp, "[%T]%T", t->down, t->type); - break; - - case TARRAY: - if(t->bound >= 0) - fmtprint(fp, "[%d]%T", t->bound, t->type); - else - fmtprint(fp, "[]%T", t->type); - break; - - case TPTR32: - case TPTR64: - fmtprint(fp, "%T", t->type); - break; - } - -out: - t->trecur--; - return 0; -} - -int -Nconv(Fmt *fp) -{ - char buf1[500]; - Node *n; - - n = va_arg(fp->args, Node*); - if(n == N) { - fmtprint(fp, "<N>"); - goto out; - } - - if(fp->flags & FmtSign) { - if(n->type == T) - fmtprint(fp, "%#N", n); - else if(n->type->etype == TNIL) - fmtprint(fp, "nil"); - else - fmtprint(fp, "%#N (type %T)", n, n->type); - goto out; - } - - if(fp->flags & FmtSharp) { - if(n->orig != N) - n = n->orig; - exprfmt(fp, n, 0); - goto out; - } - - switch(n->op) { - default: - if(fp->flags & FmtShort) - fmtprint(fp, "%O%hJ", n->op, n); - else - fmtprint(fp, "%O%J", n->op, n); - break; - - case ONAME: - case ONONAME: - if(n->sym == S) { - if(fp->flags & FmtShort) - fmtprint(fp, "%O%hJ", n->op, n); - else - fmtprint(fp, "%O%J", n->op, n); - break; - } - if(fp->flags & FmtShort) - fmtprint(fp, "%O-%S%hJ", n->op, n->sym, n); - else - fmtprint(fp, "%O-%S%J", n->op, n->sym, n); - goto ptyp; - - case OREGISTER: - fmtprint(fp, "%O-%R%J", n->op, n->val.u.reg, n); - break; - - case OLITERAL: - switch(n->val.ctype) { - default: - snprint(buf1, sizeof(buf1), "LITERAL-ctype=%d", n->val.ctype); - break; - case CTINT: - snprint(buf1, sizeof(buf1), "I%B", n->val.u.xval); - break; - case CTFLT: - snprint(buf1, sizeof(buf1), "F%g", mpgetflt(n->val.u.fval)); - break; - case CTCPLX: - snprint(buf1, sizeof(buf1), "(F%g+F%gi)", - mpgetflt(&n->val.u.cval->real), - mpgetflt(&n->val.u.cval->imag)); - break; - case CTSTR: - snprint(buf1, sizeof(buf1), "S\"%Z\"", n->val.u.sval); - break; - case CTBOOL: - snprint(buf1, sizeof(buf1), "B%d", n->val.u.bval); - break; - case CTNIL: - snprint(buf1, sizeof(buf1), "N"); - break; - } - fmtprint(fp, "%O-%s%J", n->op, buf1, n); - break; - - case OASOP: - fmtprint(fp, "%O-%O%J", n->op, n->etype, n); - break; - - case OTYPE: - fmtprint(fp, "%O %T", n->op, n->type); - break; - } - if(n->sym != S) - fmtprint(fp, " %S G%d", n->sym, n->vargen); - -ptyp: - if(n->type != T) - fmtprint(fp, " %T", n->type); - -out: - return 0; -} - Node* treecopy(Node *n) { @@ -1671,52 +839,6 @@ treecopy(Node *n) return m; } -int -Zconv(Fmt *fp) -{ - Rune r; - Strlit *sp; - char *s, *se; - int n; - - sp = va_arg(fp->args, Strlit*); - if(sp == nil) - return fmtstrcpy(fp, "<nil>"); - - s = sp->s; - se = s + sp->len; - while(s < se) { - n = chartorune(&r, s); - s += n; - switch(r) { - case Runeerror: - if(n == 1) { - fmtprint(fp, "\\x%02x", (uchar)*(s-1)); - break; - } - // fall through - default: - if(r < ' ') { - fmtprint(fp, "\\x%02x", r); - break; - } - fmtrune(fp, r); - break; - case '\t': - fmtstrcpy(fp, "\\t"); - break; - case '\n': - fmtstrcpy(fp, "\\n"); - break; - case '\"': - case '\\': - fmtrune(fp, '\\'); - fmtrune(fp, r); - break; - } - } - return 0; -} int isnil(Node *n) @@ -1872,6 +994,25 @@ eqnote(Strlit *a, Strlit *b) return memcmp(a->s, b->s, a->len) == 0; } +typedef struct TypePairList TypePairList; +struct TypePairList +{ + Type *t1; + Type *t2; + TypePairList *next; +}; + +static int +onlist(TypePairList *l, Type *t1, Type *t2) +{ + for(; l; l=l->next) + if((l->t1 == t1 && l->t2 == t2) || (l->t1 == t2 && l->t2 == t1)) + return 1; + return 0; +} + +static int eqtype1(Type*, Type*, TypePairList*); + // Return 1 if t1 and t2 are identical, following the spec rules. // // Any cyclic type must go through a named type, and if one is @@ -1881,10 +1022,40 @@ eqnote(Strlit *a, Strlit *b) int eqtype(Type *t1, Type *t2) { + return eqtype1(t1, t2, nil); +} + +static int +eqtype1(Type *t1, Type *t2, TypePairList *assumed_equal) +{ + TypePairList l; + if(t1 == t2) return 1; - if(t1 == T || t2 == T || t1->etype != t2->etype || t1->sym || t2->sym) + if(t1 == T || t2 == T || t1->etype != t2->etype) return 0; + if(t1->sym || t2->sym) { + // Special case: we keep byte and uint8 separate + // for error messages. Treat them as equal. + switch(t1->etype) { + case TUINT8: + if((t1 == types[TUINT8] || t1 == bytetype) && (t2 == types[TUINT8] || t2 == bytetype)) + return 1; + break; + case TINT: + case TINT32: + if((t1 == types[runetype->etype] || t1 == runetype) && (t2 == types[runetype->etype] || t2 == runetype)) + return 1; + break; + } + return 0; + } + + if(onlist(assumed_equal, t1, t2)) + return 1; + l.next = assumed_equal; + l.t1 = t1; + l.t2 = t2; switch(t1->etype) { case TINTER: @@ -1892,10 +1063,12 @@ eqtype(Type *t1, Type *t2) for(t1=t1->type, t2=t2->type; t1 && t2; t1=t1->down, t2=t2->down) { if(t1->etype != TFIELD || t2->etype != TFIELD) fatal("struct/interface missing field: %T %T", t1, t2); - if(t1->sym != t2->sym || t1->embedded != t2->embedded || !eqtype(t1->type, t2->type) || !eqnote(t1->note, t2->note)) - return 0; + if(t1->sym != t2->sym || t1->embedded != t2->embedded || !eqtype1(t1->type, t2->type, &l) || !eqnote(t1->note, t2->note)) + goto no; } - return t1 == T && t2 == T; + if(t1 == T && t2 == T) + goto yes; + goto no; case TFUNC: // Loop over structs: receiver, in, out. @@ -1909,26 +1082,38 @@ eqtype(Type *t1, Type *t2) for(ta=t1->type, tb=t2->type; ta && tb; ta=ta->down, tb=tb->down) { if(ta->etype != TFIELD || tb->etype != TFIELD) fatal("func struct missing field: %T %T", ta, tb); - if(ta->isddd != tb->isddd || !eqtype(ta->type, tb->type)) - return 0; + if(ta->isddd != tb->isddd || !eqtype1(ta->type, tb->type, &l)) + goto no; } if(ta != T || tb != T) - return 0; + goto no; } - return t1 == T && t2 == T; + if(t1 == T && t2 == T) + goto yes; + goto no; case TARRAY: if(t1->bound != t2->bound) - return 0; + goto no; break; case TCHAN: if(t1->chan != t2->chan) - return 0; + goto no; break; } - return eqtype(t1->down, t2->down) && eqtype(t1->type, t2->type); + if(eqtype1(t1->down, t2->down, &l) && eqtype1(t1->type, t2->type, &l)) + goto yes; + goto no; + +yes: + assumed_equal = l.next; + return 1; + +no: + assumed_equal = l.next; + return 0; } // Are t1 and t2 equal struct types when field names are ignored? @@ -1955,9 +1140,6 @@ eqtypenoname(Type *t1, Type *t2) // Is type src assignment compatible to type dst? // If so, return op code to use in conversion. // If not, return 0. -// -// It is the caller's responsibility to call exportassignok -// to check for assignments to other packages' unexported fields, int assignop(Type *src, Type *dst, char **why) { @@ -1967,7 +1149,9 @@ assignop(Type *src, Type *dst, char **why) if(why != nil) *why = ""; - if(safemode && src != T && src->etype == TUNSAFEPTR) { + // TODO(rsc,lvd): This behaves poorly in the presence of inlining. + // https://code.google.com/p/go/issues/detail?id=2795 + if(safemode && importpkg == nil && src != T && src->etype == TUNSAFEPTR) { yyerror("cannot use unsafe.Pointer"); errorexit(); } @@ -1991,6 +1175,11 @@ assignop(Type *src, Type *dst, char **why) if(dst->etype == TINTER && src->etype != TNIL) { if(implements(src, dst, &missing, &have, &ptr)) return OCONVIFACE; + + // we'll have complained about this method anyway, supress spurious messages. + if(have && have->sym == missing->sym && (have->type->broke || missing->type->broke)) + return OCONVIFACE; + if(why != nil) { if(isptrto(src, TINTER)) *why = smprint(":\n\t%T is pointer to interface, not interface", src); @@ -2107,29 +1296,25 @@ convertop(Type *src, Type *dst, char **why) return OCONV; } - // 6. src is an integer or has type []byte or []int + // 6. src is an integer or has type []byte or []rune // and dst is a string type. if(isint[src->etype] && dst->etype == TSTRING) return ORUNESTR; - if(isslice(src) && src->sym == nil && src->type == types[src->type->etype] && dst->etype == TSTRING) { - switch(src->type->etype) { - case TUINT8: + if(isslice(src) && dst->etype == TSTRING) { + if(src->type->etype == bytetype->etype) return OARRAYBYTESTR; - case TINT: + if(src->type->etype == runetype->etype) return OARRAYRUNESTR; - } } - // 7. src is a string and dst is []byte or []int. + // 7. src is a string and dst is []byte or []rune. // String to slice. - if(src->etype == TSTRING && isslice(dst) && dst->sym == nil && dst->type == types[dst->type->etype]) { - switch(dst->type->etype) { - case TUINT8: + if(src->etype == TSTRING && isslice(dst)) { + if(dst->type->etype == bytetype->etype) return OSTRARRAYBYTE; - case TINT: + if(dst->type->etype == runetype->etype) return OSTRARRAYRUNE; - } } // 8. src is a pointer or uintptr and dst is unsafe.Pointer. @@ -2161,13 +1346,12 @@ assignconv(Node *n, Type *t, char *context) if(t->etype == TBLANK) return n; - exportassignok(n->type, context); if(eqtype(n->type, t)) return n; op = assignop(n->type, t, &why); if(op == 0) { - yyerror("cannot use %+N as type %T in %s%s", n, t, context, why); + yyerror("cannot use %lN as type %T in %s%s", n, t, context, why); op = OCONV; } @@ -2392,7 +1576,7 @@ syslook(char *name, int copy) * compute a hash value for type t. * if t is a method type, ignore the receiver * so that the hash can be used in interface checks. - * %-T (which calls Tpretty, above) already contains + * %T already contains * all the necessary logic to generate a representation * of the type that completely describes it. * using smprint here avoids duplicating that code. @@ -2406,15 +1590,14 @@ typehash(Type *t) char *p; MD5 d; - longsymnames = 1; if(t->thistuple) { // hide method receiver from Tpretty t->thistuple = 0; - p = smprint("%-T", t); + p = smprint("%-uT", t); t->thistuple = 1; - }else - p = smprint("%-T", t); - longsymnames = 0; + } else + p = smprint("%-uT", t); + //print("typehash: %s\n", p); md5reset(&d); md5write(&d, (uchar*)p, strlen(p)); free(p); @@ -2427,7 +1610,7 @@ ptrto(Type *t) Type *t1; if(tptr == 0) - fatal("ptrto: nil"); + fatal("ptrto: no tptr"); t1 = typ(tptr); t1->type = t; t1->width = widthptr; @@ -2784,7 +1967,7 @@ safeexpr(Node *n, NodeList **init) return cheapexpr(n, init); } -static Node* +Node* copyexpr(Node *n, Type *t, NodeList **init) { Node *a, *l; @@ -2905,7 +2088,7 @@ lookdot0(Sym *s, Type *t, Type **save, int ignorecase) return c; } -// search depth d -- +// search depth d for field/method s -- // return count of fields+methods // found at search depth. // answer is in dotlist array and @@ -2983,7 +2166,7 @@ adddot(Node *n) out: if(c > 1) - yyerror("ambiguous DOT reference %T.%S", t, s); + yyerror("ambiguous selector %T.%S", t, s); // rebuild elided dots for(c=d-1; c>=0; c--) @@ -3028,8 +2211,6 @@ expand0(Type *t, int followptr) if(u->etype == TINTER) { for(f=u->type; f!=T; f=f->down) { - if(!exportname(f->sym->name) && f->sym->pkg != localpkg) - continue; if(f->sym->flags & SymUniq) continue; f->sym->flags |= SymUniq; @@ -3045,8 +2226,6 @@ expand0(Type *t, int followptr) u = methtype(t); if(u != T) { for(f=u->method; f!=T; f=f->down) { - if(!exportname(f->sym->name) && f->sym->pkg != localpkg) - continue; if(f->sym->flags & SymUniq) continue; f->sym->flags |= SymUniq; @@ -3122,8 +2301,11 @@ expandmeth(Sym *s, Type *t) if(c == 0) continue; if(c == 1) { - sl->good = 1; - sl->field = f; + // addot1 may have dug out arbitrary fields, we only want methods. + if(f->type->etype == TFUNC && f->type->thistuple > 0) { + sl->good = 1; + sl->field = f; + } } break; } @@ -3164,13 +2346,12 @@ structargs(Type **tl, int mustname) gen = 0; for(t = structfirst(&savet, tl); t != T; t = structnext(&savet)) { n = N; - if(t->sym) - n = newname(t->sym); - else if(mustname) { - // have to give it a name so we can refer to it in trampoline + if(mustname && (t->sym == nil || strcmp(t->sym->name, "_") == 0)) { + // invent a name so that we can refer to it in the trampoline snprint(buf, sizeof buf, ".anon%d", gen++); n = newname(lookup(buf)); - } + } else if(t->sym) + n = newname(t->sym); a = nod(ODCLFIELD, n, typenod(t->type)); a->isddd = t->isddd; if(n != N) @@ -3212,7 +2393,7 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface) int isddd; Val v; - if(0 && debug['r']) + if(debug['r']) print("genwrapper rcvrtype=%T method=%T newnam=%S\n", rcvr, method, newnam); @@ -3226,8 +2407,6 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface) in = structargs(getinarg(method->type), 1); out = structargs(getoutarg(method->type), 0); - fn = nod(ODCLFUNC, N, N); - fn->nname = newname(newnam); t = nod(OTFUNC, N, N); l = list1(this); if(iface && rcvr->width < types[tptr]->width) { @@ -3244,7 +2423,12 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface) } t->list = concat(l, in); t->rlist = out; + + fn = nod(ODCLFUNC, N, N); + fn->nname = newname(newnam); + fn->nname->defn = fn; fn->nname->ntype = t; + declare(fn->nname, PFUNC); funchdr(fn); // arg list @@ -3298,6 +2482,443 @@ genwrapper(Type *rcvr, Type *method, Sym *newnam, int iface) funccompile(fn, 0); } +static Node* +hashmem(Type *t, vlong width) +{ + Node *tfn, *n; + Sym *sym; + + sym = pkglookup("memhash", runtimepkg); + + n = newname(sym); + n->class = PFUNC; + tfn = nod(OTFUNC, N, N); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(ptrto(types[TUINTPTR])))); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(types[TUINTPTR]))); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(ptrto(t)))); + typecheck(&tfn, Etype); + n->type = tfn->type; + return n; +} + +static Node* +hashfor(Type *t) +{ + int a; + Sym *sym; + Node *tfn, *n; + + a = algtype1(t, nil); + switch(a) { + case AMEM: + return hashmem(t, t->width); + case AINTER: + sym = pkglookup("interhash", runtimepkg); + break; + case ANILINTER: + sym = pkglookup("nilinterhash", runtimepkg); + break; + case ASTRING: + sym = pkglookup("strhash", runtimepkg); + break; + case AFLOAT32: + sym = pkglookup("f32hash", runtimepkg); + break; + case AFLOAT64: + sym = pkglookup("f64hash", runtimepkg); + break; + case ACPLX64: + sym = pkglookup("c64hash", runtimepkg); + break; + case ACPLX128: + sym = pkglookup("c128hash", runtimepkg); + break; + default: + sym = typesymprefix(".hash", t); + break; + } + + n = newname(sym); + n->class = PFUNC; + tfn = nod(OTFUNC, N, N); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(ptrto(types[TUINTPTR])))); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(types[TUINTPTR]))); + tfn->list = list(tfn->list, nod(ODCLFIELD, N, typenod(ptrto(t)))); + typecheck(&tfn, Etype); + n->type = tfn->type; + return n; +} + +/* + * Generate a helper function to compute the hash of a value of type t. + */ +void +genhash(Sym *sym, Type *t) +{ + Node *n, *fn, *np, *nh, *ni, *call, *nx, *na, *tfn; + Node *hashel; + Type *first, *t1; + int old_safemode; + int64 size, mul; + + if(debug['r']) + print("genhash %S %T\n", sym, t); + + lineno = 1; // less confusing than end of input + dclcontext = PEXTERN; + markdcl(); + + // func sym(h *uintptr, s uintptr, p *T) + fn = nod(ODCLFUNC, N, N); + fn->nname = newname(sym); + fn->nname->class = PFUNC; + tfn = nod(OTFUNC, N, N); + fn->nname->ntype = tfn; + + n = nod(ODCLFIELD, newname(lookup("h")), typenod(ptrto(types[TUINTPTR]))); + tfn->list = list(tfn->list, n); + nh = n->left; + n = nod(ODCLFIELD, newname(lookup("s")), typenod(types[TUINTPTR])); + tfn->list = list(tfn->list, n); + n = nod(ODCLFIELD, newname(lookup("p")), typenod(ptrto(t))); + tfn->list = list(tfn->list, n); + np = n->left; + + funchdr(fn); + typecheck(&fn->nname->ntype, Etype); + + // genhash is only called for types that have equality but + // cannot be handled by the standard algorithms, + // so t must be either an array or a struct. + switch(t->etype) { + default: + fatal("genhash %T", t); + case TARRAY: + if(isslice(t)) + fatal("genhash %T", t); + // An array of pure memory would be handled by the + // standard algorithm, so the element type must not be + // pure memory. + hashel = hashfor(t->type); + n = nod(ORANGE, N, nod(OIND, np, N)); + ni = newname(lookup("i")); + ni->type = types[TINT]; + n->list = list1(ni); + n->colas = 1; + colasdefn(n->list, n); + ni = n->list->n; + + // *h = *h<<3 | *h>>61 + n->nbody = list(n->nbody, + nod(OAS, + nod(OIND, nh, N), + nod(OOR, + nod(OLSH, nod(OIND, nh, N), nodintconst(3)), + nod(ORSH, nod(OIND, nh, N), nodintconst(widthptr*8-3))))); + + // *h *= mul + // Same multipliers as in runtime.memhash. + if(widthptr == 4) + mul = 3267000013LL; + else + mul = 23344194077549503LL; + n->nbody = list(n->nbody, + nod(OAS, + nod(OIND, nh, N), + nod(OMUL, nod(OIND, nh, N), nodintconst(mul)))); + + // hashel(h, sizeof(p[i]), &p[i]) + call = nod(OCALL, hashel, N); + call->list = list(call->list, nh); + call->list = list(call->list, nodintconst(t->type->width)); + nx = nod(OINDEX, np, ni); + nx->etype = 1; // no bounds check + na = nod(OADDR, nx, N); + na->etype = 1; // no escape to heap + call->list = list(call->list, na); + n->nbody = list(n->nbody, call); + + fn->nbody = list(fn->nbody, n); + break; + + case TSTRUCT: + // Walk the struct using memhash for runs of AMEM + // and calling specific hash functions for the others. + first = T; + for(t1=t->type;; t1=t1->down) { + if(t1 != T && algtype1(t1->type, nil) == AMEM) { + if(first == T) + first = t1; + continue; + } + // Run memhash for fields up to this one. + if(first != T) { + if(first->down == t1) + size = first->type->width; + else if(t1 == T) + size = t->width - first->width; // first->width is offset + else + size = t1->width - first->width; // both are offsets + hashel = hashmem(first->type, size); + // hashel(h, size, &p.first) + call = nod(OCALL, hashel, N); + call->list = list(call->list, nh); + call->list = list(call->list, nodintconst(size)); + nx = nod(OXDOT, np, newname(first->sym)); // TODO: fields from other packages? + na = nod(OADDR, nx, N); + na->etype = 1; // no escape to heap + call->list = list(call->list, na); + fn->nbody = list(fn->nbody, call); + + first = T; + } + if(t1 == T) + break; + + // Run hash for this field. + hashel = hashfor(t1->type); + // hashel(h, size, &p.t1) + call = nod(OCALL, hashel, N); + call->list = list(call->list, nh); + call->list = list(call->list, nodintconst(t1->type->width)); + nx = nod(OXDOT, np, newname(t1->sym)); // TODO: fields from other packages? + na = nod(OADDR, nx, N); + na->etype = 1; // no escape to heap + call->list = list(call->list, na); + fn->nbody = list(fn->nbody, call); + } + break; + } + + if(debug['r']) + dumplist("genhash body", fn->nbody); + + funcbody(fn); + curfn = fn; + fn->dupok = 1; + typecheck(&fn, Etop); + typechecklist(fn->nbody, Etop); + curfn = nil; + + // Disable safemode while compiling this code: the code we + // generate internally can refer to unsafe.Pointer. + // In this case it can happen if we need to generate an == + // for a struct containing a reflect.Value, which itself has + // an unexported field of type unsafe.Pointer. + old_safemode = safemode; + safemode = 0; + funccompile(fn, 0); + safemode = old_safemode; +} + +// Return node for +// if p.field != q.field { *eq = false; return } +static Node* +eqfield(Node *p, Node *q, Node *field, Node *eq) +{ + Node *nif, *nx, *ny; + + nx = nod(OXDOT, p, field); + ny = nod(OXDOT, q, field); + nif = nod(OIF, N, N); + nif->ntest = nod(ONE, nx, ny); + nif->nbody = list(nif->nbody, nod(OAS, nod(OIND, eq, N), nodbool(0))); + nif->nbody = list(nif->nbody, nod(ORETURN, N, N)); + return nif; +} + +static Node* +eqmemfunc(vlong size, Type *type) +{ + char buf[30]; + Node *fn; + + switch(size) { + default: + fn = syslook("memequal", 1); + break; + case 1: + case 2: + case 4: + case 8: + case 16: + snprint(buf, sizeof buf, "memequal%d", (int)size*8); + fn = syslook(buf, 1); + break; + } + argtype(fn, type); + argtype(fn, type); + return fn; +} + +// Return node for +// if memequal(size, &p.field, &q.field, eq); !*eq { return } +static Node* +eqmem(Node *p, Node *q, Node *field, vlong size, Node *eq) +{ + Node *nif, *nx, *ny, *call; + + nx = nod(OADDR, nod(OXDOT, p, field), N); + nx->etype = 1; // does not escape + ny = nod(OADDR, nod(OXDOT, q, field), N); + ny->etype = 1; // does not escape + typecheck(&nx, Erv); + typecheck(&ny, Erv); + + call = nod(OCALL, eqmemfunc(size, nx->type->type), N); + call->list = list(call->list, eq); + call->list = list(call->list, nodintconst(size)); + call->list = list(call->list, nx); + call->list = list(call->list, ny); + + nif = nod(OIF, N, N); + nif->ninit = list(nif->ninit, call); + nif->ntest = nod(ONOT, nod(OIND, eq, N), N); + nif->nbody = list(nif->nbody, nod(ORETURN, N, N)); + return nif; +} + +/* + * Generate a helper function to check equality of two values of type t. + */ +void +geneq(Sym *sym, Type *t) +{ + Node *n, *fn, *np, *neq, *nq, *tfn, *nif, *ni, *nx, *ny, *nrange; + Type *t1, *first; + int old_safemode; + int64 size; + + if(debug['r']) + print("geneq %S %T\n", sym, t); + + lineno = 1; // less confusing than end of input + dclcontext = PEXTERN; + markdcl(); + + // func sym(eq *bool, s uintptr, p, q *T) + fn = nod(ODCLFUNC, N, N); + fn->nname = newname(sym); + fn->nname->class = PFUNC; + tfn = nod(OTFUNC, N, N); + fn->nname->ntype = tfn; + + n = nod(ODCLFIELD, newname(lookup("eq")), typenod(ptrto(types[TBOOL]))); + tfn->list = list(tfn->list, n); + neq = n->left; + n = nod(ODCLFIELD, newname(lookup("s")), typenod(types[TUINTPTR])); + tfn->list = list(tfn->list, n); + n = nod(ODCLFIELD, newname(lookup("p")), typenod(ptrto(t))); + tfn->list = list(tfn->list, n); + np = n->left; + n = nod(ODCLFIELD, newname(lookup("q")), typenod(ptrto(t))); + tfn->list = list(tfn->list, n); + nq = n->left; + + funchdr(fn); + + // geneq is only called for types that have equality but + // cannot be handled by the standard algorithms, + // so t must be either an array or a struct. + switch(t->etype) { + default: + fatal("geneq %T", t); + case TARRAY: + if(isslice(t)) + fatal("geneq %T", t); + // An array of pure memory would be handled by the + // standard memequal, so the element type must not be + // pure memory. Even if we unrolled the range loop, + // each iteration would be a function call, so don't bother + // unrolling. + nrange = nod(ORANGE, N, nod(OIND, np, N)); + ni = newname(lookup("i")); + ni->type = types[TINT]; + nrange->list = list1(ni); + nrange->colas = 1; + colasdefn(nrange->list, nrange); + ni = nrange->list->n; + + // if p[i] != q[i] { *eq = false; return } + nx = nod(OINDEX, np, ni); + nx->etype = 1; // no bounds check + ny = nod(OINDEX, nq, ni); + ny->etype = 1; // no bounds check + + nif = nod(OIF, N, N); + nif->ntest = nod(ONE, nx, ny); + nif->nbody = list(nif->nbody, nod(OAS, nod(OIND, neq, N), nodbool(0))); + nif->nbody = list(nif->nbody, nod(ORETURN, N, N)); + nrange->nbody = list(nrange->nbody, nif); + fn->nbody = list(fn->nbody, nrange); + + // *eq = true; + fn->nbody = list(fn->nbody, nod(OAS, nod(OIND, neq, N), nodbool(1))); + break; + + case TSTRUCT: + // Walk the struct using memequal for runs of AMEM + // and calling specific equality tests for the others. + first = T; + for(t1=t->type;; t1=t1->down) { + if(t1 != T && algtype1(t1->type, nil) == AMEM) { + if(first == T) + first = t1; + continue; + } + // Run memequal for fields up to this one. + // TODO(rsc): All the calls to newname are wrong for + // cross-package unexported fields. + if(first != T) { + if(first->down == t1) { + fn->nbody = list(fn->nbody, eqfield(np, nq, newname(first->sym), neq)); + } else if(first->down->down == t1) { + fn->nbody = list(fn->nbody, eqfield(np, nq, newname(first->sym), neq)); + first = first->down; + fn->nbody = list(fn->nbody, eqfield(np, nq, newname(first->sym), neq)); + } else { + // More than two fields: use memequal. + if(t1 == T) + size = t->width - first->width; // first->width is offset + else + size = t1->width - first->width; // both are offsets + fn->nbody = list(fn->nbody, eqmem(np, nq, newname(first->sym), size, neq)); + } + first = T; + } + if(t1 == T) + break; + + // Check this field, which is not just memory. + fn->nbody = list(fn->nbody, eqfield(np, nq, newname(t1->sym), neq)); + } + + // *eq = true; + fn->nbody = list(fn->nbody, nod(OAS, nod(OIND, neq, N), nodbool(1))); + break; + } + + if(debug['r']) + dumplist("geneq body", fn->nbody); + + funcbody(fn); + curfn = fn; + fn->dupok = 1; + typecheck(&fn, Etop); + typechecklist(fn->nbody, Etop); + curfn = nil; + + // Disable safemode while compiling this code: the code we + // generate internally can refer to unsafe.Pointer. + // In this case it can happen if we need to generate an == + // for a struct containing a reflect.Value, which itself has + // an unexported field of type unsafe.Pointer. + old_safemode = safemode; + safemode = 0; + funccompile(fn, 0); + safemode = old_safemode; +} + static Type* ifacelookdot(Sym *s, Type *t, int *followptr, int ignorecase) { @@ -3855,21 +3476,31 @@ ngotype(Node *n) } /* - * Convert raw string to the prefix that will be used in the symbol table. - * Invalid bytes turn into %xx. Right now the only bytes that need - * escaping are %, ., and ", but we escape all control characters too. + * Convert raw string to the prefix that will be used in the symbol + * table. All control characters, space, '%' and '"', as well as + * non-7-bit clean bytes turn into %xx. The period needs escaping + * only in the last segment of the path, and it makes for happier + * users if we escape that as little as possible. + * + * If you edit this, edit ../ld/lib.c:/^pathtoprefix copy too. */ static char* pathtoprefix(char *s) { static char hex[] = "0123456789abcdef"; - char *p, *r, *w; + char *p, *r, *w, *l; int n; + // find first character past the last slash, if any. + l = s; + for(r=s; *r; r++) + if(*r == '/') + l = r+1; + // check for chars that need escaping n = 0; for(r=s; *r; r++) - if(*r <= ' ' || *r == '.' || *r == '%' || *r == '"') + if(*r <= ' ' || (*r == '.' && r >= l) || *r == '%' || *r == '"' || *r >= 0x7f) n++; // quick exit @@ -3879,7 +3510,7 @@ pathtoprefix(char *s) // escape p = mal((r-s)+1+2*n); for(r=s, w=p; *r; r++) { - if(*r <= ' ' || *r == '.' || *r == '%' || *r == '"') { + if(*r <= ' ' || (*r == '.' && r >= l) || *r == '%' || *r == '"' || *r >= 0x7f) { *w++ = '%'; *w++ = hex[(*r>>4)&0xF]; *w++ = hex[*r&0xF]; @@ -3924,3 +3555,26 @@ strlit(char *s) t->len = strlen(s); return t; } + +void +addinit(Node **np, NodeList *init) +{ + Node *n; + + if(init == nil) + return; + + n = *np; + switch(n->op) { + case ONAME: + case OLITERAL: + // There may be multiple refs to this node; + // introduce OCONVNOP to hold init list. + n = nod(OCONVNOP, n, N); + n->type = n->left->type; + n->typecheck = 1; + *np = n; + break; + } + n->ninit = concat(init, n->ninit); +} diff --git a/src/cmd/gc/swt.c b/src/cmd/gc/swt.c index 0381132d0..f1a95587f 100644 --- a/src/cmd/gc/swt.c +++ b/src/cmd/gc/swt.c @@ -132,6 +132,7 @@ exprcmp(Case *c1, Case *c2) n = mpcmpfltflt(n1->val.u.fval, n2->val.u.fval); break; case CTINT: + case CTRUNE: n = mpcmpfixfix(n1->val.u.xval, n2->val.u.xval); break; case CTSTR: @@ -380,6 +381,7 @@ mkcaselist(Node *sw, int arg) switch(consttype(n->left)) { case CTFLT: case CTINT: + case CTRUNE: case CTSTR: c->type = Texprconst; } @@ -538,7 +540,7 @@ loop: } // deal with the variables one-at-a-time - if(c0->type != Texprconst) { + if(!okforcmp[t->etype] || c0->type != Texprconst) { a = exprbsw(c0, 1, arg); cas = list(cas, a); c0 = c0->link; @@ -790,7 +792,6 @@ walkswitch(Node *sw) * cases have OGOTO into statements. * both have inserted OBREAK statements */ - walkstmtlist(sw->ninit); if(sw->ntest == N) { sw->ntest = nodbool(1); typecheck(&sw->ntest, Erv); @@ -810,14 +811,16 @@ walkswitch(Node *sw) void typecheckswitch(Node *n) { - int top, lno; - Type *t; + int top, lno, ptr; + char *nilonly; + Type *t, *missing, *have; NodeList *l, *ll; Node *ncase, *nvar; Node *def; lno = lineno; typechecklist(n->ninit, Etop); + nilonly = nil; if(n->ntest != N && n->ntest->op == OTYPESW) { // type switch @@ -825,7 +828,7 @@ typecheckswitch(Node *n) typecheck(&n->ntest->right, Erv); t = n->ntest->right->type; if(t != T && t->etype != TINTER) - yyerror("cannot type switch on non-interface value %+N", n->ntest->right); + yyerror("cannot type switch on non-interface value %lN", n->ntest->right); } else { // value switch top = Erv; @@ -835,6 +838,16 @@ typecheckswitch(Node *n) t = n->ntest->type; } else t = types[TBOOL]; + if(t) { + if(!okforeq[t->etype] || isfixedarray(t)) + yyerror("cannot switch on %lN", n->ntest); + else if(t->etype == TARRAY) + nilonly = "slice"; + else if(t->etype == TFUNC) + nilonly = "func"; + else if(t->etype == TMAP) + nilonly = "map"; + } } n->type = t; @@ -854,21 +867,37 @@ typecheckswitch(Node *n) typecheck(&ll->n, Erv | Etype); if(ll->n->type == T || t == T) continue; + setlineno(ncase); switch(top) { case Erv: // expression switch defaultlit(&ll->n, t); if(ll->n->op == OTYPE) yyerror("type %T is not an expression", ll->n->type); - else if(ll->n->type != T && !eqtype(ll->n->type, t)) - yyerror("case %+N in %T switch", ll->n, t); + else if(ll->n->type != T && !assignop(ll->n->type, t, nil) && !assignop(t, ll->n->type, nil)) { + if(n->ntest) + yyerror("invalid case %N in switch on %N (mismatched types %T and %T)", ll->n, n->ntest, ll->n->type, t); + else + yyerror("invalid case %N in switch (mismatched types %T and bool)", ll->n, ll->n->type); + } else if(nilonly && !isconst(ll->n, CTNIL)) { + yyerror("invalid case %N in switch (can only compare %s %N to nil)", ll->n, nilonly, n->ntest); + } break; case Etype: // type switch if(ll->n->op == OLITERAL && istype(ll->n->type, TNIL)) { ; - } else if(ll->n->op != OTYPE && ll->n->type != T) { - yyerror("%#N is not a type", ll->n); + } else if(ll->n->op != OTYPE && ll->n->type != T) { // should this be ||? + yyerror("%lN is not a type", ll->n); // reset to original type ll->n = n->ntest->right; + } else if(ll->n->type->etype != TINTER && !implements(ll->n->type, t, &missing, &have, &ptr)) { + if(have && !missing->broke && !have->broke) + yyerror("impossible type switch case: %lN cannot have dynamic type %T" + " (wrong type for %S method)\n\thave %S%hT\n\twant %S%hT", + n->ntest->right, ll->n->type, missing->sym, have->sym, have->type, + missing->sym, missing->type); + else if(!missing->broke) + yyerror("impossible type switch case: %lN cannot have dynamic type %T" + " (missing %S method)", n->ntest->right, ll->n->type, missing->sym); } break; } diff --git a/src/cmd/gc/typecheck.c b/src/cmd/gc/typecheck.c index b9c302ce8..2e8c3b1e2 100644 --- a/src/cmd/gc/typecheck.c +++ b/src/cmd/gc/typecheck.c @@ -43,7 +43,7 @@ resolve(Node *n) { Node *r; - if(n != N && n->op == ONONAME && (r = n->sym->def) != N) { + if(n != N && n->op == ONONAME && n->sym != S && (r = n->sym->def) != N) { if(r->op != OIOTA) n = r; else if(n->iota >= 0) @@ -79,6 +79,7 @@ static char* _typekind[] = { [TSTRING] = "string", [TPTR32] = "pointer", [TPTR64] = "pointer", + [TUNSAFEPTR] = "unsafe.Pointer", [TSTRUCT] = "struct", [TINTER] = "interface", [TCHAN] = "chan", @@ -90,11 +91,15 @@ static char* _typekind[] = { }; static char* -typekind(int et) +typekind(Type *t) { + int et; static char buf[50]; char *s; + if(isslice(t)) + return "slice"; + et = t->etype; if(0 <= et && et < nelem(_typekind) && (s=_typekind[et]) != nil) return s; snprint(buf, sizeof buf, "etype=%d", et); @@ -113,8 +118,7 @@ typecheck(Node **np, int top) Node *n, *l, *r; NodeList *args; int lno, ok, ntop; - Type *t, *tp, *ft, *missing, *have; - Sym *sym; + Type *t, *tp, *ft, *missing, *have, *badtype; Val v; char *why; @@ -153,7 +157,7 @@ typecheck(Node **np, int top) } if(n->typecheck == 2) { - yyerror("typechecking loop involving %#N", n); + yyerror("typechecking loop involving %N", n); lineno = lno; return n; } @@ -210,6 +214,10 @@ reswitch: } n->used = 1; } + if(!(top &Ecall) && isunsafebuiltin(n)) { + yyerror("%N is not an expression, must be called", n); + goto error; + } ok |= Erv; goto ret; @@ -254,13 +262,14 @@ reswitch: l = typecheck(&n->left, Erv); switch(consttype(l)) { case CTINT: + case CTRUNE: v = l->val; break; case CTFLT: v = toint(l->val); break; default: - yyerror("invalid array bound %#N", l); + yyerror("invalid array bound %N", l); goto error; } t->bound = mpgetfix(v.u.xval); @@ -311,7 +320,7 @@ reswitch: case OTSTRUCT: ok |= Etype; n->op = OTYPE; - n->type = dostruct(n->list, TSTRUCT); + n->type = tostruct(n->list); if(n->type == T) goto error; n->list = nil; @@ -320,7 +329,7 @@ reswitch: case OTINTER: ok |= Etype; n->op = OTYPE; - n->type = dostruct(n->list, TINTER); + n->type = tointerface(n->list); if(n->type == T) goto error; break; @@ -340,6 +349,7 @@ reswitch: ntop = Erv | Etype; if(!(top & Eaddr)) // The *x in &*x is not an indirect. ntop |= Eindir; + ntop |= top & Ecomplit; l = typecheck(&n->left, ntop); if((t = l->type) == T) goto error; @@ -351,7 +361,7 @@ reswitch: goto ret; } if(!isptr[t->etype]) { - yyerror("invalid indirect of %+N", n->left); + yyerror("invalid indirect of %lN", n->left); goto error; } ok |= Erv; @@ -414,15 +424,25 @@ reswitch: if(iscmp[n->op] && t->etype != TIDEAL && !eqtype(l->type, r->type)) { // comparison is okay as long as one side is // assignable to the other. convert so they have - // the same type. (the only conversion that isn't - // a no-op is concrete == interface.) + // the same type. + // + // the only conversion that isn't a no-op is concrete == interface. + // in that case, check comparability of the concrete type. if(r->type->etype != TBLANK && (aop = assignop(l->type, r->type, nil)) != 0) { + if(isinter(r->type) && !isinter(l->type) && algtype1(l->type, nil) == ANOEQ) { + yyerror("invalid operation: %N (operator %O not defined on %s)", n, op, typekind(l->type)); + goto error; + } l = nod(aop, l, N); l->type = r->type; l->typecheck = 1; n->left = l; t = l->type; } else if(l->type->etype != TBLANK && (aop = assignop(r->type, l->type, nil)) != 0) { + if(isinter(l->type) && !isinter(r->type) && algtype1(r->type, nil) == ANOEQ) { + yyerror("invalid operation: %N (operator %O not defined on %s)", n, op, typekind(r->type)); + goto error; + } r = nod(aop, r, N); r->type = l->type; r->typecheck = 1; @@ -433,24 +453,36 @@ reswitch: } if(t->etype != TIDEAL && !eqtype(l->type, r->type)) { defaultlit2(&l, &r, 1); - yyerror("invalid operation: %#N (mismatched types %T and %T)", n, l->type, r->type); + yyerror("invalid operation: %N (mismatched types %T and %T)", n, l->type, r->type); goto error; } if(!okfor[op][et]) { - notokfor: - yyerror("invalid operation: %#N (operator %#O not defined on %s)", n, op, typekind(et)); + yyerror("invalid operation: %N (operator %O not defined on %s)", n, op, typekind(t)); + goto error; + } + // okfor allows any array == array, map == map, func == func. + // restrict to slice/map/func == nil and nil == slice/map/func. + if(isfixedarray(l->type) && algtype1(l->type, nil) == ANOEQ) { + yyerror("invalid operation: %N (%T cannot be compared)", n, l->type); goto error; } - // okfor allows any array == array; - // restrict to slice == nil and nil == slice. - if(l->type->etype == TARRAY && !isslice(l->type)) - goto notokfor; - if(r->type->etype == TARRAY && !isslice(r->type)) - goto notokfor; if(isslice(l->type) && !isnil(l) && !isnil(r)) { - yyerror("invalid operation: %#N (slice can only be compared to nil)", n); + yyerror("invalid operation: %N (slice can only be compared to nil)", n); goto error; } + if(l->type->etype == TMAP && !isnil(l) && !isnil(r)) { + yyerror("invalid operation: %N (map can only be compared to nil)", n); + goto error; + } + if(l->type->etype == TFUNC && !isnil(l) && !isnil(r)) { + yyerror("invalid operation: %N (func can only be compared to nil)", n); + goto error; + } + if(l->type->etype == TSTRUCT && algtype1(l->type, &badtype) == ANOEQ) { + yyerror("invalid operation: %N (struct containing %T cannot be compared)", n, badtype); + goto error; + } + t = l->type; if(iscmp[n->op]) { evconst(n); @@ -488,12 +520,12 @@ reswitch: n->right = r; t = r->type; if(!isint[t->etype] || issigned[t->etype]) { - yyerror("invalid operation: %#N (shift count type %T, must be unsigned integer)", n, r->type); + yyerror("invalid operation: %N (shift count type %T, must be unsigned integer)", n, r->type); goto error; } t = l->type; if(t != T && t->etype != TIDEAL && !isint[t->etype]) { - yyerror("invalid operation: %#N (shift of type %T)", n, t); + yyerror("invalid operation: %N (shift of type %T)", n, t); goto error; } // no defaultlit for left @@ -510,7 +542,7 @@ reswitch: if((t = l->type) == T) goto error; if(!okfor[n->op][t->etype]) { - yyerror("invalid operation: %#O %T", n->op, t); + yyerror("invalid operation: %O %T", n->op, t); goto error; } n->type = t; @@ -524,21 +556,17 @@ reswitch: typecheck(&n->left, Erv | Eaddr); if(n->left->type == T) goto error; - switch(n->left->op) { - case OMAPLIT: - case OSTRUCTLIT: - case OARRAYLIT: - break; - default: - checklvalue(n->left, "take the address of"); - } + checklvalue(n->left, "take the address of"); + for(l=n->left; l->op == ODOT; l=l->left) + l->addrtaken = 1; + l->addrtaken = 1; defaultlit(&n->left, T); l = n->left; if((t = l->type) == T) goto error; // top&Eindir means this is &x in *&x. (or the arg to built-in print) // n->etype means code generator flagged it as non-escaping. - if(debug['s'] && !(top & Eindir) && !n->etype) + if(debug['N'] && !(top & Eindir) && !n->etype) addrescapes(n->left); n->type = ptrto(t); goto ret; @@ -557,36 +585,34 @@ reswitch: case ODOT: typecheck(&n->left, Erv|Etype); defaultlit(&n->left, T); - l = n->left; - if((t = l->type) == T) + if((t = n->left->type) == T) goto error; if(n->right->op != ONAME) { yyerror("rhs of . must be a name"); // impossible goto error; } - sym = n->right->sym; - if(l->op == OTYPE) { + + if(n->left->op == OTYPE) { if(!looktypedot(n, t, 0)) { if(looktypedot(n, t, 1)) - yyerror("%#N undefined (cannot refer to unexported method %S)", n, n->right->sym); + yyerror("%N undefined (cannot refer to unexported method %S)", n, n->right->sym); else - yyerror("%#N undefined (type %T has no method %S)", n, t, n->right->sym); + yyerror("%N undefined (type %T has no method %S)", n, t, n->right->sym); goto error; } if(n->type->etype != TFUNC || n->type->thistuple != 1) { - yyerror("type %T has no method %hS", n->left->type, sym); + yyerror("type %T has no method %hS", n->left->type, n->right->sym); n->type = T; goto error; } n->op = ONAME; - n->sym = methodsym(sym, l->type, 0); - n->type = methodfunc(n->type, l->type); + n->sym = n->right->sym; + n->type = methodfunc(n->type, n->left->type); n->xoffset = 0; n->class = PFUNC; ok = Erv; goto ret; } - tp = t; if(isptr[t->etype] && t->type->etype != TINTER) { t = t->type; if(t == T) @@ -596,9 +622,9 @@ reswitch: } if(!lookdot(n, t, 0)) { if(lookdot(n, t, 1)) - yyerror("%#N undefined (cannot refer to unexported field or method %S)", n, n->right->sym); + yyerror("%N undefined (cannot refer to unexported field or method %S)", n, n->right->sym); else - yyerror("%#N undefined (type %T has no field or method %S)", n, tp, n->right->sym); + yyerror("%N undefined (type %T has no field or method %S)", n, n->left->type, n->right->sym); goto error; } switch(n->op) { @@ -620,7 +646,7 @@ reswitch: if((t = l->type) == T) goto error; if(!isinter(t)) { - yyerror("invalid type assertion: %#N (non-interface type %T on left)", n, t); + yyerror("invalid type assertion: %N (non-interface type %T on left)", n, t); goto error; } if(n->right != N) { @@ -633,12 +659,12 @@ reswitch: if(n->type != T && n->type->etype != TINTER) if(!implements(n->type, t, &missing, &have, &ptr)) { if(have) - yyerror("impossible type assertion: %+N cannot have dynamic type %T" - " (wrong type for %S method)\n\thave %S%hhT\n\twant %S%hhT", + yyerror("impossible type assertion: %lN cannot have dynamic type %T" + " (wrong type for %S method)\n\thave %S%hT\n\twant %S%hT", l, n->type, missing->sym, have->sym, have->type, missing->sym, missing->type); else - yyerror("impossible type assertion: %+N cannot have dynamic type %T" + yyerror("impossible type assertion: %lN cannot have dynamic type %T" " (missing %S method)", l, n->type, missing->sym); goto error; } @@ -656,13 +682,13 @@ reswitch: goto error; switch(t->etype) { default: - yyerror("invalid operation: %#N (index of type %T)", n, t); + yyerror("invalid operation: %N (index of type %T)", n, t); goto error; case TARRAY: defaultlit(&n->right, T); if(n->right->type != T && !isint[n->right->type->etype]) - yyerror("non-integer array index %#N", n->right); + yyerror("non-integer array index %N", n->right); n->type = t->type; break; @@ -678,7 +704,7 @@ reswitch: case TSTRING: defaultlit(&n->right, types[TUINT]); if(n->right->type != T && !isint[n->right->type->etype]) - yyerror("non-integer string index %#N", n->right); + yyerror("non-integer string index %N", n->right); n->type = types[TUINT8]; break; } @@ -692,11 +718,11 @@ reswitch: if((t = l->type) == T) goto error; if(t->etype != TCHAN) { - yyerror("invalid operation: %#N (receive from non-chan type %T)", n, t); + yyerror("invalid operation: %N (receive from non-chan type %T)", n, t); goto error; } if(!(t->chan & Crecv)) { - yyerror("invalid operation: %#N (receive from send-only type %T)", n, t); + yyerror("invalid operation: %N (receive from send-only type %T)", n, t); goto error; } n->type = t->type; @@ -704,7 +730,7 @@ reswitch: case OSEND: if(top & Erv) { - yyerror("send statement %#N used as value; use select for non-blocking send", n); + yyerror("send statement %N used as value; use select for non-blocking send", n); goto error; } ok |= Etop | Erv; @@ -715,11 +741,11 @@ reswitch: if((t = l->type) == T) goto error; if(t->etype != TCHAN) { - yyerror("invalid operation: %#N (send to non-chan type %T)", n, t); + yyerror("invalid operation: %N (send to non-chan type %T)", n, t); goto error; } if(!(t->chan & Csend)) { - yyerror("invalid operation: %#N (send to receive-only type %T)", n, t); + yyerror("invalid operation: %N (send to receive-only type %T)", n, t); goto error; } defaultlit(&n->right, t->type); @@ -741,14 +767,19 @@ reswitch: defaultlit(&n->right->left, T); defaultlit(&n->right->right, T); if(isfixedarray(n->left->type)) { + if(!islvalue(n->left)) { + yyerror("invalid operation %N (slice of unaddressable value)", n); + goto error; + } n->left = nod(OADDR, n->left, N); - typecheck(&n->left, top); + n->left->implicit = 1; + typecheck(&n->left, Erv); } if(n->right->left != N) { if((t = n->right->left->type) == T) goto error; if(!isint[t->etype]) { - yyerror("invalid slice index %#N (type %T)", n->right->left, t); + yyerror("invalid slice index %N (type %T)", n->right->left, t); goto error; } } @@ -756,7 +787,7 @@ reswitch: if((t = n->right->right->type) == T) goto error; if(!isint[t->etype]) { - yyerror("invalid slice index %#N (type %T)", n->right->right, t); + yyerror("invalid slice index %N (type %T)", n->right->right, t); goto error; } } @@ -780,7 +811,7 @@ reswitch: n->type = t; goto ret; } - yyerror("cannot slice %#N (type %T)", l, t); + yyerror("cannot slice %N (type %T)", l, t); goto error; /* @@ -790,7 +821,7 @@ reswitch: l = n->left; if(l->op == ONAME && (r = unsafenmagic(n)) != N) { if(n->isddd) - yyerror("invalid use of ... with builtin %#N", l); + yyerror("invalid use of ... with builtin %N", l); n = r; goto reswitch; } @@ -798,7 +829,7 @@ reswitch: l = n->left; if(l->op == ONAME && l->etype != 0) { if(n->isddd && l->etype != OAPPEND) - yyerror("invalid use of ... with builtin %#N", l); + yyerror("invalid use of ... with builtin %N", l); // builtin: OLEN, OCAP, etc. n->op = l->etype; n->left = n->right; @@ -848,7 +879,7 @@ reswitch: default: n->op = OCALLFUNC; if(t->etype != TFUNC) { - yyerror("cannot call non-function %#N (type %T)", l, t); + yyerror("cannot call non-function %N (type %T)", l, t); goto error; } break; @@ -869,7 +900,7 @@ reswitch: } // multiple return if(!(top & (Efnstruct | Etop))) { - yyerror("multiple-value %#N() in single-value context", l); + yyerror("multiple-value %N() in single-value context", l); goto ret; } n->type = getoutargx(l->type); @@ -880,7 +911,7 @@ reswitch: case OREAL: case OIMAG: ok |= Erv; - if(onearg(n, "%#O", n->op) < 0) + if(onearg(n, "%O", n->op) < 0) goto error; typecheck(&n->left, Erv); defaultlit(&n->left, T); @@ -946,7 +977,7 @@ reswitch: n->right = r; if(l->type->etype != r->type->etype) { badcmplx: - yyerror("invalid operation: %#N (complex of types %T, %T)", n, l->type, r->type); + yyerror("invalid operation: %N (complex of types %T, %T)", n, l->type, r->type); goto error; } switch(l->type->etype) { @@ -970,7 +1001,7 @@ reswitch: goto ret; case OCLOSE: - if(onearg(n, "%#O", n->op) < 0) + if(onearg(n, "%O", n->op) < 0) goto error; typecheck(&n->left, Erv); defaultlit(&n->left, T); @@ -978,10 +1009,39 @@ reswitch: if((t = l->type) == T) goto error; if(t->etype != TCHAN) { - yyerror("invalid operation: %#N (non-chan type %T)", n, t); + yyerror("invalid operation: %N (non-chan type %T)", n, t); + goto error; + } + if(!(t->chan & Csend)) { + yyerror("invalid operation: %N (cannot close receive-only channel)", n); + goto error; + } + ok |= Etop; + goto ret; + + case ODELETE: + args = n->list; + if(args == nil) { + yyerror("missing arguments to delete"); + goto error; + } + if(args->next == nil) { + yyerror("missing second (key) argument to delete"); + goto error; + } + if(args->next->next != nil) { + yyerror("too many arguments to delete"); goto error; } ok |= Etop; + typechecklist(args, Erv); + l = args->n; + r = args->next->n; + if(l->type != T && l->type->etype != TMAP) { + yyerror("first argument to delete must be map; have %lT", l->type); + goto error; + } + args->next->n = assignconv(r, l->type->down, "delete"); goto ret; case OAPPEND: @@ -999,6 +1059,7 @@ reswitch: yyerror("first argument to append must be slice; have %lT", t); goto error; } + if(n->isddd) { if(args->next == nil) { yyerror("cannot use ... on first argument to append"); @@ -1008,6 +1069,10 @@ reswitch: yyerror("too many arguments to append"); goto error; } + if(istype(t->type, TUINT8) && istype(args->next->n->type, TSTRING)) { + defaultlit(&args->next->n, types[TSTRING]); + goto ret; + } args->next->n = assignconv(args->next->n, t->orig, "append"); goto ret; } @@ -1039,15 +1104,15 @@ reswitch: goto error; defaultlit(&n->left, T); defaultlit(&n->right, T); - + // copy([]byte, string) if(isslice(n->left->type) && n->right->type->etype == TSTRING) { - if(n->left->type->type == types[TUINT8]) + if(eqtype(n->left->type->type, bytetype)) goto ret; yyerror("arguments to copy have different element types: %lT and string", n->left->type); goto error; } - + if(!isslice(n->left->type) || !isslice(n->right->type)) { if(!isslice(n->left->type) && !isslice(n->right->type)) yyerror("arguments to copy must be slices; have %lT, %lT", n->left->type, n->right->type); @@ -1071,7 +1136,7 @@ reswitch: if((t = n->left->type) == T || n->type == T) goto error; if((n->op = convertop(t, n->type, &why)) == 0) { - yyerror("cannot convert %+N to type %T%s", n->left, n->type, why); + yyerror("cannot convert %lN to type %T%s", n->left, n->type, why); n->op = OCONV; } switch(n->op) { @@ -1096,6 +1161,7 @@ reswitch: yyerror("missing argument to make"); goto error; } + n->list = nil; l = args->n; args = args->next; typecheck(&l, Etype); @@ -1275,7 +1341,7 @@ reswitch: typechecklist(n->ninit, Etop); typecheck(&n->ntest, Erv); if(n->ntest != N && (t = n->ntest->type) != T && t->etype != TBOOL) - yyerror("non-bool %+N used as for condition", n->ntest); + yyerror("non-bool %lN used as for condition", n->ntest); typecheck(&n->nincr, Etop); typechecklist(n->nbody, Etop); goto ret; @@ -1285,7 +1351,7 @@ reswitch: typechecklist(n->ninit, Etop); typecheck(&n->ntest, Erv); if(n->ntest != N && (t = n->ntest->type) != T && t->etype != TBOOL) - yyerror("non-bool %+N used as if condition", n->ntest); + yyerror("non-bool %lN used as if condition", n->ntest); typechecklist(n->nbody, Etop); typechecklist(n->nelse, Etop); goto ret; @@ -1372,21 +1438,21 @@ ret: goto error; } if((top & (Erv|Etype)) == Etype && n->op != OTYPE) { - yyerror("%#N is not a type", n); + yyerror("%N is not a type", n); goto error; } if((ok & Ecall) && !(top & Ecall)) { - yyerror("method %#N is not an expression, must be called", n); + yyerror("method %N is not an expression, must be called", n); goto error; } // TODO(rsc): simplify if((top & (Ecall|Erv|Etype)) && !(top & Etop) && !(ok & (Erv|Etype|Ecall))) { - yyerror("%#N used as value", n); + yyerror("%N used as value", n); goto error; } if((top & Etop) && !(top & (Ecall|Erv|Etype)) && !(ok & Etop)) { if(n->diag == 0) { - yyerror("%#N not used", n); + yyerror("%N not used", n); n->diag = 1; } goto error; @@ -1399,7 +1465,7 @@ ret: goto out; badcall1: - yyerror("invalid argument %#N (type %T) for %#O", n->left, n->left->type, n->op); + yyerror("invalid argument %lN for %O", n->left, n->op); goto error; error: @@ -1445,14 +1511,14 @@ onearg(Node *n, char *f, ...) va_start(arg, f); p = vsmprint(f, arg); va_end(arg); - yyerror("missing argument to %s: %#N", p, n); + yyerror("missing argument to %s: %N", p, n); return -1; } if(n->list->next != nil) { va_start(arg, f); p = vsmprint(f, arg); va_end(arg); - yyerror("too many arguments to %s: %#N", p, n); + yyerror("too many arguments to %s: %N", p, n); n->left = n->list->n; n->list = nil; return -1; @@ -1468,17 +1534,17 @@ twoarg(Node *n) if(n->left != N) return 0; if(n->list == nil) { - yyerror("missing argument to %#O - %#N", n->op, n); + yyerror("missing argument to %O - %N", n->op, n); return -1; } n->left = n->list->n; if(n->list->next == nil) { - yyerror("missing argument to %#O - %#N", n->op, n); + yyerror("missing argument to %O - %N", n->op, n); n->list = nil; return -1; } if(n->list->next->next != nil) { - yyerror("too many arguments to %#O - %#N", n->op, n); + yyerror("too many arguments to %O - %N", n->op, n); n->list = nil; return -1; } @@ -1499,7 +1565,7 @@ lookdot1(Sym *s, Type *t, Type *f, int dostrcmp) if(f->sym != s) continue; if(r != T) { - yyerror("ambiguous DOT reference %T.%S", t, s); + yyerror("ambiguous selector %T.%S", t, s); break; } r = f; @@ -1547,7 +1613,7 @@ looktypedot(Node *n, Type *t, int dostrcmp) && !isptr[t->etype] && f2->embedded != 2 && !isifacemethod(f2->type)) { - yyerror("invalid method expression %#N (needs pointer receiver: (*%T).%s)", n, t, f2->sym->name); + yyerror("invalid method expression %N (needs pointer receiver: (*%T).%hS)", n, t, f2->sym); return 0; } @@ -1558,6 +1624,14 @@ looktypedot(Node *n, Type *t, int dostrcmp) return 1; } +static Type* +derefall(Type* t) +{ + while(t && t->etype == tptr) + t = t->type; + return t; +} + static int lookdot(Node *n, Type *t, int dostrcmp) { @@ -1583,7 +1657,7 @@ lookdot(Node *n, Type *t, int dostrcmp) if(f1 != T) { if(f2 != T) - yyerror("ambiguous DOT reference %S as both field and method", + yyerror("%S is both field and method", n->right->sym); if(f1->width == BADWIDTH) fatal("lookdot badwidth %T %p", f1, f1); @@ -1606,7 +1680,7 @@ lookdot(Node *n, Type *t, int dostrcmp) if(!eqtype(rcvr, tt)) { if(rcvr->etype == tptr && eqtype(rcvr->type, tt)) { checklvalue(n->left, "call pointer method on"); - if(debug['s']) + if(debug['N']) addrescapes(n->left); n->left = nod(OADDR, n->left, N); n->left->implicit = 1; @@ -1615,14 +1689,22 @@ lookdot(Node *n, Type *t, int dostrcmp) n->left = nod(OIND, n->left, N); n->left->implicit = 1; typecheck(&n->left, Etype|Erv); + } else if(tt->etype == tptr && tt->type->etype == tptr && eqtype(derefall(tt), rcvr)) { + yyerror("calling method %N with receiver %lN requires explicit dereference", n->right, n->left); + while(tt->etype == tptr) { + n->left = nod(OIND, n->left, N); + n->left->implicit = 1; + typecheck(&n->left, Etype|Erv); + tt = tt->type; + } } else { - // method is attached to wrong type? fatal("method mismatch: %T for %T", rcvr, tt); } } n->right = methodname(n->right, n->left->type); n->xoffset = f2->width; n->type = f2->type; +// print("lookdot found [%p] %T\n", f2->type, f2->type); n->op = ODOTMETH; return 1; } @@ -1661,22 +1743,20 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char * for(tl=tstruct->type; tl; tl=tl->down) { if(tl->isddd) { for(; tn; tn=tn->down) { - exportassignok(tn->type, desc); if(assignop(tn->type, tl->type->type, &why) == 0) { if(call != N) - yyerror("cannot use %T as type %T in argument to %#N%s", tn->type, tl->type->type, call, why); + yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type, call, why); else - yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type->type, desc, why); + yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type, desc, why); } } goto out; } if(tn == T) goto notenough; - exportassignok(tn->type, desc); if(assignop(tn->type, tl->type, &why) == 0) { if(call != N) - yyerror("cannot use %T as type %T in argument to %#N%s", tn->type, tl->type, call, why); + yyerror("cannot use %T as type %T in argument to %N%s", tn->type, tl->type, call, why); else yyerror("cannot use %T as type %T in %s%s", tn->type, tl->type, desc, why); } @@ -1721,9 +1801,9 @@ typecheckaste(int op, Node *call, int isddd, Type *tstruct, NodeList *nl, char * goto toomany; if(isddd) { if(call != N) - yyerror("invalid use of ... in call to %#N", call); + yyerror("invalid use of ... in call to %N", call); else - yyerror("invalid use of ... in %#O", op); + yyerror("invalid use of ... in %O", op); } out: @@ -1732,80 +1812,20 @@ out: notenough: if(call != N) - yyerror("not enough arguments in call to %#N", call); + yyerror("not enough arguments in call to %N", call); else - yyerror("not enough arguments to %#O", op); + yyerror("not enough arguments to %O", op); goto out; toomany: if(call != N) - yyerror("too many arguments in call to %#N", call); + yyerror("too many arguments in call to %N", call); else - yyerror("too many arguments to %#O", op); + yyerror("too many arguments to %O", op); goto out; } /* - * do the export rules allow writing to this type? - * cannot be implicitly assigning to any type with - * an unavailable field. - */ -int -exportassignok(Type *t, char *desc) -{ - Type *f; - Sym *s; - - if(t == T) - return 1; - if(t->trecur) - return 1; - t->trecur = 1; - - switch(t->etype) { - default: - // most types can't contain others; they're all fine. - break; - case TSTRUCT: - for(f=t->type; f; f=f->down) { - if(f->etype != TFIELD) - fatal("structas: not field"); - s = f->sym; - // s == nil doesn't happen for embedded fields (they get the type symbol). - // it only happens for fields in a ... struct. - if(s != nil && !exportname(s->name) && s->pkg != localpkg) { - char *prefix; - - prefix = ""; - if(desc != nil) - prefix = " in "; - else - desc = ""; - yyerror("implicit assignment of unexported field '%s' of %T%s%s", s->name, t, prefix, desc); - goto no; - } - if(!exportassignok(f->type, desc)) - goto no; - } - break; - - case TARRAY: - if(t->bound < 0) // slices are pointers; that's fine - break; - if(!exportassignok(t->type, desc)) - goto no; - break; - } - t->trecur = 0; - return 1; - -no: - t->trecur = 0; - return 0; -} - - -/* * type check composite */ @@ -1850,6 +1870,7 @@ keydup(Node *n, Node *hash[], ulong nhash) b = 23; break; case CTINT: + case CTRUNE: b = mpgetfix(n->val.u.xval); break; case CTFLT: @@ -1958,13 +1979,51 @@ inithash(Node *n, Node ***hash, Node **autohash, ulong nautohash) return h; } +static int +iscomptype(Type *t) +{ + switch(t->etype) { + case TARRAY: + case TSTRUCT: + case TMAP: + return 1; + case TPTR32: + case TPTR64: + switch(t->type->etype) { + case TARRAY: + case TSTRUCT: + case TMAP: + return 1; + } + break; + } + return 0; +} + +static void +pushtype(Node *n, Type *t) +{ + if(n == N || n->op != OCOMPLIT || !iscomptype(t)) + return; + + if(n->right == N) { + n->right = typenod(t); + n->right->implicit = 1; + } + else if(debug['s']) { + typecheck(&n->right, Etype); + if(n->right->type != T && eqtype(n->right->type, t)) + print("%lL: redundant type: %T\n", n->lineno, t); + } +} + static void typecheckcomplit(Node **np) { int bad, i, len, nerr; - Node *l, *n, **hash; + Node *l, *n, *r, **hash; NodeList *ll; - Type *t, *f, *pushtype; + Type *t, *f; Sym *s; int32 lno; ulong nhash; @@ -1979,30 +2038,29 @@ typecheckcomplit(Node **np) yyerror("missing type in composite literal"); goto error; } - + setlineno(n->right); l = typecheck(&n->right /* sic */, Etype|Ecomplit); if((t = l->type) == T) goto error; nerr = nerrors; - - // can omit type on composite literal values if the outer - // composite literal is array, slice, or map, and the - // element type is itself a struct, array, slice, or map. - pushtype = T; - if(t->etype == TARRAY || t->etype == TMAP) { - pushtype = t->type; - if(pushtype != T) { - switch(pushtype->etype) { - case TSTRUCT: - case TARRAY: - case TMAP: - break; - default: - pushtype = T; - break; - } + n->type = t; + + if(isptr[t->etype]) { + // For better or worse, we don't allow pointers as + // the composite literal type, except when using + // the &T syntax, which sets implicit. + if(!n->right->implicit) { + yyerror("invalid pointer type %T for composite literal (use &%T instead)", t, t->type); + goto error; + } + + // Also, the underlying type must be a struct, map, slice, or array. + if(!iscomptype(t)) { + yyerror("invalid pointer type %T for composite literal", t); + goto error; } + t = t->type; } switch(t->etype) { @@ -2045,11 +2103,11 @@ typecheckcomplit(Node **np) } } - if(l->right->op == OCOMPLIT && l->right->right == N && pushtype != T) - l->right->right = typenod(pushtype); - typecheck(&l->right, Erv); - defaultlit(&l->right, t->type); - l->right = assignconv(l->right, t->type, "array element"); + r = l->right; + pushtype(r, t->type); + typecheck(&r, Erv); + defaultlit(&r, t->type); + l->right = assignconv(r, t->type, "array element"); } if(t->bound == -100) t->bound = len; @@ -2073,13 +2131,14 @@ typecheckcomplit(Node **np) typecheck(&l->left, Erv); defaultlit(&l->left, t->down); l->left = assignconv(l->left, t->down, "map key"); - keydup(l->left, hash, nhash); + if (l->left->op != OCONV) + keydup(l->left, hash, nhash); - if(l->right->op == OCOMPLIT && l->right->right == N && pushtype != T) - l->right->right = typenod(pushtype); - typecheck(&l->right, Erv); - defaultlit(&l->right, t->type); - l->right = assignconv(l->right, t->type, "map value"); + r = l->right; + pushtype(r, t->type); + typecheck(&r, Erv); + defaultlit(&r, t->type); + l->right = assignconv(r, t->type, "map value"); } n->op = OMAPLIT; break; @@ -2100,6 +2159,7 @@ typecheckcomplit(Node **np) s = f->sym; if(s != nil && !exportname(s->name) && s->pkg != localpkg) yyerror("implicit assignment of unexported field '%s' in %T literal", s->name, t); + // No pushtype allowed here. Must name fields for that. ll->n = assignconv(ll->n, f->type, "field value"); ll->n = nod(OKEY, newname(f->sym), ll->n); ll->n->left->type = f; @@ -2123,19 +2183,20 @@ typecheckcomplit(Node **np) } s = l->left->sym; if(s == S) { - yyerror("invalid field name %#N in struct initializer", l->left); + yyerror("invalid field name %N in struct initializer", l->left); typecheck(&l->right, Erv); continue; } + // Sym might have resolved to name in other top-level // package, because of import dot. Redirect to correct sym // before we do the lookup. - if(s->pkg != localpkg) + if(s->pkg != localpkg && exportname(s->name)) s = lookup(s->name); + f = lookdot1(s, t, t->type, 0); - typecheck(&l->right, Erv); if(f == nil) { - yyerror("unknown %T field '%s' in struct literal", t, s->name); + yyerror("unknown %T field '%S' in struct literal", t, s); continue; } l->left = newname(s); @@ -2143,7 +2204,10 @@ typecheckcomplit(Node **np) l->left->type = f; s = f->sym; fielddup(newname(s), hash, nhash); - l->right = assignconv(l->right, f->type, "field value"); + r = l->right; + // No pushtype allowed here. Tried and rejected. + typecheck(&r, Erv); + l->right = assignconv(r, f->type, "field value"); } } n->op = OSTRUCTLIT; @@ -2151,7 +2215,14 @@ typecheckcomplit(Node **np) } if(nerr != nerrors) goto error; - n->type = t; + + if(isptr[n->type->etype]) { + n = nod(OPTRLIT, n, N); + n->typecheck = 1; + n->type = n->left->type; + n->left->type = t; + n->left->typecheck = 1; + } *np = n; lineno = lno; @@ -2193,7 +2264,7 @@ static void checklvalue(Node *n, char *verb) { if(!islvalue(n)) - yyerror("cannot %s %#N", verb, n); + yyerror("cannot %s %N", verb, n); } static void @@ -2205,7 +2276,7 @@ checkassign(Node *n) n->etype = 1; return; } - yyerror("cannot assign to %#N", n); + yyerror("cannot assign to %N", n); } static void @@ -2240,8 +2311,6 @@ typecheckas(Node *n) if(n->right && n->right->type != T) { if(n->left->type != T) n->right = assignconv(n->right, n->left->type, "assignment"); - else if(!isblank(n->left)) - exportassignok(n->right->type, "assignment"); } if(n->left->defn == n && n->left->ntype == N) { defaultlit(&n->right, T); @@ -2262,10 +2331,9 @@ checkassignto(Type *src, Node *dst) char *why; if(assignop(src, dst->type, &why) == 0) { - yyerror("cannot assign %T to %+N in multiple assignment%s", src, dst, why); + yyerror("cannot assign %T to %lN in multiple assignment%s", src, dst, why); return; } - exportassignok(dst->type, "multiple assignment"); } static void @@ -2312,10 +2380,7 @@ typecheckas2(Node *n) if(cl == 1 && cr == 2 && l->op == OINDEXMAP) { if(l->type == T) goto out; - n->op = OAS2MAPW; - n->rlist->n = assignconv(r, l->type, "assignment"); - r = n->rlist->next->n; - n->rlist->next->n = assignconv(r, types[TBOOL], "assignment"); + yyerror("assignment count mismatch: %d = %d (use delete)", cl, cr); goto out; } @@ -2397,7 +2462,7 @@ typecheckfunc(Node *n) if((t = n->nname->type) == T) return; n->type = t; - + t->nname = n->nname; rcvr = getthisx(t)->type; if(rcvr != nil && n->shortname != N && !isblank(n->shortname)) addmethod(n->shortname->sym, t, 1); @@ -2427,7 +2492,7 @@ stringtoarraylit(Node **np) while(p < ep) l = list(l, nod(OKEY, nodintconst(i++), nodintconst((uchar)*p++))); } else { - // utf-8 []int + // utf-8 []rune while(p < ep) { p += chartorune(&r, p); l = list(l, nod(OKEY, nodintconst(i++), nodintconst(r))); @@ -2467,6 +2532,7 @@ static void domethod(Node *n) { Node *nt; + Type *t; nt = n->type->nname; typecheck(&nt, Etype); @@ -2476,6 +2542,20 @@ domethod(Node *n) n->type->nod = N; return; } + + // If we have + // type I interface { + // M(_ int) + // } + // then even though I.M looks like it doesn't care about the + // value of its argument, a specific implementation of I may + // care. The _ would suppress the assignment to that argument + // while generating a call, so remove it. + for(t=getinargx(nt->type)->type; t; t=t->down) { + if(t->sym != nil && strcmp(t->sym->name, "_") == 0) + t->sym = nil; + } + *n->type = *nt->type; n->type->nod = N; checkwidth(n->type); @@ -2532,6 +2612,7 @@ copytype(Node *n, Type *t) t->vargen = n->vargen; t->siggen = 0; t->method = nil; + t->xmethod = nil; t->nod = N; t->printed = 0; t->deferwidth = 0; @@ -2689,7 +2770,7 @@ typecheckdef(Node *n) goto ret; } if(!isideal(e->type) && !eqtype(t, e->type)) { - yyerror("cannot use %+N as type %T in const initializer", e, t); + yyerror("cannot use %lN as type %T in const initializer", e, t); goto ret; } convlit(&e, t); diff --git a/src/cmd/gc/unsafe.c b/src/cmd/gc/unsafe.c index 6435492e0..95200ad41 100644 --- a/src/cmd/gc/unsafe.c +++ b/src/cmd/gc/unsafe.c @@ -10,6 +10,7 @@ * look for * unsafe.Sizeof * unsafe.Offsetof + * unsafe.Alignof * rewrite with a constant */ Node* @@ -22,7 +23,7 @@ unsafenmagic(Node *nn) Val val; Node *fn; NodeList *args; - + fn = nn->left; args = nn->list; @@ -80,10 +81,10 @@ no: return N; bad: - yyerror("invalid expression %#N", nn); + yyerror("invalid expression %N", nn); v = 0; goto ret; - + yes: if(args->next != nil) yyerror("extra arguments for %S", s); @@ -93,7 +94,23 @@ ret: val.u.xval = mal(sizeof(*n->val.u.xval)); mpmovecfix(val.u.xval, v); n = nod(OLITERAL, N, N); + n->orig = nn; n->val = val; n->type = types[TUINTPTR]; + nn->type = types[TUINTPTR]; return n; } + +int +isunsafebuiltin(Node *n) +{ + if(n == N || n->op != ONAME || n->sym == S || n->sym->pkg != unsafepkg) + return 0; + if(strcmp(n->sym->name, "Sizeof") == 0) + return 1; + if(strcmp(n->sym->name, "Offsetof") == 0) + return 1; + if(strcmp(n->sym->name, "Alignof") == 0) + return 1; + return 0; +} diff --git a/src/cmd/gc/unsafe.go b/src/cmd/gc/unsafe.go index db27d7425..c7b48a8b0 100644 --- a/src/cmd/gc/unsafe.go +++ b/src/cmd/gc/unsafe.go @@ -6,6 +6,8 @@ // to update builtin.c.boot. This is not done automatically // to avoid depending on having a working compiler binary. +// +build ignore + package PACKAGE type Pointer uintptr // not really; filled in by compiler diff --git a/src/cmd/gc/walk.c b/src/cmd/gc/walk.c index 8a84956a6..53040fe93 100644 --- a/src/cmd/gc/walk.c +++ b/src/cmd/gc/walk.c @@ -7,9 +7,8 @@ #include "go.h" static Node* walkprint(Node*, NodeList**, int); -static Node* conv(Node*, Type*); static Node* mapfn(char*, Type*); -static Node* makenewvar(Type*, NodeList**, Node**); +static Node* mapfndel(char*, Type*); static Node* ascompatee1(int, Node*, Node*, NodeList**); static NodeList* ascompatee(int, NodeList*, NodeList*, NodeList**); static NodeList* ascompatet(int, NodeList*, Type**, int, NodeList**); @@ -22,6 +21,7 @@ static NodeList* reorder3(NodeList*); static Node* addstr(Node*, NodeList**); static Node* appendslice(Node*, NodeList**); static Node* append(Node*, NodeList**); +static void walkcompare(Node**, NodeList**); // can this code branch reach the end // without an unconditional RETURN @@ -62,7 +62,6 @@ walk(Node *fn) { char s[50]; NodeList *l; - Node *n; int lno; curfn = fn; @@ -76,15 +75,33 @@ walk(Node *fn) yyerror("function ends without a return statement"); lno = lineno; + + // Final typecheck for any unused variables. + // It's hard to be on the heap when not-used, but best to be consistent about &~PHEAP here and below. + for(l=fn->dcl; l; l=l->next) + if(l->n->op == ONAME && (l->n->class&~PHEAP) == PAUTO) + typecheck(&l->n, Erv | Easgn); + + // Propagate the used flag for typeswitch variables up to the NONAME in it's definition. + for(l=fn->dcl; l; l=l->next) + if(l->n->op == ONAME && (l->n->class&~PHEAP) == PAUTO && l->n->defn && l->n->defn->op == OTYPESW && l->n->used) + l->n->defn->left->used++; + for(l=fn->dcl; l; l=l->next) { - n = l->n; - if(n->op != ONAME || n->class != PAUTO) + if(l->n->op != ONAME || (l->n->class&~PHEAP) != PAUTO || l->n->sym->name[0] == '&' || l->n->used) continue; - lineno = n->lineno; - typecheck(&n, Erv | Easgn); // only needed for unused variables - if(!n->used && n->sym->name[0] != '&' && !nsyntaxerrors) - yyerror("%S declared and not used", n->sym); - } + if(l->n->defn && l->n->defn->op == OTYPESW) { + if(l->n->defn->left->used) + continue; + lineno = l->n->defn->left->lineno; + yyerror("%S declared and not used", l->n->sym); + l->n->defn->left->used = 1; // suppress repeats + } else { + lineno = l->n->lineno; + yyerror("%S declared and not used", l->n->sym); + } + } + lineno = lno; if(nerrors != 0) return; @@ -121,11 +138,12 @@ static int paramoutheap(Node *fn) { NodeList *l; - + for(l=fn->dcl; l; l=l->next) { switch(l->n->class) { + case PPARAMOUT: case PPARAMOUT|PHEAP: - return 1; + return l->n->addrtaken; case PAUTO: case PAUTO|PHEAP: // stop early - parameters are over @@ -149,6 +167,8 @@ walkstmt(Node **np) setlineno(n); + walkstmtlist(n->ninit); + switch(n->op) { default: if(n->op == ONAME) @@ -164,7 +184,6 @@ walkstmt(Node **np) case OAS2DOTTYPE: case OAS2RECV: case OAS2FUNC: - case OAS2MAPW: case OAS2MAPR: case OCLOSE: case OCOPY: @@ -172,6 +191,7 @@ walkstmt(Node **np) case OCALLINTER: case OCALL: case OCALLFUNC: + case ODELETE: case OSEND: case ORECV: case OPRINT: @@ -179,14 +199,12 @@ walkstmt(Node **np) case OPANIC: case OEMPTY: case ORECOVER: - if(n->typecheck == 0) { - dump("missing typecheck:", n); - fatal("missing typecheck"); - } + if(n->typecheck == 0) + fatal("missing typecheck: %+N", n); init = n->ninit; n->ninit = nil; walkexpr(&n, &init); - n->ninit = concat(init, n->ninit); + addinit(&n, init); break; case OBREAK: @@ -225,20 +243,18 @@ walkstmt(Node **np) break; case OFOR: - walkstmtlist(n->ninit); if(n->ntest != N) { walkstmtlist(n->ntest->ninit); init = n->ntest->ninit; n->ntest->ninit = nil; walkexpr(&n->ntest, &init); - n->ntest->ninit = concat(init, n->ntest->ninit); + addinit(&n->ntest, init); } walkstmt(&n->nincr); walkstmtlist(n->nbody); break; case OIF: - walkstmtlist(n->ninit); walkexpr(&n->ntest, &n->ninit); walkstmtlist(n->nbody); walkstmtlist(n->nelse); @@ -281,10 +297,13 @@ walkstmt(Node **np) // OAS2FUNC in disguise f = n->list->n; if(f->op != OCALLFUNC && f->op != OCALLMETH && f->op != OCALLINTER) - fatal("expected return of call, have %#N", f); + fatal("expected return of call, have %N", f); n->list = concat(list1(f), ascompatet(n->op, rl, &f->type, 0, &n->ninit)); break; } + + // move function calls out, to make reorder3's job easier. + walkexprlistsafe(n->list, &n->ninit); ll = ascompatee(n->op, rl, n->list, &n->ninit); n->list = reorder3(ll); break; @@ -311,6 +330,9 @@ walkstmt(Node **np) break; } + if(n->op == ONAME) + fatal("walkstmt ended up with name: %+N", n); + *np = n; } @@ -363,6 +385,12 @@ walkexpr(Node **np, NodeList **init) fatal("walkexpr init == &n->ninit"); } + if(n->ninit != nil) { + walkstmtlist(n->ninit); + *init = concat(*init, n->ninit); + n->ninit = nil; + } + // annoying case - not typechecked if(n->op == OKEY) { walkexpr(&n->left, init); @@ -375,10 +403,8 @@ walkexpr(Node **np, NodeList **init) if(debug['w'] > 1) dump("walk-before", n); - if(n->typecheck != 1) { - dump("missed typecheck", n); - fatal("missed typecheck"); - } + if(n->typecheck != 1) + fatal("missed typecheck: %+N\n", n); switch(n->op) { default: @@ -409,7 +435,7 @@ walkexpr(Node **np, NodeList **init) case OLEN: case OCAP: walkexpr(&n->left, init); - + // replace len(*[10]int) with 10. // delayed until now to preserve side effects. t = n->left->type; @@ -421,7 +447,7 @@ walkexpr(Node **np, NodeList **init) n->typecheck = 1; } goto ret; - + case OLSH: case ORSH: case OAND: @@ -429,8 +455,6 @@ walkexpr(Node **np, NodeList **init) case OXOR: case OSUB: case OMUL: - case OEQ: - case ONE: case OLT: case OLE: case OGE: @@ -440,7 +464,14 @@ walkexpr(Node **np, NodeList **init) walkexpr(&n->left, init); walkexpr(&n->right, init); goto ret; - + + case OEQ: + case ONE: + walkexpr(&n->left, init); + walkexpr(&n->right, init); + walkcompare(&n, init); + goto ret; + case OANDAND: case OOROR: walkexpr(&n->left, init); @@ -449,7 +480,7 @@ walkexpr(Node **np, NodeList **init) // save elsewhere and store on the eventual n->right. ll = nil; walkexpr(&n->right, &ll); - n->right->ninit = concat(n->right->ninit, ll); + addinit(&n->right, ll); goto ret; case OPRINT: @@ -553,7 +584,7 @@ walkexpr(Node **np, NodeList **init) walkexprlistsafe(n->list, init); walkexpr(&r, init); l = n->list->n; - + // all the really hard stuff - explicit function calls and so on - // is gone, but map assignments remain. // if there are map assignments here, assign via @@ -606,15 +637,19 @@ walkexpr(Node **np, NodeList **init) n->op = OAS2FUNC; goto as2func; - case OAS2MAPW: - // map[] = a,b - mapassign2 - // a,b = m[i]; + case ODELETE: *init = concat(*init, n->ninit); n->ninit = nil; - walkexprlistsafe(n->list, init); l = n->list->n; - t = l->left->type; - n = mkcall1(mapfn("mapassign2", t), T, init, typename(t), l->left, l->right, n->rlist->n, n->rlist->next->n); + r = n->list->next->n; + if(n->right != N) { + // TODO: Remove once two-element map assigment is gone. + l = safeexpr(l, init); + r = safeexpr(r, init); + safeexpr(n->right, init); // cause side effects from n->right + } + t = l->type; + n = mkcall1(mapfndel("mapdelete", t), t->down, init, typename(t), l, r); goto ret; case OAS2DOTTYPE: @@ -648,7 +683,7 @@ walkexpr(Node **np, NodeList **init) if(n->op == ODOTTYPE2) *p++ = '2'; *p = '\0'; - + fn = syslook(buf, 1); ll = list1(typename(n->type)); ll = list(ll, n->left); @@ -679,7 +714,7 @@ walkexpr(Node **np, NodeList **init) else *p++ = 'I'; *p = '\0'; - + fn = syslook(buf, 1); ll = nil; if(!isinter(n->left->type)) @@ -840,6 +875,7 @@ walkexpr(Node **np, NodeList **init) // delayed until now because "abc"[2] is not // an ideal constant. nodconst(n, n->type, n->left->val.u.sval->s[v]); + n->typecheck = 1; } } goto ret; @@ -894,7 +930,7 @@ walkexpr(Node **np, NodeList **init) } if(v1 >= 0 && v2 >= 0 && v1 > v2) yyerror("inverted slice range"); - + if(n->op == OSLICEARR) goto slicearray; @@ -925,7 +961,7 @@ walkexpr(Node **np, NodeList **init) l, nodintconst(t->type->width)); } - n->etype = et; // preserve no-typecheck flag from OSLICE to the slice* call. + n->etype = et; // preserve no-typecheck flag from OSLICE to the slice* call. goto ret; slicearray: @@ -950,32 +986,17 @@ walkexpr(Node **np, NodeList **init) nodintconst(t->type->width)); goto ret; - case OADDR:; - Node *nvar, *nstar; - - // turn &Point(1, 2) or &[]int(1, 2) or &[...]int(1, 2) into allocation. - // initialize with - // nvar := new(*Point); - // *nvar = Point(1, 2); - // and replace expression with nvar - switch(n->left->op) { - case OARRAYLIT: - case OMAPLIT: - case OSTRUCTLIT: - nvar = makenewvar(n->type, init, &nstar); - anylit(0, n->left, nstar, init); - n = nvar; - goto ret; - } - + case OADDR: walkexpr(&n->left, init); goto ret; case ONEW: if(n->esc == EscNone && n->type->type->width < (1<<16)) { r = temp(n->type->type); - *init = list(*init, nod(OAS, r, N)); // zero temp - r = nod(OADDR, r, N); + r = nod(OAS, r, N); // zero temp + typecheck(&r, Etop); + *init = list(*init, r); + r = nod(OADDR, r->left, N); typecheck(&r, Erv); n = r; } else { @@ -1054,10 +1075,14 @@ walkexpr(Node **np, NodeList **init) l); } goto ret; - + case OAPPEND: - if(n->isddd) - n = appendslice(n, init); + if(n->isddd) { + if(istype(n->type->type, TUINT8) && istype(n->list->next->n->type, TSTRING)) + n = mkcall("appendstr", n->type, init, typename(n->type), n->list->n, n->list->next->n); + else + n = appendslice(n, init); + } else n = append(n, init); goto ret; @@ -1066,7 +1091,7 @@ walkexpr(Node **np, NodeList **init) if(n->right->type->etype == TSTRING) fn = syslook("slicestringcopy", 1); else - fn = syslook("slicecopy", 1); + fn = syslook("copy", 1); argtype(fn, n->left->type); argtype(fn, n->right->type); n = mkcall1(fn, n->type, init, @@ -1126,8 +1151,8 @@ walkexpr(Node **np, NodeList **init) goto ret; case OARRAYRUNESTR: - // sliceinttostring([]int) string; - n = mkcall("sliceinttostring", n->type, init, n->left); + // slicerunetostring([]rune) string; + n = mkcall("slicerunetostring", n->type, init, n->left); goto ret; case OSTRARRAYBYTE: @@ -1136,8 +1161,8 @@ walkexpr(Node **np, NodeList **init) goto ret; case OSTRARRAYRUNE: - // stringtosliceint(string) []int - n = mkcall("stringtosliceint", n->type, init, n->left); + // stringtoslicerune(string) []rune + n = mkcall("stringtoslicerune", n->type, init, n->left); goto ret; case OCMPIFACE: @@ -1161,9 +1186,10 @@ walkexpr(Node **np, NodeList **init) case OARRAYLIT: case OMAPLIT: case OSTRUCTLIT: - nvar = temp(n->type); - anylit(0, n, nvar, init); - n = nvar; + case OPTRLIT: + var = temp(n->type); + anylit(0, n, var, init); + n = var; goto ret; case OSEND: @@ -1186,22 +1212,6 @@ ret: } static Node* -makenewvar(Type *t, NodeList **init, Node **nstar) -{ - Node *nvar, *nas; - - nvar = temp(t); - nas = nod(OAS, nvar, callnew(t->type)); - typecheck(&nas, Etop); - walkexpr(&nas, init); - *init = list(*init, nas); - - *nstar = nod(OIND, nvar, N); - typecheck(nstar, Erv); - return nvar; -} - -static Node* ascompatee1(int op, Node *l, Node *r, NodeList **init) { USED(op); @@ -1232,7 +1242,7 @@ ascompatee(int op, NodeList *nl, NodeList *nr, NodeList **init) // cannot happen: caller checked that lists had same length if(ll || lr) - yyerror("error in shape across %O", op); + yyerror("error in shape across %+H %O %+H", nl, op, nr); return nn; } @@ -1304,10 +1314,11 @@ ascompatet(int op, NodeList *nl, Type **nr, int fp, NodeList **init) } if(ll != nil || r != T) - yyerror("assignment count mismatch: %d = %d", + yyerror("ascompatet: assignment count mismatch: %d = %d", count(nl), structcount(*nr)); + if(ucount) - fatal("reorder2: too many function calls evaluating parameters"); + fatal("ascompatet: too many function calls evaluating parameters"); return concat(nn, mm); } @@ -1319,7 +1330,7 @@ mkdotargslice(NodeList *lr0, NodeList *nn, Type *l, int fp, NodeList **init, int { Node *a, *n; Type *tslice; - + tslice = typ(TARRAY); tslice->type = l->type->type; tslice->bound = -1; @@ -1413,7 +1424,7 @@ ascompatte(int op, Node *call, int isddd, Type **nl, NodeList *lr, int fp, NodeL if(lr) r = lr->n; nn = nil; - + // f(g()) where g has multiple return values if(r != N && lr->next == nil && r->type->etype == TSTRUCT && r->type->funarg) { // optimization - can do block copy @@ -1423,7 +1434,7 @@ ascompatte(int op, Node *call, int isddd, Type **nl, NodeList *lr, int fp, NodeL nn = list1(convas(nod(OAS, a, r), init)); goto ret; } - + // conversions involved. // copy into temporaries. alist = nil; @@ -1540,6 +1551,9 @@ walkprint(Node *nn, NodeList **init, int defer) n = l->n; if(n->op == OLITERAL) { switch(n->val.ctype) { + case CTRUNE: + defaultlit(&n, runetype); + break; case CTINT: defaultlit(&n, types[TINT64]); break; @@ -1682,7 +1696,7 @@ callnew(Type *t) dowidth(t); fn = syslook("new", 1); argtype(fn, t); - return mkcall1(fn, ptrto(t), nil, nodintconst(t->width)); + return mkcall1(fn, ptrto(t), nil, typename(t)); } static Node* @@ -1714,10 +1728,10 @@ convas(Node *n, NodeList **init) n->left->left, n->left->right, n->right); goto out; } - + if(eqtype(lt, rt)) goto out; - + n->right = assignconv(n->right, lt, "assignment"); walkexpr(&n->right, init); @@ -1786,28 +1800,242 @@ reorder1(NodeList *all) return concat(g, r); } +static void reorder3save(Node**, NodeList*, NodeList*, NodeList**); +static int aliased(Node*, NodeList*, NodeList*); + /* * from ascompat[ee] * a,b = c,d * simultaneous assignment. there cannot * be later use of an earlier lvalue. + * + * function calls have been removed. + */ +static NodeList* +reorder3(NodeList *all) +{ + NodeList *list, *early; + Node *l; + + // If a needed expression may be affected by an + // earlier assignment, make an early copy of that + // expression and use the copy instead. + early = nil; + for(list=all; list; list=list->next) { + l = list->n->left; + + // Save subexpressions needed on left side. + // Drill through non-dereferences. + for(;;) { + if(l->op == ODOT || l->op == OPAREN) { + l = l->left; + continue; + } + if(l->op == OINDEX && isfixedarray(l->left->type)) { + reorder3save(&l->right, all, list, &early); + l = l->left; + continue; + } + break; + } + switch(l->op) { + default: + fatal("reorder3 unexpected lvalue %#O", l->op); + case ONAME: + break; + case OINDEX: + reorder3save(&l->left, all, list, &early); + reorder3save(&l->right, all, list, &early); + break; + case OIND: + case ODOTPTR: + reorder3save(&l->left, all, list, &early); + } + + // Save expression on right side. + reorder3save(&list->n->right, all, list, &early); + } + + return concat(early, all); +} + +static int vmatch2(Node*, Node*); +static int varexpr(Node*); + +/* + * if the evaluation of *np would be affected by the + * assignments in all up to but not including stop, + * copy into a temporary during *early and + * replace *np with that temp. + */ +static void +reorder3save(Node **np, NodeList *all, NodeList *stop, NodeList **early) +{ + Node *n, *q; + + n = *np; + if(!aliased(n, all, stop)) + return; + + q = temp(n->type); + q = nod(OAS, q, n); + typecheck(&q, Etop); + *early = list(*early, q); + *np = q->left; +} + +/* + * what's the outer value that a write to n affects? + * outer value means containing struct or array. + */ +static Node* +outervalue(Node *n) +{ + for(;;) { + if(n->op == ODOT || n->op == OPAREN) { + n = n->left; + continue; + } + if(n->op == OINDEX && isfixedarray(n->left->type)) { + n = n->left; + continue; + } + break; + } + return n; +} + +/* + * Is it possible that the computation of n might be + * affected by writes in as up to but not including stop? + */ +static int +aliased(Node *n, NodeList *all, NodeList *stop) +{ + int memwrite, varwrite; + Node *a; + NodeList *l; + + if(n == N) + return 0; + + // Look for obvious aliasing: a variable being assigned + // during the all list and appearing in n. + // Also record whether there are any writes to main memory. + // Also record whether there are any writes to variables + // whose addresses have been taken. + memwrite = 0; + varwrite = 0; + for(l=all; l!=stop; l=l->next) { + a = outervalue(l->n->left); + if(a->op != ONAME) { + memwrite = 1; + continue; + } + switch(n->class) { + default: + varwrite = 1; + continue; + case PAUTO: + case PPARAM: + case PPARAMOUT: + if(n->addrtaken) { + varwrite = 1; + continue; + } + if(vmatch2(a, n)) { + // Direct hit. + return 1; + } + } + } + + // The variables being written do not appear in n. + // However, n might refer to computed addresses + // that are being written. + + // If no computed addresses are affected by the writes, no aliasing. + if(!memwrite && !varwrite) + return 0; + + // If n does not refer to computed addresses + // (that is, if n only refers to variables whose addresses + // have not been taken), no aliasing. + if(varexpr(n)) + return 0; + + // Otherwise, both the writes and n refer to computed memory addresses. + // Assume that they might conflict. + return 1; +} + +/* + * does the evaluation of n only refer to variables + * whose addresses have not been taken? + * (and no other memory) */ +static int +varexpr(Node *n) +{ + if(n == N) + return 1; + + switch(n->op) { + case OLITERAL: + return 1; + case ONAME: + switch(n->class) { + case PAUTO: + case PPARAM: + case PPARAMOUT: + if(!n->addrtaken) + return 1; + } + return 0; + case OADD: + case OSUB: + case OOR: + case OXOR: + case OMUL: + case ODIV: + case OMOD: + case OLSH: + case ORSH: + case OAND: + case OANDNOT: + case OPLUS: + case OMINUS: + case OCOM: + case OPAREN: + case OANDAND: + case OOROR: + case ODOT: // but not ODOTPTR + case OCONV: + case OCONVNOP: + case OCONVIFACE: + case ODOTTYPE: + return varexpr(n->left) && varexpr(n->right); + } + + // Be conservative. + return 0; +} + +/* + * is the name l mentioned in r? + */ static int vmatch2(Node *l, Node *r) { NodeList *ll; - /* - * isolate all right sides - */ if(r == N) return 0; switch(r->op) { case ONAME: // match each right given left - if(l == r) - return 1; + return l == r; case OLITERAL: return 0; } @@ -1821,6 +2049,10 @@ vmatch2(Node *l, Node *r) return 0; } +/* + * is any name mentioned in l also mentioned in r? + * called by sinit.c + */ int vmatch1(Node *l, Node *r) { @@ -1859,33 +2091,6 @@ vmatch1(Node *l, Node *r) return 0; } -static NodeList* -reorder3(NodeList *all) -{ - Node *n1, *n2, *q; - int c1, c2; - NodeList *l1, *l2, *r; - - r = nil; - for(l1=all, c1=0; l1; l1=l1->next, c1++) { - n1 = l1->n; - for(l2=all, c2=0; l2; l2=l2->next, c2++) { - n2 = l2->n; - if(c2 > c1) { - if(vmatch1(n1->left, n2->right)) { - // delay assignment to n1->left - q = temp(n1->right->type); - q = nod(OAS, n1->left, q); - n1->left = q->right; - r = list(r, q); - break; - } - } - } - } - return concat(all, r); -} - /* * walk through argin parameters. * generate and return code to allocate @@ -1952,7 +2157,7 @@ heapmoves(void) { NodeList *nn; int32 lno; - + lno = lineno; lineno = curfn->lineno; nn = paramstoheap(getthis(curfn->type), 0); @@ -1972,7 +2177,7 @@ vmkcall(Node *fn, Type *t, NodeList **init, va_list va) NodeList *args; if(fn->type == T || fn->type->etype != TFUNC) - fatal("mkcall %#N %T", fn, fn->type); + fatal("mkcall %N %T", fn, fn->type); args = nil; n = fn->type->intuple; @@ -2014,7 +2219,7 @@ mkcall1(Node *fn, Type *t, NodeList **init, ...) return r; } -static Node* +Node* conv(Node *n, Type *t) { if(eqtype(n->type, t)) @@ -2055,12 +2260,26 @@ mapfn(char *name, Type *t) } static Node* +mapfndel(char *name, Type *t) +{ + Node *fn; + + if(t->etype != TMAP) + fatal("mapfn %T", t); + fn = syslook(name, 1); + argtype(fn, t->down); + argtype(fn, t->type); + argtype(fn, t->down); + return fn; +} + +static Node* addstr(Node *n, NodeList **init) { Node *r, *cat, *typstr; NodeList *in, *args; int i, count; - + count = 0; for(r=n; r->op == OADDSTR; r=r->left) count++; // r->right @@ -2089,7 +2308,7 @@ addstr(Node *n, NodeList **init) typecheck(&r, Erv); walkexpr(&r, init); r->type = n->type; - + return r; } @@ -2097,7 +2316,7 @@ static Node* appendslice(Node *n, NodeList **init) { Node *f; - + f = syslook("appendslice", 1); argtype(f, n->type); argtype(f, n->type->type); @@ -2111,7 +2330,7 @@ appendslice(Node *n, NodeList **init) // s := src // const argc = len(args) - 1 // if cap(s) - len(s) < argc { -// s = growslice(s, argc) +// s = growslice(s, argc) // } // n := len(s) // s = s[:n+argc] @@ -2140,13 +2359,13 @@ append(Node *n, NodeList **init) ns = temp(nsrc->type); l = list(l, nod(OAS, ns, nsrc)); // s = src - na = nodintconst(argc); // const argc - nx = nod(OIF, N, N); // if cap(s) - len(s) < argc + na = nodintconst(argc); // const argc + nx = nod(OIF, N, N); // if cap(s) - len(s) < argc nx->ntest = nod(OLT, nod(OSUB, nod(OCAP, ns, N), nod(OLEN, ns, N)), na); - fn = syslook("growslice", 1); // growslice(<type>, old []T, n int64) (ret []T) - argtype(fn, ns->type->type); // 1 old []any - argtype(fn, ns->type->type); // 2 ret []any + fn = syslook("growslice", 1); // growslice(<type>, old []T, n int64) (ret []T) + argtype(fn, ns->type->type); // 1 old []any + argtype(fn, ns->type->type); // 2 ret []any nx->nbody = list1(nod(OAS, ns, mkcall1(fn, ns->type, &nx->ninit, typename(ns->type), @@ -2155,16 +2374,16 @@ append(Node *n, NodeList **init) l = list(l, nx); nn = temp(types[TINT]); - l = list(l, nod(OAS, nn, nod(OLEN, ns, N))); // n = len(s) + l = list(l, nod(OAS, nn, nod(OLEN, ns, N))); // n = len(s) - nx = nod(OSLICE, ns, nod(OKEY, N, nod(OADD, nn, na))); // ...s[:n+argc] - nx->etype = 1; // disable bounds check - l = list(l, nod(OAS, ns, nx)); // s = s[:n+argc] + nx = nod(OSLICE, ns, nod(OKEY, N, nod(OADD, nn, na))); // ...s[:n+argc] + nx->etype = 1; // disable bounds check + l = list(l, nod(OAS, ns, nx)); // s = s[:n+argc] - for (a = n->list->next; a != nil; a = a->next) { - nx = nod(OINDEX, ns, nn); // s[n] ... - nx->etype = 1; // disable bounds check - l = list(l, nod(OAS, nx, a->n)); // s[n] = arg + for (a = n->list->next; a != nil; a = a->next) { + nx = nod(OINDEX, ns, nn); // s[n] ... + nx->etype = 1; // disable bounds check + l = list(l, nod(OAS, nx, a->n)); // s[n] = arg if (a->next != nil) l = list(l, nod(OAS, nn, nod(OADD, nn, nodintconst(1)))); // n = n + 1 } @@ -2174,3 +2393,186 @@ append(Node *n, NodeList **init) *init = concat(*init, l); return ns; } + +static Node* +eqfor(Type *t) +{ + int a; + Node *n; + Node *ntype; + Sym *sym; + + // Should only arrive here with large memory or + // a struct/array containing a non-memory field/element. + // Small memory is handled inline, and single non-memory + // is handled during type check (OCMPSTR etc). + a = algtype1(t, nil); + if(a != AMEM && a != -1) + fatal("eqfor %T", t); + + if(a == AMEM) { + n = syslook("memequal", 1); + argtype(n, t); + argtype(n, t); + return n; + } + + sym = typesymprefix(".eq", t); + n = newname(sym); + n->class = PFUNC; + ntype = nod(OTFUNC, N, N); + ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(types[TBOOL])))); + ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(types[TUINTPTR]))); + ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(t)))); + ntype->list = list(ntype->list, nod(ODCLFIELD, N, typenod(ptrto(t)))); + typecheck(&ntype, Etype); + n->type = ntype->type; + return n; +} + +static int +countfield(Type *t) +{ + Type *t1; + int n; + + n = 0; + for(t1=t->type; t1!=T; t1=t1->down) + n++; + return n; +} + +static void +walkcompare(Node **np, NodeList **init) +{ + Node *n, *l, *r, *fn, *call, *a, *li, *ri, *expr; + int andor, i; + Type *t, *t1; + static Node *tempbool; + + n = *np; + + // Must be comparison of array or struct. + // Otherwise back end handles it. + t = n->left->type; + switch(t->etype) { + default: + return; + case TARRAY: + if(isslice(t)) + return; + break; + case TSTRUCT: + break; + } + + if(!islvalue(n->left) || !islvalue(n->right)) + goto hard; + + l = temp(ptrto(t)); + a = nod(OAS, l, nod(OADDR, n->left, N)); + a->right->etype = 1; // addr does not escape + typecheck(&a, Etop); + *init = list(*init, a); + + r = temp(ptrto(t)); + a = nod(OAS, r, nod(OADDR, n->right, N)); + a->right->etype = 1; // addr does not escape + typecheck(&a, Etop); + *init = list(*init, a); + + expr = N; + andor = OANDAND; + if(n->op == ONE) + andor = OOROR; + + if(t->etype == TARRAY && + t->bound <= 4 && + issimple[t->type->etype]) { + // Four or fewer elements of a basic type. + // Unroll comparisons. + for(i=0; i<t->bound; i++) { + li = nod(OINDEX, l, nodintconst(i)); + ri = nod(OINDEX, r, nodintconst(i)); + a = nod(n->op, li, ri); + if(expr == N) + expr = a; + else + expr = nod(andor, expr, a); + } + if(expr == N) + expr = nodbool(n->op == OEQ); + typecheck(&expr, Erv); + walkexpr(&expr, init); + *np = expr; + return; + } + + if(t->etype == TSTRUCT && countfield(t) <= 4) { + // Struct of four or fewer fields. + // Inline comparisons. + for(t1=t->type; t1; t1=t1->down) { + li = nod(OXDOT, l, newname(t1->sym)); + ri = nod(OXDOT, r, newname(t1->sym)); + a = nod(n->op, li, ri); + if(expr == N) + expr = a; + else + expr = nod(andor, expr, a); + } + if(expr == N) + expr = nodbool(n->op == OEQ); + typecheck(&expr, Erv); + walkexpr(&expr, init); + *np = expr; + return; + } + + // Chose not to inline, but still have addresses. + // Call equality function directly. + // The equality function requires a bool pointer for + // storing its address, because it has to be callable + // from C, and C can't access an ordinary Go return value. + // To avoid creating many temporaries, cache one per function. + if(tempbool == N || tempbool->curfn != curfn) + tempbool = temp(types[TBOOL]); + + call = nod(OCALL, eqfor(t), N); + a = nod(OADDR, tempbool, N); + a->etype = 1; // does not escape + call->list = list(call->list, a); + call->list = list(call->list, nodintconst(t->width)); + call->list = list(call->list, l); + call->list = list(call->list, r); + typecheck(&call, Etop); + walkstmt(&call); + *init = list(*init, call); + + if(n->op == OEQ) + r = tempbool; + else + r = nod(ONOT, tempbool, N); + typecheck(&r, Erv); + walkexpr(&r, init); + *np = r; + return; + +hard: + // Cannot take address of one or both of the operands. + // Instead, pass directly to runtime helper function. + // Easier on the stack than passing the address + // of temporary variables, because we are better at reusing + // the argument space than temporary variable space. + fn = syslook("equal", 1); + l = n->left; + r = n->right; + argtype(fn, n->left->type); + argtype(fn, n->left->type); + r = mkcall1(fn, n->type, init, typename(n->left->type), l, r); + if(n->op == ONE) { + r = nod(ONOT, r, N); + typecheck(&r, Erv); + } + *np = r; + return; +} diff --git a/src/cmd/hgpatch/Makefile b/src/cmd/go/Makefile index 1ef98d7f9..295a14498 100644 --- a/src/cmd/hgpatch/Makefile +++ b/src/cmd/go/Makefile @@ -4,8 +4,22 @@ include ../../Make.inc -TARG=hgpatch +TARG=go GOFILES=\ + build.go\ + fix.go\ + get.go\ + fmt.go\ + help.go\ + http.go\ + list.go\ main.go\ + pkg.go\ + run.go\ + test.go\ + testflag.go\ + version.go\ + vet.go\ + vcs.go\ include ../../Make.cmd diff --git a/src/cmd/go/bootstrap.go b/src/cmd/go/bootstrap.go new file mode 100644 index 000000000..bc9a3dbbc --- /dev/null +++ b/src/cmd/go/bootstrap.go @@ -0,0 +1,17 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build cmd_go_bootstrap + +// This code is compiled only into the bootstrap 'go' binary. +// These stubs avoid importing packages with large dependency +// trees, like the use of "net/http" in vcs.go. + +package main + +import "errors" + +func httpGET(url string) ([]byte, error) { + return nil, errors.New("no http in bootstrap go command") +} diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go new file mode 100644 index 000000000..cbe36f52e --- /dev/null +++ b/src/cmd/go/build.go @@ -0,0 +1,1142 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "container/heap" + "errors" + "fmt" + "go/build" + "io" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "regexp" + "runtime" + "strings" + "sync" +) + +var cmdBuild = &Command{ + UsageLine: "build [-a] [-n] [-o output] [-p n] [-v] [-x] [importpath... | gofiles...]", + Short: "compile packages and dependencies", + Long: ` +Build compiles the packages named by the import paths, +along with their dependencies, but it does not install the results. + +If the arguments are a list of .go files, build treats them as a list +of source files specifying a single package. + +When the command line specifies a single main package, +build writes the resulting executable to output (default a.out). +Otherwise build compiles the packages but discards the results, +serving only as a check that the packages can be built. + +The -a flag forces rebuilding of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -v flag prints the names of packages as they are compiled. +The -x flag prints the commands. + +The -o flag specifies the output file name. +It is an error to use -o when the command line specifies multiple packages. + +The -p flag specifies the number of builds that can be run in parallel. +The default is the number of CPUs available. + +For more about import paths, see 'go help importpath'. + +See also: go install, go get, go clean. + `, +} + +func init() { + // break init cycle + cmdBuild.Run = runBuild + cmdInstall.Run = runInstall + + addBuildFlags(cmdBuild) + addBuildFlags(cmdInstall) +} + +// Flags set by multiple commands. +var buildA bool // -a flag +var buildN bool // -n flag +var buildP = runtime.NumCPU() // -p flag +var buildV bool // -v flag +var buildX bool // -x flag +var buildO = cmdBuild.Flag.String("o", "", "output file") + +var buildContext = build.DefaultContext + +// addBuildFlags adds the flags common to the build and install commands. +func addBuildFlags(cmd *Command) { + cmd.Flag.BoolVar(&buildA, "a", false, "") + cmd.Flag.BoolVar(&buildN, "n", false, "") + cmd.Flag.IntVar(&buildP, "p", buildP, "") + cmd.Flag.BoolVar(&buildV, "v", false, "") + cmd.Flag.BoolVar(&buildX, "x", false, "") + + // TODO(rsc): This -t flag is used by buildscript.sh but + // not documented. Should be documented but the + // usage lines are getting too long. Probably need to say + // that these flags are applicable to every command and + // document them in one help message instead of on every + // command's help message. + cmd.Flag.Var((*stringsFlag)(&buildContext.BuildTags), "t", "") +} + +type stringsFlag []string + +func (v *stringsFlag) Set(s string) error { + *v = append(*v, s) + return nil +} + +func (v *stringsFlag) String() string { + return "<stringsFlag>" +} + +func runBuild(cmd *Command, args []string) { + var b builder + b.init() + + var pkgs []*Package + if len(args) > 0 && strings.HasSuffix(args[0], ".go") { + pkg := goFilesPackage(args, "") + pkgs = append(pkgs, pkg) + } else { + pkgs = packagesForBuild(args) + } + + if len(pkgs) == 1 && pkgs[0].Name == "main" && *buildO == "" { + *buildO = "a.out" + } + + if *buildO != "" { + if len(pkgs) > 1 { + fatalf("go build: cannot use -o with multiple packages") + } + p := pkgs[0] + p.target = "" // must build - not up to date + a := b.action(modeInstall, modeBuild, p) + a.target = *buildO + b.do(a) + return + } + + a := &action{} + for _, p := range packages(args) { + a.deps = append(a.deps, b.action(modeBuild, modeBuild, p)) + } + b.do(a) +} + +var cmdInstall = &Command{ + UsageLine: "install [-a] [-n] [-p n] [-v] [-x] [importpath...]", + Short: "compile and install packages and dependencies", + Long: ` +Install compiles and installs the packages named by the import paths, +along with their dependencies. + +The -a flag forces reinstallation of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -v flag prints the names of packages as they are compiled. +The -x flag prints the commands. + +The -p flag specifies the number of builds that can be run in parallel. +The default is the number of CPUs available. + +For more about import paths, see 'go help importpath'. + +See also: go build, go get, go clean. + `, +} + +func runInstall(cmd *Command, args []string) { + pkgs := packagesForBuild(args) + + var b builder + b.init() + a := &action{} + for _, p := range pkgs { + a.deps = append(a.deps, b.action(modeInstall, modeInstall, p)) + } + b.do(a) +} + +// A builder holds global state about a build. +// It does not hold per-package state, because eventually we will +// build packages in parallel, and the builder will be shared. +type builder struct { + work string // the temporary work directory (ends in filepath.Separator) + arch string // e.g., "6" + goroot string // the $GOROOT + goarch string // the $GOARCH + goos string // the $GOOS + gobin string // the $GOBIN + exe string // the executable suffix - "" or ".exe" + gcflags []string // additional flags for Go compiler + actionCache map[cacheKey]*action // a cache of already-constructed actions + mkdirCache map[string]bool // a cache of created directories + + output sync.Mutex + scriptDir string // current directory in printed script + + exec sync.Mutex + readySema chan bool + ready actionQueue +} + +// An action represents a single action in the action graph. +type action struct { + p *Package // the package this action works on + deps []*action // actions that must happen before this one + triggers []*action // inverse of deps + cgo *action // action for cgo binary if needed + args []string // additional args for runProgram + testOutput *bytes.Buffer // test output buffer + + f func(*builder, *action) error // the action itself (nil = no-op) + ignoreFail bool // whether to run f even if dependencies fail + + // Generated files, directories. + link bool // target is executable, not just package + pkgdir string // the -I or -L argument to use when importing this package + objdir string // directory for intermediate objects + objpkg string // the intermediate package .a file created during the action + target string // goal of the action: the created package or executable + + // Execution state. + pending int // number of deps yet to complete + priority int // relative execution priority + failed bool // whether the action failed +} + +// cacheKey is the key for the action cache. +type cacheKey struct { + mode buildMode + p *Package +} + +// buildMode specifies the build mode: +// are we just building things or also installing the results? +type buildMode int + +const ( + modeBuild buildMode = iota + modeInstall +) + +func (b *builder) init() { + var err error + b.actionCache = make(map[cacheKey]*action) + b.mkdirCache = make(map[string]bool) + b.goarch = buildContext.GOARCH + b.goos = buildContext.GOOS + b.goroot = build.Path[0].Path + b.gobin = build.Path[0].BinDir() + if b.goos == "windows" { + b.exe = ".exe" + } + b.gcflags = strings.Fields(os.Getenv("GCFLAGS")) + + b.arch, err = build.ArchChar(b.goarch) + if err != nil { + fatalf("%s", err) + } + + if buildN { + b.work = "$WORK" + } else { + b.work, err = ioutil.TempDir("", "go-build") + if err != nil { + fatalf("%s", err) + } + if buildX { + fmt.Printf("WORK=%s\n", b.work) + } + atexit(func() { os.RemoveAll(b.work) }) + } +} + +// goFilesPackage creates a package for building a collection of Go files +// (typically named on the command line). If target is given, the package +// target is target. Otherwise, the target is named p.a for +// package p or named after the first Go file for package main. +func goFilesPackage(gofiles []string, target string) *Package { + // TODO: Remove this restriction. + for _, f := range gofiles { + if !strings.HasSuffix(f, ".go") || strings.Contains(f, "/") || strings.Contains(f, string(filepath.Separator)) { + fatalf("named files must be in current directory and .go files") + } + } + + // Synthesize fake "directory" that only shows those two files, + // to make it look like this is a standard package or + // command directory. + var dir []os.FileInfo + for _, file := range gofiles { + fi, err := os.Stat(file) + if err != nil { + fatalf("%s", err) + } + if fi.IsDir() { + fatalf("%s is a directory, should be a Go file", file) + } + dir = append(dir, fi) + } + ctxt := buildContext + ctxt.ReadDir = func(string) ([]os.FileInfo, error) { return dir, nil } + pwd, _ := os.Getwd() + var stk importStack + pkg := scanPackage(&ctxt, &build.Tree{Path: "."}, "<command line>", "<command line>", pwd+"/.", &stk) + if pkg.Error != nil { + fatalf("%s", pkg.Error) + } + if target != "" { + pkg.target = target + } else if pkg.Name == "main" { + pkg.target = gofiles[0][:len(gofiles[0])-len(".go")] + } else { + pkg.target = pkg.Name + ".a" + } + pkg.ImportPath = "_/" + pkg.target + return pkg +} + +// action returns the action for applying the given operation (mode) to the package. +// depMode is the action to use when building dependencies. +func (b *builder) action(mode buildMode, depMode buildMode, p *Package) *action { + key := cacheKey{mode, p} + a := b.actionCache[key] + if a != nil { + return a + } + + a = &action{p: p, pkgdir: p.t.PkgDir()} + if p.pkgdir != "" { // overrides p.t + a.pkgdir = p.pkgdir + } + + b.actionCache[key] = a + + for _, p1 := range p.imports { + a.deps = append(a.deps, b.action(depMode, depMode, p1)) + } + + if len(p.CgoFiles) > 0 { + var stk importStack + p1 := loadPackage("cmd/cgo", &stk) + if p1.Error != nil { + fatalf("load cmd/cgo: %v", p1.Error) + } + a.cgo = b.action(depMode, depMode, p1) + a.deps = append(a.deps, a.cgo) + } + + if p.Standard { + switch p.ImportPath { + case "builtin", "unsafe": + // Fake packages - nothing to build. + return a + } + } + + if !p.Stale && !buildA && p.target != "" { + // p.Stale==false implies that p.target is up-to-date. + // Record target name for use by actions depending on this one. + a.target = p.target + return a + } + + a.objdir = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+"/_obj")) + string(filepath.Separator) + a.objpkg = filepath.Join(b.work, filepath.FromSlash(a.p.ImportPath+".a")) + a.link = p.Name == "main" + + switch mode { + case modeInstall: + a.f = (*builder).install + a.deps = []*action{b.action(modeBuild, depMode, p)} + a.target = a.p.target + case modeBuild: + a.f = (*builder).build + a.target = a.objpkg + if a.link { + // An executable file. + // Have to use something other than .a for the suffix. + // It is easier on Windows if we use .exe, so use .exe everywhere. + // (This is the name of a temporary file.) + a.target = a.objdir + "a.out" + b.exe + } + } + + return a +} + +// actionList returns the list of actions in the dag rooted at root +// as visited in a depth-first post-order traversal. +func actionList(root *action) []*action { + seen := map[*action]bool{} + all := []*action{} + var walk func(*action) + walk = func(a *action) { + if seen[a] { + return + } + seen[a] = true + for _, a1 := range a.deps { + walk(a1) + } + all = append(all, a) + } + walk(root) + return all +} + +// do runs the action graph rooted at root. +func (b *builder) do(root *action) { + // Build list of all actions, assigning depth-first post-order priority. + // The original implementation here was a true queue + // (using a channel) but it had the effect of getting + // distracted by low-level leaf actions to the detriment + // of completing higher-level actions. The order of + // work does not matter much to overall execution time, + // but when running "go test std" it is nice to see each test + // results as soon as possible. The priorities assigned + // ensure that, all else being equal, the execution prefers + // to do what it would have done first in a simple depth-first + // dependency order traversal. + all := actionList(root) + for i, a := range all { + a.priority = i + } + + b.readySema = make(chan bool, len(all)) + done := make(chan bool) + + // Initialize per-action execution state. + for _, a := range all { + for _, a1 := range a.deps { + a1.triggers = append(a1.triggers, a) + } + a.pending = len(a.deps) + if a.pending == 0 { + b.ready.push(a) + b.readySema <- true + } + } + + // Handle runs a single action and takes care of triggering + // any actions that are runnable as a result. + handle := func(a *action) { + var err error + if a.f != nil && (!a.failed || a.ignoreFail) { + err = a.f(b, a) + } + + // The actions run in parallel but all the updates to the + // shared work state are serialized through b.exec. + b.exec.Lock() + defer b.exec.Unlock() + + if err != nil { + if err == errPrintedOutput { + exitStatus = 2 + } else { + errorf("%s", err) + } + a.failed = true + } + + for _, a0 := range a.triggers { + if a.failed { + a0.failed = true + } + if a0.pending--; a0.pending == 0 { + b.ready.push(a0) + b.readySema <- true + } + } + + if a == root { + close(b.readySema) + done <- true + } + } + + // Kick off goroutines according to parallelism. + // If we are using the -n flag (just printing commands) + // drop the parallelism to 1, both to make the output + // deterministic and because there is no real work anyway. + par := buildP + if buildN { + par = 1 + } + for i := 0; i < par; i++ { + go func() { + for _ = range b.readySema { + // Receiving a value from b.sema entitles + // us to take from the ready queue. + b.exec.Lock() + a := b.ready.pop() + b.exec.Unlock() + handle(a) + } + }() + } + + <-done +} + +// build is the action for building a single package or command. +func (b *builder) build(a *action) error { + if buildN { + // In -n mode, print a banner between packages. + // The banner is five lines so that when changes to + // different sections of the bootstrap script have to + // be merged, the banners give patch something + // to use to find its context. + fmt.Printf("\n#\n# %s\n#\n\n", a.p.ImportPath) + } + + if buildV { + fmt.Fprintf(os.Stderr, "%s\n", a.p.ImportPath) + } + + // Make build directory. + obj := a.objdir + if err := b.mkdir(obj); err != nil { + return err + } + + var gofiles, cfiles, sfiles, objects, cgoObjects []string + gofiles = append(gofiles, a.p.GoFiles...) + cfiles = append(cfiles, a.p.CFiles...) + sfiles = append(sfiles, a.p.SFiles...) + + // Run cgo. + if len(a.p.CgoFiles) > 0 { + // In a package using cgo, cgo compiles the C and assembly files with gcc. + // There is one exception: runtime/cgo's job is to bridge the + // cgo and non-cgo worlds, so it necessarily has files in both. + // In that case gcc only gets the gcc_* files. + var gccfiles []string + if a.p.Standard && a.p.ImportPath == "runtime/cgo" { + filter := func(files, nongcc, gcc []string) ([]string, []string) { + for _, f := range files { + if strings.HasPrefix(f, "gcc_") { + gcc = append(gcc, f) + } else { + nongcc = append(nongcc, f) + } + } + return nongcc, gcc + } + cfiles, gccfiles = filter(cfiles, cfiles[:0], gccfiles) + sfiles, gccfiles = filter(sfiles, sfiles[:0], gccfiles) + } else { + gccfiles = append(cfiles, sfiles...) + cfiles = nil + sfiles = nil + } + + outGo, outObj, err := b.cgo(a.p, a.cgo.target, obj, gccfiles) + if err != nil { + return err + } + cgoObjects = append(cgoObjects, outObj...) + gofiles = append(gofiles, outGo...) + } + + // Prepare Go import path list. + inc := b.includeArgs("-I", a.deps) + + // Compile Go. + if len(gofiles) > 0 { + out := "_go_." + b.arch + gcargs := []string{"-p", a.p.ImportPath} + if a.p.Standard && a.p.ImportPath == "runtime" { + // runtime compiles with a special 6g flag to emit + // additional reflect type data. + gcargs = append(gcargs, "-+") + } + if err := b.gc(a.p, obj+out, gcargs, inc, gofiles); err != nil { + return err + } + objects = append(objects, out) + } + + // Copy .h files named for goos or goarch or goos_goarch + // to names using GOOS and GOARCH. + // For example, defs_linux_amd64.h becomes defs_GOOS_GOARCH.h. + _goos_goarch := "_" + b.goos + "_" + b.goarch + ".h" + _goos := "_" + b.goos + ".h" + _goarch := "_" + b.goarch + ".h" + for _, file := range a.p.HFiles { + switch { + case strings.HasSuffix(file, _goos_goarch): + targ := file[:len(file)-len(_goos_goarch)] + "_GOOS_GOARCH.h" + if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { + return err + } + case strings.HasSuffix(file, _goarch): + targ := file[:len(file)-len(_goarch)] + "_GOARCH.h" + if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { + return err + } + case strings.HasSuffix(file, _goos): + targ := file[:len(file)-len(_goos)] + "_GOOS.h" + if err := b.copyFile(obj+targ, filepath.Join(a.p.Dir, file), 0666); err != nil { + return err + } + } + } + + for _, file := range cfiles { + out := file[:len(file)-len(".c")] + "." + b.arch + if err := b.cc(a.p, obj, obj+out, file); err != nil { + return err + } + objects = append(objects, out) + } + + // Assemble .s files. + for _, file := range sfiles { + out := file[:len(file)-len(".s")] + "." + b.arch + if err := b.asm(a.p, obj, obj+out, file); err != nil { + return err + } + objects = append(objects, out) + } + + // NOTE(rsc): On Windows, it is critically important that the + // gcc-compiled objects (cgoObjects) be listed after the ordinary + // objects in the archive. I do not know why this is. + // http://golang.org/issue/2601 + objects = append(objects, cgoObjects...) + + // Pack into archive in obj directory + if err := b.gopack(a.p, obj, a.objpkg, objects); err != nil { + return err + } + + // Link if needed. + if a.link { + // The compiler only cares about direct imports, but the + // linker needs the whole dependency tree. + all := actionList(a) + all = all[:len(all)-1] // drop a + inc := b.includeArgs("-L", all) + if err := b.ld(a.p, a.target, inc, a.objpkg); err != nil { + return err + } + } + + return nil +} + +// install is the action for installing a single package or executable. +func (b *builder) install(a *action) error { + a1 := a.deps[0] + perm := os.FileMode(0666) + if a1.link { + perm = 0777 + } + + // make target directory + dir, _ := filepath.Split(a.target) + if dir != "" { + if err := b.mkdir(dir); err != nil { + return err + } + } + + return b.copyFile(a.target, a1.target, perm) +} + +// includeArgs returns the -I or -L directory list for access +// to the results of the list of actions. +func (b *builder) includeArgs(flag string, all []*action) []string { + inc := []string{} + incMap := map[string]bool{ + b.work: true, // handled later + build.Path[0].PkgDir(): true, // goroot + "": true, // ignore empty strings + } + + // Look in the temporary space for results of test-specific actions. + // This is the $WORK/my/package/_test directory for the + // package being built, so there are few of these. + for _, a1 := range all { + if dir := a1.pkgdir; dir != a1.p.t.PkgDir() && !incMap[dir] { + incMap[dir] = true + inc = append(inc, flag, dir) + } + } + + // Also look in $WORK for any non-test packages that have + // been built but not installed. + inc = append(inc, flag, b.work) + + // Finally, look in the installed package directories for each action. + for _, a1 := range all { + if dir := a1.pkgdir; dir == a1.p.t.PkgDir() && !incMap[dir] { + incMap[dir] = true + inc = append(inc, flag, dir) + } + } + + return inc +} + +// removeByRenaming removes file name by moving it to a tmp +// directory and deleting the target if possible. +func removeByRenaming(name string) error { + f, err := ioutil.TempFile("", "") + if err != nil { + return err + } + tmpname := f.Name() + f.Close() + err = os.Remove(tmpname) + if err != nil { + return err + } + err = os.Rename(name, tmpname) + if err != nil { + // assume name file does not exists, + // otherwise later code will fail. + return nil + } + err = os.Remove(tmpname) + if err != nil { + // TODO(brainman): file is locked and can't be deleted. + // We need to come up with a better way of doing it. + } + return nil +} + +// copyFile is like 'cp src dst'. +func (b *builder) copyFile(dst, src string, perm os.FileMode) error { + if buildN || buildX { + b.showcmd("", "cp %s %s", src, dst) + if buildN { + return nil + } + } + + sf, err := os.Open(src) + if err != nil { + return err + } + defer sf.Close() + os.Remove(dst) + df, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + if runtime.GOOS != "windows" { + return err + } + // Windows does not allow to replace binary file + // while it is executing. We will cheat. + err = removeByRenaming(dst) + if err != nil { + return err + } + df, err = os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) + if err != nil { + return err + } + } + _, err = io.Copy(df, sf) + df.Close() + if err != nil { + os.Remove(dst) + return err + } + return nil +} + +// fmtcmd formats a command in the manner of fmt.Sprintf but also: +// +// If dir is non-empty and the script is not in dir right now, +// fmtcmd inserts "cd dir\n" before the command. +// +// fmtcmd replaces the value of b.work with $WORK. +// fmtcmd replaces the value of b.goroot with $GOROOT. +// fmtcmd replaces the value of b.gobin with $GOBIN. +// +// fmtcmd replaces the name of the current directory with dot (.) +// but only when it is at the beginning of a space-separated token. +// +func (b *builder) fmtcmd(dir string, format string, args ...interface{}) string { + cmd := fmt.Sprintf(format, args...) + if dir != "" { + cmd = strings.Replace(" "+cmd, " "+dir, " .", -1)[1:] + if b.scriptDir != dir { + b.scriptDir = dir + cmd = "cd " + dir + "\n" + cmd + } + } + cmd = strings.Replace(cmd, b.work, "$WORK", -1) + cmd = strings.Replace(cmd, b.gobin, "$GOBIN", -1) + cmd = strings.Replace(cmd, b.goroot, "$GOROOT", -1) + return cmd +} + +// showcmd prints the given command to standard output +// for the implementation of -n or -x. +func (b *builder) showcmd(dir string, format string, args ...interface{}) { + b.output.Lock() + defer b.output.Unlock() + fmt.Println(b.fmtcmd(dir, format, args...)) +} + +// showOutput prints "# desc" followed by the given output. +// The output is expected to contain references to 'dir', usually +// the source directory for the package that has failed to build. +// showOutput rewrites mentions of dir with a relative path to dir +// when the relative path is shorter. This is usually more pleasant. +// For example, if fmt doesn't compile and we are in src/pkg/html, +// the output is +// +// $ go build +// # fmt +// ../fmt/print.go:1090: undefined: asdf +// $ +// +// instead of +// +// $ go build +// # fmt +// /usr/gopher/go/src/pkg/fmt/print.go:1090: undefined: asdf +// $ +// +// showOutput also replaces references to the work directory with $WORK. +// +func (b *builder) showOutput(dir, desc, out string) { + prefix := "# " + desc + suffix := "\n" + out + pwd, _ := os.Getwd() + if reldir, err := filepath.Rel(pwd, dir); err == nil && len(reldir) < len(dir) { + suffix = strings.Replace(suffix, " "+dir, " "+reldir, -1) + suffix = strings.Replace(suffix, "\n"+dir, "\n"+reldir, -1) + } + suffix = strings.Replace(suffix, " "+b.work, " $WORK", -1) + + b.output.Lock() + defer b.output.Unlock() + fmt.Print(prefix, suffix) +} + +// relPaths returns a copy of paths with absolute paths +// made relative to the current directory if they would be shorter. +func relPaths(paths []string) []string { + var out []string + pwd, _ := os.Getwd() + for _, p := range paths { + rel, err := filepath.Rel(pwd, p) + if err == nil && len(rel) < len(p) { + p = rel + } + out = append(out, p) + } + return out +} + +// errPrintedOutput is a special error indicating that a command failed +// but that it generated output as well, and that output has already +// been printed, so there's no point showing 'exit status 1' or whatever +// the wait status was. The main executor, builder.do, knows not to +// print this error. +var errPrintedOutput = errors.New("already printed output - no need to show error") + +// run runs the command given by cmdline in the directory dir. +// If the commnd fails, run prints information about the failure +// and returns a non-nil error. +func (b *builder) run(dir string, desc string, cmdargs ...interface{}) error { + out, err := b.runOut(dir, desc, cmdargs...) + if len(out) > 0 { + if out[len(out)-1] != '\n' { + out = append(out, '\n') + } + if desc == "" { + desc = b.fmtcmd(dir, "%s", strings.Join(stringList(cmdargs...), " ")) + } + b.showOutput(dir, desc, string(out)) + if err != nil { + err = errPrintedOutput + } + } + return err +} + +// runOut runs the command given by cmdline in the directory dir. +// It returns the command output and any errors that occurred. +func (b *builder) runOut(dir string, desc string, cmdargs ...interface{}) ([]byte, error) { + cmdline := stringList(cmdargs...) + if buildN || buildX { + b.showcmd(dir, "%s", strings.Join(cmdline, " ")) + if buildN { + return nil, nil + } + } + + var buf bytes.Buffer + cmd := exec.Command(cmdline[0], cmdline[1:]...) + cmd.Stdout = &buf + cmd.Stderr = &buf + cmd.Dir = dir + // TODO: cmd.Env + err := cmd.Run() + return buf.Bytes(), err +} + +// mkdir makes the named directory. +func (b *builder) mkdir(dir string) error { + b.exec.Lock() + defer b.exec.Unlock() + // We can be a little aggressive about being + // sure directories exist. Skip repeated calls. + if b.mkdirCache[dir] { + return nil + } + b.mkdirCache[dir] = true + + if buildN || buildX { + b.showcmd("", "mkdir -p %s", dir) + if buildN { + return nil + } + } + + if err := os.MkdirAll(dir, 0777); err != nil { + return err + } + return nil +} + +// mkAbs returns an absolute path corresponding to +// evaluating f in the directory dir. +// We always pass absolute paths of source files so that +// the error messages will include the full path to a file +// in need of attention. +func mkAbs(dir, f string) string { + // Leave absolute paths alone. + // Also, during -n mode we use the pseudo-directory $WORK + // instead of creating an actual work directory that won't be used. + // Leave paths beginning with $WORK alone too. + if filepath.IsAbs(f) || strings.HasPrefix(f, "$WORK") { + return f + } + return filepath.Join(dir, f) +} + +// gc runs the Go compiler in a specific directory on a set of files +// to generate the named output file. +func (b *builder) gc(p *Package, ofile string, gcargs, importArgs []string, gofiles []string) error { + args := stringList(b.arch+"g", "-o", ofile, b.gcflags, gcargs, importArgs) + for _, f := range gofiles { + args = append(args, mkAbs(p.Dir, f)) + } + return b.run(p.Dir, p.ImportPath, args) +} + +// asm runs the assembler in a specific directory on a specific file +// to generate the named output file. +func (b *builder) asm(p *Package, obj, ofile, sfile string) error { + sfile = mkAbs(p.Dir, sfile) + return b.run(p.Dir, p.ImportPath, b.arch+"a", "-I", obj, "-o", ofile, "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, sfile) +} + +// gopack runs the assembler in a specific directory to create +// an archive from a set of object files. +// typically it is run in the object directory. +func (b *builder) gopack(p *Package, objDir, afile string, ofiles []string) error { + var absOfiles []string + for _, f := range ofiles { + absOfiles = append(absOfiles, mkAbs(objDir, f)) + } + return b.run(p.Dir, p.ImportPath, "gopack", "grc", mkAbs(objDir, afile), absOfiles) +} + +// ld runs the linker to create a package starting at mainpkg. +func (b *builder) ld(p *Package, out string, importArgs []string, mainpkg string) error { + return b.run(p.Dir, p.ImportPath, b.arch+"l", "-o", out, importArgs, mainpkg) +} + +// cc runs the gc-toolchain C compiler in a directory on a C file +// to produce an output file. +func (b *builder) cc(p *Package, objdir, ofile, cfile string) error { + inc := filepath.Join(b.goroot, "pkg", fmt.Sprintf("%s_%s", b.goos, b.goarch)) + cfile = mkAbs(p.Dir, cfile) + return b.run(p.Dir, p.ImportPath, b.arch+"c", "-FVw", + "-I", objdir, "-I", inc, "-o", ofile, + "-DGOOS_"+b.goos, "-DGOARCH_"+b.goarch, cfile) +} + +// gcc runs the gcc C compiler to create an object from a single C file. +func (b *builder) gcc(p *Package, out string, flags []string, cfile string) error { + cfile = mkAbs(p.Dir, cfile) + return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), flags, "-o", out, "-c", cfile) +} + +// gccld runs the gcc linker to create an executable from a set of object files +func (b *builder) gccld(p *Package, out string, flags []string, obj []string) error { + return b.run(p.Dir, p.ImportPath, b.gccCmd(p.Dir), "-o", out, obj, flags) +} + +// gccCmd returns a gcc command line prefix +func (b *builder) gccCmd(objdir string) []string { + // TODO: HOST_CC? + a := []string{"gcc", "-I", objdir, "-g", "-O2"} + + // Definitely want -fPIC but on Windows gcc complains + // "-fPIC ignored for target (all code is position independent)" + if b.goos != "windows" { + a = append(a, "-fPIC") + } + switch b.arch { + case "8": + a = append(a, "-m32") + case "6": + a = append(a, "-m64") + } + // gcc-4.5 and beyond require explicit "-pthread" flag + // for multithreading with pthread library. + if buildContext.CgoEnabled { + switch b.goos { + case "windows": + a = append(a, "-mthreads") + default: + a = append(a, "-pthread") + } + } + return a +} + +var cgoRe = regexp.MustCompile(`[/\\:]`) + +func (b *builder) cgo(p *Package, cgoExe, obj string, gccfiles []string) (outGo, outObj []string, err error) { + if b.goos != runtime.GOOS { + return nil, nil, errors.New("cannot use cgo when compiling for a different operating system") + } + + outObj = append(outObj, "") // for importObj, at end of function + + cgoCFLAGS := stringList(p.info.CgoCFLAGS) + cgoLDFLAGS := stringList(p.info.CgoLDFLAGS) + if pkgs := p.info.CgoPkgConfig; len(pkgs) > 0 { + out, err := b.runOut(p.Dir, p.ImportPath, "pkg-config", "--cflags", pkgs) + if err != nil { + return nil, nil, err + } + if len(out) > 0 { + cgoCFLAGS = append(cgoCFLAGS, strings.Fields(string(out))...) + } + out, err = b.runOut(p.Dir, p.ImportPath, "pkg-config", "--libs", pkgs) + if err != nil { + return nil, nil, err + } + if len(out) > 0 { + cgoLDFLAGS = append(cgoLDFLAGS, strings.Fields(string(out))...) + } + } + + // cgo + // TODO: CGOPKGPATH, CGO_FLAGS? + gofiles := []string{obj + "_cgo_gotypes.go"} + cfiles := []string{"_cgo_main.c", "_cgo_export.c"} + for _, fn := range p.CgoFiles { + f := cgoRe.ReplaceAllString(fn[:len(fn)-2], "_") + gofiles = append(gofiles, obj+f+"cgo1.go") + cfiles = append(cfiles, f+"cgo2.c") + } + defunC := obj + "_cgo_defun.c" + // TODO: make cgo not depend on $GOARCH? + var runtimeFlag []string + if p.Standard && p.ImportPath == "runtime/cgo" { + runtimeFlag = []string{"-import_runtime_cgo=false"} + } + if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, runtimeFlag, "--", p.CgoFiles); err != nil { + return nil, nil, err + } + outGo = append(outGo, gofiles...) + + // cc _cgo_defun.c + defunObj := obj + "_cgo_defun." + b.arch + if err := b.cc(p, obj, defunObj, defunC); err != nil { + return nil, nil, err + } + outObj = append(outObj, defunObj) + + // gcc + var linkobj []string + for _, cfile := range cfiles { + ofile := obj + cfile[:len(cfile)-1] + "o" + if err := b.gcc(p, ofile, cgoCFLAGS, obj+cfile); err != nil { + return nil, nil, err + } + linkobj = append(linkobj, ofile) + if !strings.HasSuffix(ofile, "_cgo_main.o") { + outObj = append(outObj, ofile) + } + } + for _, file := range gccfiles { + ofile := obj + cgoRe.ReplaceAllString(file[:len(file)-1], "_") + "o" + if err := b.gcc(p, ofile, cgoCFLAGS, file); err != nil { + return nil, nil, err + } + linkobj = append(linkobj, ofile) + outObj = append(outObj, ofile) + } + dynobj := obj + "_cgo_.o" + if err := b.gccld(p, dynobj, cgoLDFLAGS, linkobj); err != nil { + return nil, nil, err + } + + // cgo -dynimport + importC := obj + "_cgo_import.c" + if err := b.run(p.Dir, p.ImportPath, cgoExe, "-objdir", obj, "-dynimport", dynobj, "-dynout", importC); err != nil { + return nil, nil, err + } + + // cc _cgo_import.ARCH + importObj := obj + "_cgo_import." + b.arch + if err := b.cc(p, obj, importObj, importC); err != nil { + return nil, nil, err + } + + // NOTE(rsc): The importObj is a 5c/6c/8c object and on Windows + // must be processed before the gcc-generated objects. + // Put it first. We left room above. http://golang.org/issue/2601 + outObj[0] = importObj + + return outGo, outObj, nil +} + +// An actionQueue is a priority queue of actions. +type actionQueue []*action + +// Implement heap.Interface +func (q *actionQueue) Len() int { return len(*q) } +func (q *actionQueue) Swap(i, j int) { (*q)[i], (*q)[j] = (*q)[j], (*q)[i] } +func (q *actionQueue) Less(i, j int) bool { return (*q)[i].priority < (*q)[j].priority } +func (q *actionQueue) Push(x interface{}) { *q = append(*q, x.(*action)) } +func (q *actionQueue) Pop() interface{} { + n := len(*q) - 1 + x := (*q)[n] + *q = (*q)[:n] + return x +} + +func (q *actionQueue) push(a *action) { + heap.Push(q, a) +} + +func (q *actionQueue) pop() *action { + return heap.Pop(q).(*action) +} diff --git a/src/cmd/go/doc.go b/src/cmd/go/doc.go new file mode 100644 index 000000000..27be32bf3 --- /dev/null +++ b/src/cmd/go/doc.go @@ -0,0 +1,601 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +Go is a tool for managing Go source code. + +Usage: go command [arguments] + +The commands are: + + build compile packages and dependencies + doc run godoc on package sources + fix run gofix on packages + fmt run gofmt on package sources + get download and install packages and dependencies + install compile and install packages and dependencies + list list packages + run compile and run Go program + test test packages + version print Go version + vet run govet on packages + +Use "go help [command]" for more information about a command. + +Additional help topics: + + gopath GOPATH environment variable + importpath description of import paths + remote remote import path syntax + testflag description of testing flags + testfunc description of testing functions + +Use "go help [topic]" for more information about that topic. + + +Compile packages and dependencies + +Usage: + + go build [-a] [-n] [-o output] [-p n] [-v] [-x] [importpath... | gofiles...] + +Build compiles the packages named by the import paths, +along with their dependencies, but it does not install the results. + +If the arguments are a list of .go files, build treats them as a list +of source files specifying a single package. + +When the command line specifies a single main package, +build writes the resulting executable to output (default a.out). +Otherwise build compiles the packages but discards the results, +serving only as a check that the packages can be built. + +The -a flag forces rebuilding of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -v flag prints the names of packages as they are compiled. +The -x flag prints the commands. + +The -o flag specifies the output file name. +It is an error to use -o when the command line specifies multiple packages. + +The -p flag specifies the number of builds that can be run in parallel. +The default is the number of CPUs available. + +For more about import paths, see 'go help importpath'. + +See also: go install, go get, go clean. + + +Run godoc on package sources + +Usage: + + go doc [importpath...] + +Doc runs the godoc command on the packages named by the +import paths. + +For more about godoc, see 'godoc godoc'. +For more about import paths, see 'go help importpath'. + +To run godoc with specific options, run godoc itself. + +See also: go fix, go fmt, go vet. + + +Run gofix on packages + +Usage: + + go fix [importpath...] + +Fix runs the gofix command on the packages named by the import paths. + +For more about gofix, see 'godoc gofix'. +For more about import paths, see 'go help importpath'. + +To run gofix with specific options, run gofix itself. + +See also: go fmt, go vet. + + +Run gofmt on package sources + +Usage: + + go fmt [importpath...] + +Fmt runs the command 'gofmt -l -w' on the packages named +by the import paths. It prints the names of the files that are modified. + +For more about gofmt, see 'godoc gofmt'. +For more about import paths, see 'go help importpath'. + +To run gofmt with specific options, run gofmt itself. + +See also: go doc, go fix, go vet. + + +Download and install packages and dependencies + +Usage: + + go get [-a] [-d] [-fix] [-n] [-p n] [-u] [-v] [-x] [importpath...] + +Get downloads and installs the packages named by the import paths, +along with their dependencies. + +The -a, -n, -v, -x, and -p flags have the same meaning as in 'go build' +and 'go install'. See 'go help install'. + +The -d flag instructs get to stop after downloading the packages; that is, +it instructs get not to install the packages. + +The -fix flag instructs get to run gofix on the downloaded packages +before resolving dependencies or building the code. + +The -u flag instructs get to use the network to update the named packages +and their dependencies. By default, get uses the network to check out +missing packages but does not use it to look for updates to existing packages. + +TODO: Explain versions better. + +For more about import paths, see 'go help importpath'. + +For more about how 'go get' finds source code to +download, see 'go help remote'. + +See also: go build, go install, go clean. + + +Compile and install packages and dependencies + +Usage: + + go install [-a] [-n] [-p n] [-v] [-x] [importpath...] + +Install compiles and installs the packages named by the import paths, +along with their dependencies. + +The -a flag forces reinstallation of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -v flag prints the names of packages as they are compiled. +The -x flag prints the commands. + +The -p flag specifies the number of builds that can be run in parallel. +The default is the number of CPUs available. + +For more about import paths, see 'go help importpath'. + +See also: go build, go get, go clean. + + +List packages + +Usage: + + go list [-e] [-f format] [-json] [importpath...] + +List lists the packages named by the import paths, one per line. + +The default output shows the package import path: + + code.google.com/p/google-api-go-client/books/v1 + code.google.com/p/goauth2/oauth + code.google.com/p/sqlite + +The -f flag specifies an alternate format for the list, +using the syntax of package template. The default output +is equivalent to -f '{{.ImportPath}}'. The struct +being passed to the template is: + + type Package struct { + Name string // package name + Doc string // package documentation string + ImportPath string // import path of package in dir + Dir string // directory containing package sources + Version string // version of installed package (TODO) + Stale bool // would 'go install' do anything for this package? + + // Source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, and XTestGoFiles) + TestGoFiles []string // _test.go source files internal to the package they are testing + XTestGoFiles []string // _test.go source files external to the package they are testing + CFiles []string // .c source files + HFiles []string // .h source files + SFiles []string // .s source files + CgoFiles []string // .go sources files that import "C" + + // Dependency information + Imports []string // import paths used by this package + Deps []string // all (recursively) imported dependencies + + // Error information + Incomplete bool // this package or a dependency has an error + Error *PackageError // error loading package + DepsErrors []*PackageError // errors loading dependencies + } + +The -json flag causes the package data to be printed in JSON format +instead of using the template format. + +The -e flag changes the handling of erroneous packages, those that +cannot be found or are malformed. By default, the list command +prints an error to standard error for each erroneous package and +omits the packages from consideration during the usual printing. +With the -e flag, the list command never prints errors to standard +error and instead processes the erroneous packages with the usual +printing. Erroneous packages will have a non-empty ImportPath and +a non-nil Error field; other information may or may not be missing +(zeroed). + +For more about import paths, see 'go help importpath'. + + +Compile and run Go program + +Usage: + + go run [-a] [-n] [-x] gofiles... [arguments...] + +Run compiles and runs the main package comprising the named Go source files. + +The -a flag forces reinstallation of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -x flag prints the commands. + +See also: go build. + + +Test packages + +Usage: + + go test [-c] [-file a.go -file b.go ...] [-p n] [-x] [importpath...] [flags for test binary] + +'Go test' automates testing the packages named by the import paths. +It prints a summary of the test results in the format: + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + +followed by detailed output for each failed package. + +'Go test' recompiles each package along with any files with names matching +the file pattern "*_test.go". These additional files can contain test functions, +benchmark functions, and example functions. See 'go help testfunc' for more. + +By default, go test needs no arguments. It compiles and tests the package +with source in the current directory, including tests, and runs the tests. +If file names are given (with flag -file=test.go, one per extra test source file), +only those test files are added to the package. (The non-test files are always +compiled.) + +The package is built in a temporary directory so it does not interfere with the +non-test installation. + +See 'go help testflag' for details about flags handled by 'go test' +and the test binary. + +See 'go help importpath' for more about import paths. + +See also: go build, go vet. + + +Print Go version + +Usage: + + go version + +Version prints the Go version, as reported by runtime.Version. + + +Run govet on packages + +Usage: + + go vet [importpath...] + +Vet runs the govet command on the packages named by the import paths. + +For more about govet, see 'godoc govet'. +For more about import paths, see 'go help importpath'. + +To run govet with specific options, run govet itself. + +See also: go fmt, go fix. + + +GOPATH environment variable + +The GOPATH environment variable lists places to look for Go code. +On Unix, the value is a colon-separated string. +On Windows, the value is a semicolon-separated string. +On Plan 9, the value is a list. + +GOPATH must be set to build and install packages outside the +standard Go tree. + +Each directory listed in GOPATH must have a prescribed structure: + +The src/ directory holds source code. The path below 'src' +determines the import path or executable name. + +The pkg/ directory holds installed package objects. +As in the Go tree, each target operating system and +architecture pair has its own subdirectory of pkg +(pkg/GOOS_GOARCH). + +If DIR is a directory listed in the GOPATH, a package with +source in DIR/src/foo/bar can be imported as "foo/bar" and +has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a". + +The bin/ directory holds compiled commands. +Each command is named for its source directory, but only +the final element, not the entire path. That is, the +command with source in DIR/src/foo/quux is installed into +DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped +so that you can add DIR/bin to your PATH to get at the +installed commands. + +Here's an example directory layout: + + GOPATH=/home/user/gocode + + /home/user/gocode/ + src/ + foo/ + bar/ (go code in package bar) + x.go + quux/ (go code in package main) + y.go + bin/ + quux (installed command) + pkg/ + linux_amd64/ + foo/ + bar.a (installed package object) + +Go searches each directory listed in GOPATH to find source code, +but new packages are always downloaded into the first directory +in the list. + + +Description of import paths + +Many commands apply to a set of packages named by import paths: + + go action [importpath...] + +An import path that is a rooted path or that begins with +a . or .. element is interpreted as a file system path and +denotes the package in that directory. + +Otherwise, the import path P denotes the package found in +the directory DIR/src/P for some DIR listed in the GOPATH +environment variable (see 'go help gopath'). + +If no import paths are given, the action applies to the +package in the current directory. + +The special import path "all" expands to all package directories +found in all the GOPATH trees. For example, 'go list all' +lists all the packages on the local system. + +The special import path "std" is like all but expands to just the +packages in the standard Go library. + +An import path is a pattern if it includes one or more "..." wildcards, +each of which can match any string, including the empty string and +strings containing slashes. Such a pattern expands to all package +directories found in the GOPATH trees with names matching the +patterns. For example, encoding/... expands to all packages +in the encoding tree. + +An import path can also name a package to be downloaded from +a remote repository. Run 'go help remote' for details. + +Every package in a program must have a unique import path. +By convention, this is arranged by starting each path with a +unique prefix that belongs to you. For example, paths used +internally at Google all begin with 'google', and paths +denoting remote repositories begin with the path to the code, +such as 'code.google.com/p/project'. + + +Remote import path syntax + +An import path (see 'go help importpath') denotes a package +stored in the local file system. Certain import paths also +describe how to obtain the source code for the package using +a revision control system. + +A few common code hosting sites have special syntax: + + BitBucket (Mercurial) + + import "bitbucket.org/user/project" + import "bitbucket.org/user/project/sub/directory" + + GitHub (Git) + + import "github.com/user/project" + import "github.com/user/project/sub/directory" + + Google Code Project Hosting (Git, Mercurial, Subversion) + + import "code.google.com/p/project" + import "code.google.com/p/project/sub/directory" + + import "code.google.com/p/project.subrepository" + import "code.google.com/p/project.subrepository/sub/directory" + + Launchpad (Bazaar) + + import "launchpad.net/project" + import "launchpad.net/project/series" + import "launchpad.net/project/series/sub/directory" + + import "launchpad.net/~user/project/branch" + import "launchpad.net/~user/project/branch/sub/directory" + +For code hosted on other servers, an import path of the form + + repository.vcs/path + +specifies the given repository, with or without the .vcs suffix, +using the named version control system, and then the path inside +that repository. The supported version control systems are: + + Bazaar .bzr + Git .git + Mercurial .hg + Subversion .svn + +For example, + + import "example.org/user/foo.hg" + +denotes the root directory of the Mercurial repository at +example.org/user/foo or foo.hg, and + + import "example.org/repo.git/foo/bar" + +denotes the foo/bar directory of the Git repository at +example.com/repo or repo.git. + +When a version control system supports multiple protocols, +each is tried in turn when downloading. For example, a Git +download tries git://, then https://, then http://. + +New downloaded packages are written to the first directory +listed in the GOPATH environment variable (see 'go help gopath'). + +The go command attempts to download the version of the +package appropriate for the Go release being used. +Run 'go help install' for more. + + +Description of testing flags + +The 'go test' command takes both flags that apply to 'go test' itself +and flags that apply to the resulting test binary. + +The flags handled by 'go test' are: + + -c Compile the test binary to test.out but do not run it. + + -file a.go + Use only the tests in the source file a.go. + Multiple -file flags may be provided. + + -p n + Compile and test up to n packages in parallel. + The default value is the number of CPUs available. + + -x Print each subcommand go test executes. + +The resulting test binary, called test.out, has its own flags: + + -test.v + Verbose output: log all tests as they are run. + + -test.run pattern + Run only those tests matching the regular expression. + + -test.bench pattern + Run benchmarks matching the regular expression. + By default, no benchmarks run. + + -test.cpuprofile cpu.out + Write a CPU profile to the specified file before exiting. + + -test.memprofile mem.out + Write a memory profile to the specified file when all tests + are complete. + + -test.memprofilerate n + Enable more precise (and expensive) memory profiles by setting + runtime.MemProfileRate. See 'godoc runtime MemProfileRate'. + To profile all memory allocations, use -test.memprofilerate=1 + and set the environment variable GOGC=off to disable the + garbage collector, provided the test can run in the available + memory without garbage collection. + + -test.parallel n + Allow parallel execution of test functions that call t.Parallel. + The value of this flag is the maximum number of tests to run + simultaneously; by default, it is set to the value of GOMAXPROCS. + + -test.short + Tell long-running tests to shorten their run time. + It is off by default but set during all.bash so that installing + the Go tree can run a sanity check but not spend time running + exhaustive tests. + + -test.timeout t + If a test runs longer than t, panic. + + -test.benchtime n + Run enough iterations of each benchmark to take n seconds. + The default is 1 second. + + -test.cpu 1,2,4 + Specify a list of GOMAXPROCS values for which the tests or + benchmarks should be executed. The default is the current value + of GOMAXPROCS. + +For convenience, each of these -test.X flags of the test binary is +also available as the flag -X in 'go test' itself. Flags not listed +here are passed through unaltered. For instance, the command + + go test -x -v -cpuprofile=prof.out -dir=testdata -update -file x_test.go + +will compile the test binary using x_test.go and then run it as + + test.out -test.v -test.cpuprofile=prof.out -dir=testdata -update + + +Description of testing functions + +The 'go test' command expects to find test, benchmark, and example functions +in the "*_test.go" files corresponding to the package under test. + +A test function is one named TestXXX (where XXX is any alphanumeric string +not starting with a lower case letter) and should have the signature, + + func TestXXX(t *testing.T) { ... } + +A benchmark function is one named BenchmarkXXX and should have the signature, + + func BenchmarkXXX(b *testing.B) { ... } + +An example function is similar to a test function but, instead of using *testing.T +to report success or failure, prints output to os.Stdout and os.Stderr. +That output is compared against the function's doc comment. +An example without a doc comment is compiled but not executed. + +Godoc displays the body of ExampleXXX to demonstrate the use +of the function, constant, or variable XXX. An example of a method M with +receiver type T or *T is named ExampleT_M. There may be multiple examples +for a given function, constant, or variable, distinguished by a trailing _xxx, +where xxx is a suffix not beginning with an upper case letter. + +Here is an example of an example: + + // The output of this example function. + func ExamplePrintln() { + Println("The output of this example function.") + } + +See the documentation of the testing package for more information. + + +*/ +package documentation + +// NOTE: cmdDoc is in fmt.go. diff --git a/src/cmd/go/fix.go b/src/cmd/go/fix.go new file mode 100644 index 000000000..bae9f5c98 --- /dev/null +++ b/src/cmd/go/fix.go @@ -0,0 +1,30 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +var cmdFix = &Command{ + Run: runFix, + UsageLine: "fix [importpath...]", + Short: "run gofix on packages", + Long: ` +Fix runs the gofix command on the packages named by the import paths. + +For more about gofix, see 'godoc gofix'. +For more about import paths, see 'go help importpath'. + +To run gofix with specific options, run gofix itself. + +See also: go fmt, go vet. + `, +} + +func runFix(cmd *Command, args []string) { + for _, pkg := range packages(args) { + // Use pkg.gofiles instead of pkg.Dir so that + // the command only applies to this package, + // not to packages in subdirectories. + run(stringList("gofix", relPaths(pkg.gofiles))) + } +} diff --git a/src/cmd/go/fmt.go b/src/cmd/go/fmt.go new file mode 100644 index 000000000..4a47e2ea2 --- /dev/null +++ b/src/cmd/go/fmt.go @@ -0,0 +1,54 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +var cmdFmt = &Command{ + Run: runFmt, + UsageLine: "fmt [importpath...]", + Short: "run gofmt on package sources", + Long: ` +Fmt runs the command 'gofmt -l -w' on the packages named +by the import paths. It prints the names of the files that are modified. + +For more about gofmt, see 'godoc gofmt'. +For more about import paths, see 'go help importpath'. + +To run gofmt with specific options, run gofmt itself. + +See also: go doc, go fix, go vet. + `, +} + +func runFmt(cmd *Command, args []string) { + for _, pkg := range packages(args) { + // Use pkg.gofiles instead of pkg.Dir so that + // the command only applies to this package, + // not to packages in subdirectories. + run(stringList("gofmt", "-l", "-w", relPaths(pkg.gofiles))) + } +} + +var cmdDoc = &Command{ + Run: runDoc, + UsageLine: "doc [importpath...]", + Short: "run godoc on package sources", + Long: ` +Doc runs the godoc command on the packages named by the +import paths. + +For more about godoc, see 'godoc godoc'. +For more about import paths, see 'go help importpath'. + +To run godoc with specific options, run godoc itself. + +See also: go fix, go fmt, go vet. + `, +} + +func runDoc(cmd *Command, args []string) { + for _, pkg := range packages(args) { + run("godoc", pkg.Dir) + } +} diff --git a/src/cmd/go/get.go b/src/cmd/go/get.go new file mode 100644 index 000000000..cd57d3025 --- /dev/null +++ b/src/cmd/go/get.go @@ -0,0 +1,269 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// TODO: Dashboard upload + +package main + +import ( + "fmt" + "go/build" + "os" + "path/filepath" + "runtime" + "strconv" + "strings" +) + +var cmdGet = &Command{ + UsageLine: "get [-a] [-d] [-fix] [-n] [-p n] [-u] [-v] [-x] [importpath...]", + Short: "download and install packages and dependencies", + Long: ` +Get downloads and installs the packages named by the import paths, +along with their dependencies. + +The -a, -n, -v, -x, and -p flags have the same meaning as in 'go build' +and 'go install'. See 'go help install'. + +The -d flag instructs get to stop after downloading the packages; that is, +it instructs get not to install the packages. + +The -fix flag instructs get to run gofix on the downloaded packages +before resolving dependencies or building the code. + +The -u flag instructs get to use the network to update the named packages +and their dependencies. By default, get uses the network to check out +missing packages but does not use it to look for updates to existing packages. + +TODO: Explain versions better. + +For more about import paths, see 'go help importpath'. + +For more about how 'go get' finds source code to +download, see 'go help remote'. + +See also: go build, go install, go clean. + `, +} + +var getD = cmdGet.Flag.Bool("d", false, "") +var getU = cmdGet.Flag.Bool("u", false, "") +var getFix = cmdGet.Flag.Bool("fix", false, "") + +func init() { + addBuildFlags(cmdGet) + cmdGet.Run = runGet // break init loop +} + +func runGet(cmd *Command, args []string) { + // Phase 1. Download/update. + args = importPaths(args) + var stk importStack + for _, arg := range args { + download(arg, &stk) + } + exitIfErrors() + + if *getD { + // download only + return + } + + // Phase 2. Install. + + // Code we downloaded and all code that depends on it + // needs to be evicted from the package cache so that + // the information will be recomputed. Instead of keeping + // track of the reverse dependency information, evict + // everything. + for name := range packageCache { + delete(packageCache, name) + } + + runInstall(cmd, args) +} + +// downloadCache records the import paths we have already +// considered during the download, to avoid duplicate work when +// there is more than one dependency sequence leading to +// a particular package. +var downloadCache = map[string]bool{} + +// downloadRootCache records the version control repository +// root directories we have already considered during the download. +// For example, all the packages in the code.google.com/p/codesearch repo +// share the same root (the directory for that path), and we only need +// to run the hg commands to consider each repository once. +var downloadRootCache = map[string]bool{} + +// download runs the download half of the get command +// for the package named by the argument. +func download(arg string, stk *importStack) { + p := loadPackage(arg, stk) + + // There's nothing to do if this is a package in the standard library. + if p.Standard { + return + } + + // Only process each package once. + if downloadCache[arg] { + return + } + downloadCache[arg] = true + + // Download if the package is missing, or update if we're using -u. + if p.Dir == "" || *getU { + // The actual download. + stk.push(p.ImportPath) + defer stk.pop() + if err := downloadPackage(p); err != nil { + errorf("%s", &PackageError{stk.copy(), err.Error()}) + return + } + + // Reread the package information from the updated files. + p = reloadPackage(arg, stk) + if p.Error != nil { + errorf("%s", p.Error) + return + } + } + + if *getFix { + run(stringList("gofix", relPaths(p.gofiles))) + + // The imports might have changed, so reload again. + p = reloadPackage(arg, stk) + if p.Error != nil { + errorf("%s", p.Error) + return + } + } + + // Process dependencies, now that we know what they are. + for _, dep := range p.deps { + download(dep.ImportPath, stk) + } +} + +// downloadPackage runs the create or download command +// to make the first copy of or update a copy of the given package. +func downloadPackage(p *Package) error { + // Analyze the import path to determine the version control system, + // repository, and the import path for the root of the repository. + vcs, repo, rootPath, err := vcsForImportPath(p.ImportPath) + if err != nil { + return err + } + if p.t == nil { + // Package not found. Put in first directory of $GOPATH or else $GOROOT. + p.t = build.Path[0] // $GOROOT + if len(build.Path) > 1 { + p.t = build.Path[1] // first in $GOPATH + } + p.Dir = filepath.Join(p.t.SrcDir(), p.ImportPath) + } + root := filepath.Join(p.t.SrcDir(), rootPath) + + // If we've considered this repository already, don't do it again. + if downloadRootCache[root] { + return nil + } + downloadRootCache[root] = true + + if buildV { + fmt.Fprintf(os.Stderr, "%s (download)\n", rootPath) + } + + // Check that this is an appropriate place for the repo to be checked out. + // The target directory must either not exist or have a repo checked out already. + meta := filepath.Join(root, "."+vcs.cmd) + st, err := os.Stat(meta) + if err == nil && !st.IsDir() { + return fmt.Errorf("%s exists but is not a directory", meta) + } + if err != nil { + // Metadata directory does not exist. Prepare to checkout new copy. + // Some version control tools require the target directory not to exist. + // We require that too, just to avoid stepping on existing work. + if _, err := os.Stat(root); err == nil { + return fmt.Errorf("%s exists but %s does not - stale checkout?", root, meta) + } + // Some version control tools require the parent of the target to exist. + parent, _ := filepath.Split(root) + if err := os.MkdirAll(parent, 0777); err != nil { + return err + } + if err = vcs.create(root, repo); err != nil { + return err + } + } else { + // Metadata directory does exist; download incremental updates. + if err = vcs.download(root); err != nil { + return err + } + } + + // Select and sync to appropriate version of the repository. + tags, err := vcs.tags(root) + if err != nil { + return err + } + vers := runtime.Version() + if i := strings.Index(vers, " "); i >= 0 { + vers = vers[:i] + } + tag := selectTag(vers, tags) + if tag == "" { + tag = vcs.tagDefault + } + if err := vcs.tagSync(root, tag); err != nil { + return err + } + + return nil +} + +// selectTag returns the closest matching tag for a given version. +// Closest means the latest one that is not after the current release. +// Version "release.rN" matches tags of the form "go.rN" (N being a decimal). +// Version "weekly.YYYY-MM-DD" matches tags like "go.weekly.YYYY-MM-DD". +func selectTag(goVersion string, tags []string) (match string) { + const rPrefix = "release.r" + if strings.HasPrefix(goVersion, rPrefix) { + p := "go.r" + v, err := strconv.ParseFloat(goVersion[len(rPrefix):], 64) + if err != nil { + return "" + } + var matchf float64 + for _, t := range tags { + if !strings.HasPrefix(t, p) { + continue + } + tf, err := strconv.ParseFloat(t[len(p):], 64) + if err != nil { + continue + } + if matchf < tf && tf <= v { + match, matchf = t, tf + } + } + } + const wPrefix = "weekly." + if strings.HasPrefix(goVersion, wPrefix) { + p := "go.weekly." + v := goVersion[len(wPrefix):] + for _, t := range tags { + if !strings.HasPrefix(t, p) { + continue + } + if match < t && t[len(p):] <= v { + match = t + } + } + } + return match +} diff --git a/src/cmd/go/help.go b/src/cmd/go/help.go new file mode 100644 index 000000000..33716eff9 --- /dev/null +++ b/src/cmd/go/help.go @@ -0,0 +1,185 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +var helpImportpath = &Command{ + UsageLine: "importpath", + Short: "description of import paths", + Long: ` +Many commands apply to a set of packages named by import paths: + + go action [importpath...] + +An import path that is a rooted path or that begins with +a . or .. element is interpreted as a file system path and +denotes the package in that directory. + +Otherwise, the import path P denotes the package found in +the directory DIR/src/P for some DIR listed in the GOPATH +environment variable (see 'go help gopath'). + +If no import paths are given, the action applies to the +package in the current directory. + +The special import path "all" expands to all package directories +found in all the GOPATH trees. For example, 'go list all' +lists all the packages on the local system. + +The special import path "std" is like all but expands to just the +packages in the standard Go library. + +An import path is a pattern if it includes one or more "..." wildcards, +each of which can match any string, including the empty string and +strings containing slashes. Such a pattern expands to all package +directories found in the GOPATH trees with names matching the +patterns. For example, encoding/... expands to all packages +in the encoding tree. + +An import path can also name a package to be downloaded from +a remote repository. Run 'go help remote' for details. + +Every package in a program must have a unique import path. +By convention, this is arranged by starting each path with a +unique prefix that belongs to you. For example, paths used +internally at Google all begin with 'google', and paths +denoting remote repositories begin with the path to the code, +such as 'code.google.com/p/project'. + `, +} + +var helpRemote = &Command{ + UsageLine: "remote", + Short: "remote import path syntax", + Long: ` + +An import path (see 'go help importpath') denotes a package +stored in the local file system. Certain import paths also +describe how to obtain the source code for the package using +a revision control system. + +A few common code hosting sites have special syntax: + + BitBucket (Mercurial) + + import "bitbucket.org/user/project" + import "bitbucket.org/user/project/sub/directory" + + GitHub (Git) + + import "github.com/user/project" + import "github.com/user/project/sub/directory" + + Google Code Project Hosting (Git, Mercurial, Subversion) + + import "code.google.com/p/project" + import "code.google.com/p/project/sub/directory" + + import "code.google.com/p/project.subrepository" + import "code.google.com/p/project.subrepository/sub/directory" + + Launchpad (Bazaar) + + import "launchpad.net/project" + import "launchpad.net/project/series" + import "launchpad.net/project/series/sub/directory" + + import "launchpad.net/~user/project/branch" + import "launchpad.net/~user/project/branch/sub/directory" + +For code hosted on other servers, an import path of the form + + repository.vcs/path + +specifies the given repository, with or without the .vcs suffix, +using the named version control system, and then the path inside +that repository. The supported version control systems are: + + Bazaar .bzr + Git .git + Mercurial .hg + Subversion .svn + +For example, + + import "example.org/user/foo.hg" + +denotes the root directory of the Mercurial repository at +example.org/user/foo or foo.hg, and + + import "example.org/repo.git/foo/bar" + +denotes the foo/bar directory of the Git repository at +example.com/repo or repo.git. + +When a version control system supports multiple protocols, +each is tried in turn when downloading. For example, a Git +download tries git://, then https://, then http://. + +New downloaded packages are written to the first directory +listed in the GOPATH environment variable (see 'go help gopath'). + +The go command attempts to download the version of the +package appropriate for the Go release being used. +Run 'go help install' for more. + `, +} + +var helpGopath = &Command{ + UsageLine: "gopath", + Short: "GOPATH environment variable", + Long: ` +The GOPATH environment variable lists places to look for Go code. +On Unix, the value is a colon-separated string. +On Windows, the value is a semicolon-separated string. +On Plan 9, the value is a list. + +GOPATH must be set to build and install packages outside the +standard Go tree. + +Each directory listed in GOPATH must have a prescribed structure: + +The src/ directory holds source code. The path below 'src' +determines the import path or executable name. + +The pkg/ directory holds installed package objects. +As in the Go tree, each target operating system and +architecture pair has its own subdirectory of pkg +(pkg/GOOS_GOARCH). + +If DIR is a directory listed in the GOPATH, a package with +source in DIR/src/foo/bar can be imported as "foo/bar" and +has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a". + +The bin/ directory holds compiled commands. +Each command is named for its source directory, but only +the final element, not the entire path. That is, the +command with source in DIR/src/foo/quux is installed into +DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped +so that you can add DIR/bin to your PATH to get at the +installed commands. + +Here's an example directory layout: + + GOPATH=/home/user/gocode + + /home/user/gocode/ + src/ + foo/ + bar/ (go code in package bar) + x.go + quux/ (go code in package main) + y.go + bin/ + quux (installed command) + pkg/ + linux_amd64/ + foo/ + bar.a (installed package object) + +Go searches each directory listed in GOPATH to find source code, +but new packages are always downloaded into the first directory +in the list. + `, +} diff --git a/src/cmd/go/http.go b/src/cmd/go/http.go new file mode 100644 index 000000000..8d9b2a165 --- /dev/null +++ b/src/cmd/go/http.go @@ -0,0 +1,35 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !cmd_go_bootstrap + +// This code is compiled into the real 'go' binary, but it is not +// compiled into the binary that is built during all.bash, so as +// to avoid needing to build net (and thus use cgo) during the +// bootstrap process. + +package main + +import ( + "fmt" + "io/ioutil" + "net/http" +) + +// httpGET returns the data from an HTTP GET request for the given URL. +func httpGET(url string) ([]byte, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return nil, fmt.Errorf("%s: %s", url, resp.Status) + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("%s: %v", url, err) + } + return b, nil +} diff --git a/src/cmd/go/list.go b/src/cmd/go/list.go new file mode 100644 index 000000000..af211f98d --- /dev/null +++ b/src/cmd/go/list.go @@ -0,0 +1,114 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "encoding/json" + "os" + "text/template" +) + +var cmdList = &Command{ + UsageLine: "list [-e] [-f format] [-json] [importpath...]", + Short: "list packages", + Long: ` +List lists the packages named by the import paths, one per line. + +The default output shows the package import path: + + code.google.com/p/google-api-go-client/books/v1 + code.google.com/p/goauth2/oauth + code.google.com/p/sqlite + +The -f flag specifies an alternate format for the list, +using the syntax of package template. The default output +is equivalent to -f '{{.ImportPath}}'. The struct +being passed to the template is: + + type Package struct { + Name string // package name + Doc string // package documentation string + ImportPath string // import path of package in dir + Dir string // directory containing package sources + Version string // version of installed package (TODO) + Stale bool // would 'go install' do anything for this package? + + // Source files + GoFiles []string // .go source files (excluding CgoFiles, TestGoFiles, and XTestGoFiles) + TestGoFiles []string // _test.go source files internal to the package they are testing + XTestGoFiles []string // _test.go source files external to the package they are testing + CFiles []string // .c source files + HFiles []string // .h source files + SFiles []string // .s source files + CgoFiles []string // .go sources files that import "C" + + // Dependency information + Imports []string // import paths used by this package + Deps []string // all (recursively) imported dependencies + + // Error information + Incomplete bool // this package or a dependency has an error + Error *PackageError // error loading package + DepsErrors []*PackageError // errors loading dependencies + } + +The -json flag causes the package data to be printed in JSON format +instead of using the template format. + +The -e flag changes the handling of erroneous packages, those that +cannot be found or are malformed. By default, the list command +prints an error to standard error for each erroneous package and +omits the packages from consideration during the usual printing. +With the -e flag, the list command never prints errors to standard +error and instead processes the erroneous packages with the usual +printing. Erroneous packages will have a non-empty ImportPath and +a non-nil Error field; other information may or may not be missing +(zeroed). + +For more about import paths, see 'go help importpath'. + `, +} + +func init() { + cmdList.Run = runList // break init cycle +} + +var listE = cmdList.Flag.Bool("e", false, "") +var listFmt = cmdList.Flag.String("f", "{{.ImportPath}}", "") +var listJson = cmdList.Flag.Bool("json", false, "") +var nl = []byte{'\n'} + +func runList(cmd *Command, args []string) { + var do func(*Package) + if *listJson { + do = func(p *Package) { + b, err := json.MarshalIndent(p, "", "\t") + if err != nil { + fatalf("%s", err) + } + os.Stdout.Write(b) + os.Stdout.Write(nl) + } + } else { + tmpl, err := template.New("main").Parse(*listFmt + "\n") + if err != nil { + fatalf("%s", err) + } + do = func(p *Package) { + if err := tmpl.Execute(os.Stdout, p); err != nil { + fatalf("%s", err) + } + } + } + + load := packages + if *listE { + load = packagesAndErrors + } + + for _, pkg := range load(args) { + do(pkg) + } +} diff --git a/src/cmd/go/main.go b/src/cmd/go/main.go new file mode 100644 index 000000000..ca3b1188a --- /dev/null +++ b/src/cmd/go/main.go @@ -0,0 +1,485 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/build" + "io" + "log" + "os" + "os/exec" + "path" + "path/filepath" + "regexp" + "strings" + "text/template" + "unicode" + "unicode/utf8" +) + +// A Command is an implementation of a go command +// like go build or go fix. +type Command struct { + // Run runs the command. + // The args are the arguments after the command name. + Run func(cmd *Command, args []string) + + // UsageLine is the one-line usage message. + // The first word in the line is taken to be the command name. + UsageLine string + + // Short is the short description shown in the 'go help' output. + Short string + + // Long is the long message shown in the 'go help <this-command>' output. + Long string + + // Flag is a set of flags specific to this command. + Flag flag.FlagSet + + // CustomFlags indicates that the command will do its own + // flag parsing. + CustomFlags bool +} + +// Name returns the command's name: the first word in the usage line. +func (c *Command) Name() string { + name := c.UsageLine + i := strings.Index(name, " ") + if i >= 0 { + name = name[:i] + } + return name +} + +func (c *Command) Usage() { + fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) + fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(c.Long)) + os.Exit(2) +} + +// Commands lists the available commands and help topics. +// The order here is the order in which they are printed by 'go help'. +var commands = []*Command{ + cmdBuild, + cmdDoc, + cmdFix, + cmdFmt, + cmdGet, + cmdInstall, + cmdList, + cmdRun, + cmdTest, + cmdVersion, + cmdVet, + + helpGopath, + helpImportpath, + helpRemote, + helpTestflag, + helpTestfunc, +} + +var exitStatus = 0 + +func main() { + flag.Usage = usage + flag.Parse() + log.SetFlags(0) + + args := flag.Args() + if len(args) < 1 { + usage() + } + + if args[0] == "help" { + help(args[1:]) + return + } + + for _, cmd := range commands { + if cmd.Name() == args[0] && cmd.Run != nil { + cmd.Flag.Usage = func() { cmd.Usage() } + if cmd.CustomFlags { + args = args[1:] + } else { + cmd.Flag.Parse(args[1:]) + args = cmd.Flag.Args() + } + cmd.Run(cmd, args) + exit() + return + } + } + + fmt.Fprintf(os.Stderr, "Unknown command %#q\n\n", args[0]) + usage() +} + +var usageTemplate = `Go is a tool for managing Go source code. + +Usage: go command [arguments] + +The commands are: +{{range .}}{{if .Run}} + {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} + +Use "go help [command]" for more information about a command. + +Additional help topics: +{{range .}}{{if not .Run}} + {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} + +Use "go help [topic]" for more information about that topic. + +` + +var helpTemplate = `{{if .Run}}usage: go {{.UsageLine}} + +{{end}}{{.Long | trim}} +` + +var documentationTemplate = `// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +/* +{{range .}}{{if .Short}}{{.Short | capitalize}} + +{{end}}{{if .Run}}Usage: + + go {{.UsageLine}} + +{{end}}{{.Long | trim}} + + +{{end}}*/ +package documentation + +// NOTE: cmdDoc is in fmt.go. +` + +// tmpl executes the given template text on data, writing the result to w. +func tmpl(w io.Writer, text string, data interface{}) { + t := template.New("top") + t.Funcs(template.FuncMap{"trim": strings.TrimSpace, "capitalize": capitalize}) + template.Must(t.Parse(text)) + if err := t.Execute(w, data); err != nil { + panic(err) + } +} + +func capitalize(s string) string { + if s == "" { + return s + } + r, n := utf8.DecodeRuneInString(s) + return string(unicode.ToTitle(r)) + s[n:] +} + +func printUsage(w io.Writer) { + tmpl(w, usageTemplate, commands) +} + +func usage() { + printUsage(os.Stderr) + os.Exit(2) +} + +// help implements the 'help' command. +func help(args []string) { + if len(args) == 0 { + printUsage(os.Stdout) + // not exit 2: succeeded at 'go help'. + return + } + if len(args) != 1 { + fmt.Fprintf(os.Stderr, "usage: go help command\n\nToo many arguments given.\n") + os.Exit(2) // failed at 'go help' + } + + arg := args[0] + + // 'go help documentation' generates doc.go. + if arg == "documentation" { + buf := new(bytes.Buffer) + printUsage(buf) + usage := &Command{Long: buf.String()} + tmpl(os.Stdout, documentationTemplate, append([]*Command{usage}, commands...)) + return + } + + for _, cmd := range commands { + if cmd.Name() == arg { + tmpl(os.Stdout, helpTemplate, cmd) + // not exit 2: succeeded at 'go help cmd'. + return + } + } + + fmt.Fprintf(os.Stderr, "Unknown help topic %#q. Run 'go help'.\n", arg) + os.Exit(2) // failed at 'go help cmd' +} + +// importPaths returns the import paths to use for the given command line. +func importPaths(args []string) []string { + if len(args) == 0 { + return []string{"."} + } + var out []string + for _, a := range args { + if isLocalPath(a) && strings.Contains(a, "...") { + out = append(out, allPackagesInFS(a)...) + continue + } + if a == "all" || a == "std" || strings.Contains(a, "...") { + out = append(out, allPackages(a)...) + continue + } + out = append(out, a) + } + return out +} + +var atexitFuncs []func() + +func atexit(f func()) { + atexitFuncs = append(atexitFuncs, f) +} + +func exit() { + for _, f := range atexitFuncs { + f() + } + os.Exit(exitStatus) +} + +func fatalf(format string, args ...interface{}) { + errorf(format, args...) + exit() +} + +func errorf(format string, args ...interface{}) { + log.Printf(format, args...) + exitStatus = 1 +} + +var logf = log.Printf + +func exitIfErrors() { + if exitStatus != 0 { + exit() + } +} + +func run(cmdargs ...interface{}) { + cmdline := stringList(cmdargs...) + cmd := exec.Command(cmdline[0], cmdline[1:]...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + errorf("%v", err) + } +} + +func runOut(dir string, cmdargs ...interface{}) []byte { + cmdline := stringList(cmdargs...) + cmd := exec.Command(cmdline[0], cmdline[1:]...) + cmd.Dir = dir + out, err := cmd.CombinedOutput() + if err != nil { + os.Stderr.Write(out) + errorf("%v", err) + out = nil + } + return out +} + +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +func matchPattern(pattern string) func(name string) bool { + re := regexp.QuoteMeta(pattern) + re = strings.Replace(re, `\.\.\.`, `.*`, -1) + reg := regexp.MustCompile(`^` + re + `$`) + return func(name string) bool { + return reg.MatchString(name) + } +} + +// allPackages returns all the packages that can be found +// under the $GOPATH directories and $GOROOT matching pattern. +// The pattern is either "all" (all packages), "std" (standard packages) +// or a path including "...". +func allPackages(pattern string) []string { + match := func(string) bool { return true } + if pattern != "all" && pattern != "std" { + match = matchPattern(pattern) + } + + have := map[string]bool{ + "builtin": true, // ignore pseudo-package that exists only for documentation + } + if !buildContext.CgoEnabled { + have["runtime/cgo"] = true // ignore during walk + } + var pkgs []string + + // Commands + goroot := build.Path[0].Path + cmd := filepath.Join(goroot, "src/cmd") + string(filepath.Separator) + filepath.Walk(cmd, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + name := path[len(cmd):] + // Commands are all in cmd/, not in subdirectories. + if strings.Contains(name, string(filepath.Separator)) { + return filepath.SkipDir + } + + _, err = build.ScanDir(path) + if err != nil { + return nil + } + + // We use, e.g., cmd/gofmt as the pseudo import path for gofmt. + name = "cmd/" + name + if !have[name] { + have[name] = true + if match(name) { + pkgs = append(pkgs, name) + } + } + return nil + }) + + for _, t := range build.Path { + if pattern == "std" && !t.Goroot { + continue + } + src := t.SrcDir() + string(filepath.Separator) + filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + + // Avoid .foo, _foo, and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + return filepath.SkipDir + } + + name := filepath.ToSlash(path[len(src):]) + if pattern == "std" && strings.Contains(name, ".") { + return filepath.SkipDir + } + if have[name] { + return nil + } + have[name] = true + + _, err = build.ScanDir(path) + if err != nil { + return nil + } + + if match(name) { + pkgs = append(pkgs, name) + } + + // Avoid go/build test data. + // TODO: Move it into a testdata directory. + if path == filepath.Join(build.Path[0].SrcDir(), "go/build") { + return filepath.SkipDir + } + + return nil + }) + } + + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// allPackagesInFS is like allPackages but is passed a pattern +// beginning ./ or ../, meaning it should scan the tree rooted +// at the given directory. There are ... in the pattern too. +func allPackagesInFS(pattern string) []string { + // Find directory to begin the scan. + // Could be smarter but this one optimization + // is enough for now, since ... is usually at the + // end of a path. + i := strings.Index(pattern, "...") + dir, _ := path.Split(pattern[:i]) + + // pattern begins with ./ or ../. + // path.Clean will discard the ./ but not the ../. + // We need to preserve the ./ for pattern matching + // and in the returned import paths. + prefix := "" + if strings.HasPrefix(pattern, "./") { + prefix = "./" + } + match := matchPattern(pattern) + + var pkgs []string + filepath.Walk(dir, func(path string, fi os.FileInfo, err error) error { + if err != nil || !fi.IsDir() { + return nil + } + + // Avoid .foo, _foo, and testdata directory trees. + _, elem := filepath.Split(path) + if strings.HasPrefix(elem, ".") || strings.HasPrefix(elem, "_") || elem == "testdata" { + return filepath.SkipDir + } + + name := prefix + filepath.ToSlash(path) + if !match(name) { + return nil + } + if _, err = build.ScanDir(path); err != nil { + return nil + } + pkgs = append(pkgs, name) + return nil + }) + + if len(pkgs) == 0 { + fmt.Fprintf(os.Stderr, "warning: %q matched no packages\n", pattern) + } + return pkgs +} + +// stringList's arguments should be a sequence of string or []string values. +// stringList flattens them into a single []string. +func stringList(args ...interface{}) []string { + var x []string + for _, arg := range args { + switch arg := arg.(type) { + case []string: + x = append(x, arg...) + case string: + x = append(x, arg) + default: + panic("stringList: invalid argument") + } + } + return x +} + +// isLocalPath returns true if arg is an import path denoting +// a local file system directory. That is, it returns true if the +// path begins with ./ or ../ . +func isLocalPath(arg string) bool { + return arg == "." || arg == ".." || strings.HasPrefix(arg, "./") || strings.HasPrefix(arg, "../") +} diff --git a/src/cmd/go/mkdoc.sh b/src/cmd/go/mkdoc.sh new file mode 100755 index 000000000..7768baeb6 --- /dev/null +++ b/src/cmd/go/mkdoc.sh @@ -0,0 +1,8 @@ +#!/bin/sh +# Copyright 2012 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +go help documentation > doc.go +gofmt -w doc.go + diff --git a/src/cmd/go/pkg.go b/src/cmd/go/pkg.go new file mode 100644 index 000000000..09fa67127 --- /dev/null +++ b/src/cmd/go/pkg.go @@ -0,0 +1,445 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/build" + "os" + "path/filepath" + "sort" + "strings" + "time" +) + +// A Package describes a single package found in a directory. +type Package struct { + // Note: These fields are part of the go command's public API. + // See list.go. It is okay to add fields, but not to change or + // remove existing ones. Keep in sync with list.go + ImportPath string // import path of package in dir + Name string `json:",omitempty"` // package name + Doc string `json:",omitempty"` // package documentation string + Dir string `json:",omitempty"` // directory containing package sources + Version string `json:",omitempty"` // version of installed package (TODO) + Standard bool `json:",omitempty"` // is this package part of the standard Go library? + Stale bool `json:",omitempty"` // would 'go install' do anything for this package? + Incomplete bool `json:",omitempty"` // was there an error loading this package or dependencies? + Error *PackageError `json:",omitempty"` // error loading this package (not dependencies) + + // Source files + GoFiles []string `json:",omitempty"` // .go source files (excluding CgoFiles, TestGoFiles and XTestGoFiles) + TestGoFiles []string `json:",omitempty"` // _test.go source files internal to the package they are testing + XTestGoFiles []string `json:",omitempty"` //_test.go source files external to the package they are testing + CFiles []string `json:",omitempty"` // .c source files + HFiles []string `json:",omitempty"` // .h source files + SFiles []string `json:",omitempty"` // .s source files + CgoFiles []string `json:",omitempty"` // .go sources files that import "C" + CgoCFLAGS []string `json:",omitempty"` // cgo: flags for C compiler + CgoLDFLAGS []string `json:",omitempty"` // cgo: flags for linker + + // Dependency information + Imports []string `json:",omitempty"` // import paths used by this package + Deps []string `json:",omitempty"` // all (recursively) imported dependencies + DepsErrors []*PackageError `json:",omitempty"` // errors loading dependencies + + // Unexported fields are not part of the public API. + t *build.Tree + pkgdir string + info *build.DirInfo + imports []*Package + deps []*Package + gofiles []string // GoFiles+CgoFiles+TestGoFiles+XTestGoFiles files, absolute paths + target string // installed file for this package (may be executable) + fake bool // synthesized package +} + +// A PackageError describes an error loading information about a package. +type PackageError struct { + ImportStack []string // shortest path from package named on command line to this one + Err string // the error itself +} + +func (p *PackageError) Error() string { + return strings.Join(p.ImportStack, "\n\timports ") + ": " + p.Err +} + +// An importStack is a stack of import paths. +type importStack []string + +func (s *importStack) push(p string) { + *s = append(*s, p) +} + +func (s *importStack) pop() { + *s = (*s)[0 : len(*s)-1] +} + +func (s *importStack) copy() []string { + return append([]string{}, *s...) +} + +// shorterThan returns true if sp is shorter than t. +// We use this to record the shortest import sequence +// that leads to a particular package. +func (sp *importStack) shorterThan(t []string) bool { + s := *sp + if len(s) != len(t) { + return len(s) < len(t) + } + // If they are the same length, settle ties using string ordering. + for i := range s { + if s[i] != t[i] { + return s[i] < t[i] + } + } + return false // they are equal +} + +// packageCache is a lookup cache for loadPackage, +// so that if we look up a package multiple times +// we return the same pointer each time. +var packageCache = map[string]*Package{} + +// reloadPackage is like loadPackage but makes sure +// not to use the package cache. +func reloadPackage(arg string, stk *importStack) *Package { + p := packageCache[arg] + if p != nil { + delete(packageCache, p.Dir) + delete(packageCache, p.ImportPath) + } + return loadPackage(arg, stk) +} + +// loadPackage scans directory named by arg, +// which is either an import path or a file system path +// (if the latter, must be rooted or begin with . or ..), +// and returns a *Package describing the package +// found in that directory. +func loadPackage(arg string, stk *importStack) *Package { + stk.push(arg) + defer stk.pop() + + // Check package cache. + if p := packageCache[arg]; p != nil { + return reusePackage(p, stk) + } + + // Find basic information about package path. + t, importPath, err := build.FindTree(arg) + dir := "" + // Maybe it is a standard command. + if err != nil && strings.HasPrefix(arg, "cmd/") { + goroot := build.Path[0] + p := filepath.Join(goroot.Path, "src", arg) + if st, err1 := os.Stat(p); err1 == nil && st.IsDir() { + t = goroot + importPath = arg + dir = p + err = nil + } + } + // Maybe it is a path to a standard command. + if err != nil && (filepath.IsAbs(arg) || isLocalPath(arg)) { + arg, _ := filepath.Abs(arg) + goroot := build.Path[0] + cmd := filepath.Join(goroot.Path, "src", "cmd") + string(filepath.Separator) + if st, err1 := os.Stat(arg); err1 == nil && st.IsDir() && strings.HasPrefix(arg, cmd) { + t = goroot + importPath = filepath.FromSlash(arg[len(cmd):]) + dir = arg + err = nil + } + } + if err != nil { + p := &Package{ + ImportPath: arg, + Error: &PackageError{ + ImportStack: stk.copy(), + Err: err.Error(), + }, + Incomplete: true, + } + packageCache[arg] = p + return p + } + + if dir == "" { + dir = filepath.Join(t.SrcDir(), filepath.FromSlash(importPath)) + } + + // Maybe we know the package by its directory. + if p := packageCache[dir]; p != nil { + packageCache[importPath] = p + return reusePackage(p, stk) + } + + return scanPackage(&buildContext, t, arg, importPath, dir, stk) +} + +func reusePackage(p *Package, stk *importStack) *Package { + // We use p.imports==nil to detect a package that + // is in the midst of its own loadPackage call + // (all the recursion below happens before p.imports gets set). + if p.imports == nil { + if p.Error == nil { + p.Error = &PackageError{ + ImportStack: stk.copy(), + Err: "import loop", + } + } + p.Incomplete = true + } + if p.Error != nil && stk.shorterThan(p.Error.ImportStack) { + p.Error.ImportStack = stk.copy() + } + return p +} + +// firstSentence returns the first sentence of the document text. +// The sentence ends after the first period followed by a space. +// The returned sentence will have no \n \r or \t characters and +// will use only single spaces between words. +func firstSentence(text string) string { + var b []byte + space := true +Loop: + for i := 0; i < len(text); i++ { + switch c := text[i]; c { + case ' ', '\t', '\r', '\n': + if !space { + space = true + if len(b) > 0 && b[len(b)-1] == '.' { + break Loop + } + b = append(b, ' ') + } + default: + space = false + b = append(b, c) + } + } + return string(b) +} + +func scanPackage(ctxt *build.Context, t *build.Tree, arg, importPath, dir string, stk *importStack) *Package { + // Read the files in the directory to learn the structure + // of the package. + p := &Package{ + ImportPath: importPath, + Dir: dir, + Standard: t.Goroot && !strings.Contains(importPath, "."), + t: t, + } + packageCache[dir] = p + packageCache[importPath] = p + + info, err := ctxt.ScanDir(dir) + if err != nil { + p.Error = &PackageError{ + ImportStack: stk.copy(), + Err: err.Error(), + } + p.Incomplete = true + return p + } + + p.info = info + p.Name = info.Package + p.Doc = firstSentence(info.PackageComment.Text()) + p.Imports = info.Imports + p.GoFiles = info.GoFiles + p.TestGoFiles = info.TestGoFiles + p.XTestGoFiles = info.XTestGoFiles + p.CFiles = info.CFiles + p.HFiles = info.HFiles + p.SFiles = info.SFiles + p.CgoFiles = info.CgoFiles + p.CgoCFLAGS = info.CgoCFLAGS + p.CgoLDFLAGS = info.CgoLDFLAGS + + if info.Package == "main" { + _, elem := filepath.Split(importPath) + p.target = filepath.Join(t.BinDir(), elem) + if ctxt.GOOS == "windows" { + p.target += ".exe" + } + } else { + p.target = filepath.Join(t.PkgDir(), filepath.FromSlash(importPath)+".a") + } + + var built time.Time + if fi, err := os.Stat(p.target); err == nil { + built = fi.ModTime() + } + + // Build list of full paths to all Go files in the package, + // for use by commands like go fmt. + for _, f := range info.GoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.CgoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.TestGoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + for _, f := range info.XTestGoFiles { + p.gofiles = append(p.gofiles, filepath.Join(dir, f)) + } + + sort.Strings(p.gofiles) + + srcss := [][]string{ + p.GoFiles, + p.CFiles, + p.HFiles, + p.SFiles, + p.CgoFiles, + } +Stale: + for _, srcs := range srcss { + for _, src := range srcs { + if fi, err := os.Stat(filepath.Join(p.Dir, src)); err != nil || fi.ModTime().After(built) { + //println("STALE", p.ImportPath, "needs", src, err) + p.Stale = true + break Stale + } + } + } + + importPaths := p.Imports + // Packages that use cgo import runtime/cgo implicitly, + // except runtime/cgo itself. + if len(info.CgoFiles) > 0 && (!p.Standard || p.ImportPath != "runtime/cgo") { + importPaths = append(importPaths, "runtime/cgo") + } + // Everything depends on runtime, except runtime and unsafe. + if !p.Standard || (p.ImportPath != "runtime" && p.ImportPath != "unsafe") { + importPaths = append(importPaths, "runtime") + } + + // Record package under both import path and full directory name. + packageCache[dir] = p + packageCache[importPath] = p + + // Build list of imported packages and full dependency list. + imports := make([]*Package, 0, len(p.Imports)) + deps := make(map[string]bool) + for _, path := range importPaths { + if path == "C" { + continue + } + deps[path] = true + p1 := loadPackage(path, stk) + imports = append(imports, p1) + for _, dep := range p1.Deps { + deps[dep] = true + } + if p1.Stale { + p.Stale = true + } + if p1.Incomplete { + p.Incomplete = true + } + // p1.target can be empty only if p1 is not a real package, + // such as package unsafe or the temporary packages + // created during go test. + if !p.Stale && p1.target != "" { + if fi, err := os.Stat(p1.target); err != nil || fi.ModTime().After(built) { + //println("STALE", p.ImportPath, "needs", p1.target, err) + //println("BUILT", built.String(), "VS", fi.ModTime().String()) + p.Stale = true + } + } + } + p.imports = imports + + p.Deps = make([]string, 0, len(deps)) + for dep := range deps { + p.Deps = append(p.Deps, dep) + } + sort.Strings(p.Deps) + for _, dep := range p.Deps { + p1 := packageCache[dep] + if p1 == nil { + panic("impossible: missing entry in package cache for " + dep + " imported by " + p.ImportPath) + } + p.deps = append(p.deps, p1) + if p1.Error != nil { + p.DepsErrors = append(p.DepsErrors, p1.Error) + } + } + + // unsafe is a fake package and is never out-of-date. + if p.Standard && p.ImportPath == "unsafe" { + p.Stale = false + p.target = "" + } + + return p +} + +// packages returns the packages named by the +// command line arguments 'args'. If a named package +// cannot be loaded at all (for example, if the directory does not exist), +// then packages prints an error and does not include that +// package in the results. However, if errors occur trying +// to load dependencies of a named package, the named +// package is still returned, with p.Incomplete = true +// and details in p.DepsErrors. +func packages(args []string) []*Package { + args = importPaths(args) + var pkgs []*Package + var stk importStack + for _, arg := range args { + pkg := loadPackage(arg, &stk) + if pkg.Error != nil { + errorf("%s", pkg.Error) + continue + } + pkgs = append(pkgs, pkg) + } + return pkgs +} + +// packagesAndErrors is like 'packages' but returns a +// *Package for every argument, even the ones that +// cannot be loaded at all. +// The packages that fail to load will have p.Error != nil. +func packagesAndErrors(args []string) []*Package { + args = importPaths(args) + var pkgs []*Package + var stk importStack + for _, arg := range args { + pkgs = append(pkgs, loadPackage(arg, &stk)) + } + return pkgs +} + +// packagesForBuild is like 'packages' but fails if any of +// the packages or their dependencies have errors +// (cannot be built). +func packagesForBuild(args []string) []*Package { + pkgs := packagesAndErrors(args) + printed := map[*PackageError]bool{} + for _, pkg := range pkgs { + if pkg.Error != nil { + errorf("%s", pkg.Error) + } + for _, err := range pkg.DepsErrors { + // Since these are errors in dependencies, + // the same error might show up multiple times, + // once in each package that depends on it. + // Only print each once. + if !printed[err] { + printed[err] = true + errorf("%s", err) + } + } + } + exitIfErrors() + return pkgs +} diff --git a/src/cmd/go/run.go b/src/cmd/go/run.go new file mode 100644 index 000000000..714cd4051 --- /dev/null +++ b/src/cmd/go/run.go @@ -0,0 +1,57 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "strings" + +var cmdRun = &Command{ + UsageLine: "run [-a] [-n] [-x] gofiles... [arguments...]", + Short: "compile and run Go program", + Long: ` +Run compiles and runs the main package comprising the named Go source files. + +The -a flag forces reinstallation of packages that are already up-to-date. +The -n flag prints the commands but does not run them. +The -x flag prints the commands. + +See also: go build. + `, +} + +func init() { + cmdRun.Run = runRun // break init loop + + cmdRun.Flag.BoolVar(&buildA, "a", false, "") + cmdRun.Flag.BoolVar(&buildN, "n", false, "") + cmdRun.Flag.BoolVar(&buildX, "x", false, "") +} + +func runRun(cmd *Command, args []string) { + var b builder + b.init() + i := 0 + for i < len(args) && strings.HasSuffix(args[i], ".go") { + i++ + } + files, cmdArgs := args[:i], args[i:] + p := goFilesPackage(files, "") + p.target = "" // must build - not up to date + a1 := b.action(modeBuild, modeBuild, p) + a := &action{f: (*builder).runProgram, args: cmdArgs, deps: []*action{a1}} + b.do(a) +} + +// runProgram is the action for running a binary that has already +// been compiled. We ignore exit status. +func (b *builder) runProgram(a *action) error { + if buildN || buildX { + b.showcmd("", "%s %s", a.deps[0].target, strings.Join(a.args, " ")) + if buildN { + return nil + } + } + run(a.deps[0].target, a.args) + return nil +} diff --git a/src/cmd/go/script b/src/cmd/go/script new file mode 100755 index 000000000..340a7e824 --- /dev/null +++ b/src/cmd/go/script @@ -0,0 +1,23 @@ +#!/bin/sh + +x() { + echo '--- ' "$@" + "$@" + echo '---' + echo +} + +x go help +x go help build +x go help clean +x go help install +x go help fix +x go help fmt +x go help get +x go help list +x go help test +x go help version +x go help vet +x go help gopath +x go help importpath +x go help remote diff --git a/src/cmd/go/script.txt b/src/cmd/go/script.txt new file mode 100644 index 000000000..a67214658 --- /dev/null +++ b/src/cmd/go/script.txt @@ -0,0 +1,352 @@ +--- go help +usage: go command [arguments] + +go manages Go source code. + +The commands are: + + build compile and install packages and dependencies + clean remove intermediate objects + fix run gofix on packages + fmt run gofmt -w on packages + get download and install packages and dependencies + install install packages and dependencies + list list packages + test test packages + version print Go version + vet run govet on packages + +Use "go help [command]" for more information about a command. + +Additional help topics: + + gopath GOPATH environment variable + importpath description of import paths + remote remote import path syntax + +Use "go help [topic]" for more information about that topic. + +--- + +--- go help build +usage: go build [-n] [-v] [importpath...] + +Build compiles the packages named by the import paths, +along with their dependencies, but it does not install the results. + +The -n flag prints the commands but does not run them. +The -v flag prints the commands. + +For more about import paths, see 'go help importpath'. + +See also: go install, go get, go clean. +--- + +--- go help clean +usage: go clean [-nuke] [importpath...] + +Clean removes intermediate object files generated during +the compilation of the packages named by the import paths, +but by default it does not remove the installed package binaries. + +The -nuke flag causes clean to remove the installed package binaries too. + +TODO: Clean does not clean dependencies of the packages. + +For more about import paths, see 'go help importpath'. +--- + +--- go help install +usage: go install [-n] [-v] [importpath...] + +Install compiles and installs the packages named by the import paths, +along with their dependencies. + +The -n flag prints the commands but does not run them. +The -v flag prints the commands. + +For more about import paths, see 'go help importpath'. + +See also: go build, go get, go clean. +--- + +--- go help fix +usage: go fix [importpath...] + +Fix runs the gofix command on the packages named by the import paths. + +For more about gofix, see 'godoc gofix'. +For more about import paths, see 'go help importpath'. + +To run gofix with specific options, run gofix itself. + +See also: go fmt, go vet. +--- + +--- go help fmt +usage: go fmt [importpath...] + +Fmt runs the command 'gofmt -w' on the packages named by the import paths. + +For more about gofmt, see 'godoc gofmt'. +For more about import paths, see 'go help importpath'. + +To run gofmt with specific options, run gofmt itself. + +See also: go fix, go vet. +--- + +--- go help get +usage: go get [importpath...] + +Get downloads and installs the packages named by the import paths, +along with their dependencies. + +After downloading the code, 'go get' looks for a tag beginning +with "go." that corresponds to the local Go version. +For Go "release.r58" it looks for a tag named "go.r58". +For "weekly.2011-06-03" it looks for "go.weekly.2011-06-03". +If the specific "go.X" tag is not found, it uses the latest earlier +version it can find. Otherwise, it uses the default version for +the version control system: HEAD for git, tip for Mercurial, +and so on. + +TODO: Explain versions better. + +For more about import paths, see 'go help importpath'. + +For more about how 'go get' finds source code to +download, see 'go help remote'. + +See also: go build, go install, go clean. +--- + +--- go help list +usage: go list [-f format] [-json] [importpath...] + +List lists the packages named by the import paths. + +The default output shows the package name and file system location: + + books /home/you/src/google-api-go-client.googlecode.com/hg/books/v1 + oauth /home/you/src/goauth2.googlecode.com/hg/oauth + sqlite /home/you/src/gosqlite.googlecode.com/hg/sqlite + +The -f flag specifies an alternate format for the list, +using the syntax of package template. The default output +is equivalent to -f '{{.Name}} {{.Dir}}' The struct +being passed to the template is: + + type Package struct { + Name string // package name + Doc string // package documentation string + GoFiles []string // names of Go source files in package + ImportPath string // import path denoting package + Imports []string // import paths used by this package + Deps []string // all (recursively) imported dependencies + Dir string // directory containing package sources + Version string // version of installed package + } + +The -json flag causes the package data to be printed in JSON format. + +For more about import paths, see 'go help importpath'. +--- + +--- go help test +usage: go test [importpath...] + +Test runs gotest to test the packages named by the import paths. +It prints a summary of the test results in the format: + + test archive/tar + FAIL archive/zip + test compress/gzip + ... + +followed by gotest output for each failed package. + +For more about import paths, see 'go help importpath'. + +See also: go build, go compile, go vet. +--- + +--- go help version +usage: go version + +Version prints the Go version, as reported by runtime.Version. +--- + +--- go help vet +usage: go vet [importpath...] + +Vet runs the govet command on the packages named by the import paths. + +For more about govet, see 'godoc govet'. +For more about import paths, see 'go help importpath'. + +To run govet with specific options, run govet itself. + +See also: go fmt, go fix. +--- + +--- go help gopath +The GOPATH environment variable lists places to look for Go code. +On Unix, the value is a colon-separated string. +On Windows, the value is a semicolon-separated string. +On Plan 9, the value is a list. + +GOPATH must be set to build and install packages outside the +standard Go tree. + +Each directory listed in GOPATH must have a prescribed structure: + +The src/ directory holds source code. The path below 'src' +determines the import path or executable name. + +The pkg/ directory holds installed package objects. +As in the Go tree, each target operating system and +architecture pair has its own subdirectory of pkg +(pkg/GOOS_GOARCH). + +If DIR is a directory listed in the GOPATH, a package with +source in DIR/src/foo/bar can be imported as "foo/bar" and +has its compiled form installed to "DIR/pkg/GOOS_GOARCH/foo/bar.a". + +The bin/ directory holds compiled commands. +Each command is named for its source directory, but only +the final element, not the entire path. That is, the +command with source in DIR/src/foo/quux is installed into +DIR/bin/quux, not DIR/bin/foo/quux. The foo/ is stripped +so that you can add DIR/bin to your PATH to get at the +installed commands. + +Here's an example directory layout: + + GOPATH=/home/user/gocode + + /home/user/gocode/ + src/ + foo/ + bar/ (go code in package bar) + x.go + quux/ (go code in package main) + y.go + bin/ + quux (installed command) + pkg/ + linux_amd64/ + foo/ + bar.a (installed package object) + +Go searches each directory listed in GOPATH to find source code, +but new packages are always downloaded into the first directory +in the list. +--- + +--- go help importpath +Many commands apply to a set of packages named by import paths: + + go action [importpath...] + +An import path that is a rooted path or that begins with +a . or .. element is interpreted as a file system path and +denotes the package in that directory. + +Otherwise, the import path P denotes the package found in +the directory DIR/src/P for some DIR listed in the GOPATH +environment variable (see 'go help gopath'). + +If no import paths are given, the action applies to the +package in the current directory. + +The special import path "all" expands to all package directories +found in all the GOPATH trees. For example, 'go list all' +lists all the packages on the local system. + +An import path can also name a package to be downloaded from +a remote repository. Run 'go help remote' for details. + +Every package in a program must have a unique import path. +By convention, this is arranged by starting each path with a +unique prefix that belongs to you. For example, paths used +internally at Google all begin with 'google', and paths +denoting remote repositories begin with the path to the code, +such as 'project.googlecode.com/'. +--- + +--- go help remote +An import path (see 'go help importpath') denotes a package +stored in the local file system. Certain import paths also +describe how to obtain the source code for the package using +a revision control system. + +A few common code hosting sites have special syntax: + + BitBucket (Mercurial) + + import "bitbucket.org/user/project" + import "bitbucket.org/user/project/sub/directory" + + GitHub (Git) + + import "github.com/user/project" + import "github.com/user/project/sub/directory" + + Google Code Project Hosting (Git, Mercurial, Subversion) + + import "project.googlecode.com/git" + import "project.googlecode.com/git/sub/directory" + + import "project.googlecode.com/hg" + import "project.googlecode.com/hg/sub/directory" + + import "project.googlecode.com/svn/trunk" + import "project.googlecode.com/svn/trunk/sub/directory" + + Launchpad (Bazaar) + + import "launchpad.net/project" + import "launchpad.net/project/series" + import "launchpad.net/project/series/sub/directory" + + import "launchpad.net/~user/project/branch" + import "launchpad.net/~user/project/branch/sub/directory" + +For code hosted on other servers, an import path of the form + + repository.vcs/path + +specifies the given repository, with or without the .vcs suffix, +using the named version control system, and then the path inside +that repository. The supported version control systems are: + + Bazaar .bzr + Git .git + Mercurial .hg + Subversion .svn + +For example, + + import "example.org/user/foo.hg" + +denotes the root directory of the Mercurial repository at +example.org/user/foo or foo.hg, and + + import "example.org/repo.git/foo/bar" + +denotes the foo/bar directory of the Git repository at +example.com/repo or repo.git. + +When a version control system supports multiple protocols, +each is tried in turn when downloading. For example, a Git +download tries git://, then https://, then http://. + +New downloaded packages are written to the first directory +listed in the GOPATH environment variable (see 'go help gopath'). + +The go command attempts to download the version of the +package appropriate for the Go release being used. +Run 'go help install' for more. +--- + diff --git a/src/cmd/go/test.go b/src/cmd/go/test.go new file mode 100644 index 000000000..6cd49fe5a --- /dev/null +++ b/src/cmd/go/test.go @@ -0,0 +1,681 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "os" + "os/exec" + "path/filepath" + "strings" + "text/template" + "time" + "unicode" + "unicode/utf8" +) + +// Break init loop. +func init() { + cmdTest.Run = runTest +} + +var cmdTest = &Command{ + CustomFlags: true, + UsageLine: "test [-c] [-file a.go -file b.go ...] [-p n] [-x] [importpath...] [flags for test binary]", + Short: "test packages", + Long: ` +'Go test' automates testing the packages named by the import paths. +It prints a summary of the test results in the format: + + ok archive/tar 0.011s + FAIL archive/zip 0.022s + ok compress/gzip 0.033s + ... + +followed by detailed output for each failed package. + +'Go test' recompiles each package along with any files with names matching +the file pattern "*_test.go". These additional files can contain test functions, +benchmark functions, and example functions. See 'go help testfunc' for more. + +By default, go test needs no arguments. It compiles and tests the package +with source in the current directory, including tests, and runs the tests. +If file names are given (with flag -file=test.go, one per extra test source file), +only those test files are added to the package. (The non-test files are always +compiled.) + +The package is built in a temporary directory so it does not interfere with the +non-test installation. + +See 'go help testflag' for details about flags handled by 'go test' +and the test binary. + +See 'go help importpath' for more about import paths. + +See also: go build, go vet. + `, +} + +var helpTestflag = &Command{ + UsageLine: "testflag", + Short: "description of testing flags", + Long: ` +The 'go test' command takes both flags that apply to 'go test' itself +and flags that apply to the resulting test binary. + +The flags handled by 'go test' are: + + -c Compile the test binary to test.out but do not run it. + + -file a.go + Use only the tests in the source file a.go. + Multiple -file flags may be provided. + + -p n + Compile and test up to n packages in parallel. + The default value is the number of CPUs available. + + -x Print each subcommand go test executes. + +The resulting test binary, called test.out, has its own flags: + + -test.v + Verbose output: log all tests as they are run. + + -test.run pattern + Run only those tests matching the regular expression. + + -test.bench pattern + Run benchmarks matching the regular expression. + By default, no benchmarks run. + + -test.cpuprofile cpu.out + Write a CPU profile to the specified file before exiting. + + -test.memprofile mem.out + Write a memory profile to the specified file when all tests + are complete. + + -test.memprofilerate n + Enable more precise (and expensive) memory profiles by setting + runtime.MemProfileRate. See 'godoc runtime MemProfileRate'. + To profile all memory allocations, use -test.memprofilerate=1 + and set the environment variable GOGC=off to disable the + garbage collector, provided the test can run in the available + memory without garbage collection. + + -test.parallel n + Allow parallel execution of test functions that call t.Parallel. + The value of this flag is the maximum number of tests to run + simultaneously; by default, it is set to the value of GOMAXPROCS. + + -test.short + Tell long-running tests to shorten their run time. + It is off by default but set during all.bash so that installing + the Go tree can run a sanity check but not spend time running + exhaustive tests. + + -test.timeout t + If a test runs longer than t, panic. + + -test.benchtime n + Run enough iterations of each benchmark to take n seconds. + The default is 1 second. + + -test.cpu 1,2,4 + Specify a list of GOMAXPROCS values for which the tests or + benchmarks should be executed. The default is the current value + of GOMAXPROCS. + +For convenience, each of these -test.X flags of the test binary is +also available as the flag -X in 'go test' itself. Flags not listed +here are passed through unaltered. For instance, the command + + go test -x -v -cpuprofile=prof.out -dir=testdata -update -file x_test.go + +will compile the test binary using x_test.go and then run it as + + test.out -test.v -test.cpuprofile=prof.out -dir=testdata -update + `, +} + +var helpTestfunc = &Command{ + UsageLine: "testfunc", + Short: "description of testing functions", + Long: ` +The 'go test' command expects to find test, benchmark, and example functions +in the "*_test.go" files corresponding to the package under test. + +A test function is one named TestXXX (where XXX is any alphanumeric string +not starting with a lower case letter) and should have the signature, + + func TestXXX(t *testing.T) { ... } + +A benchmark function is one named BenchmarkXXX and should have the signature, + + func BenchmarkXXX(b *testing.B) { ... } + +An example function is similar to a test function but, instead of using *testing.T +to report success or failure, prints output to os.Stdout and os.Stderr. +That output is compared against the function's doc comment. +An example without a doc comment is compiled but not executed. + +Godoc displays the body of ExampleXXX to demonstrate the use +of the function, constant, or variable XXX. An example of a method M with +receiver type T or *T is named ExampleT_M. There may be multiple examples +for a given function, constant, or variable, distinguished by a trailing _xxx, +where xxx is a suffix not beginning with an upper case letter. + +Here is an example of an example: + + // The output of this example function. + func ExamplePrintln() { + Println("The output of this example function.") + } + +See the documentation of the testing package for more information. + `, +} + +var ( + testC bool // -c flag + testP int // -p flag + testX bool // -x flag + testV bool // -v flag + testFiles []string // -file flag(s) TODO: not respected + testArgs []string + testShowPass bool // whether to display passing output + testBench bool +) + +func runTest(cmd *Command, args []string) { + var pkgArgs []string + pkgArgs, testArgs = testFlags(args) + + // show test PASS output when no packages + // are listed (implicitly current directory: "go test") + // or when the -v flag has been given. + testShowPass = len(pkgArgs) == 0 || testV + + pkgs := packagesForBuild(pkgArgs) + if len(pkgs) == 0 { + fatalf("no packages to test") + } + + if testC && len(pkgs) != 1 { + fatalf("cannot use -c flag with multiple packages") + } + + buildX = testX + if testP > 0 { + buildP = testP + } + + var b builder + b.init() + + var builds, runs, prints []*action + + // Prepare build + run + print actions for all packages being tested. + for _, p := range pkgs { + buildTest, runTest, printTest, err := b.test(p) + if err != nil { + errorf("%s", err) + continue + } + builds = append(builds, buildTest) + runs = append(runs, runTest) + prints = append(prints, printTest) + } + + // Ultimately the goal is to print the output. + root := &action{deps: prints} + + // Force the printing of results to happen in order, + // one at a time. + for i, a := range prints { + if i > 0 { + a.deps = append(a.deps, prints[i-1]) + } + } + + // If we are benchmarking, force everything to + // happen in serial. Could instead allow all the + // builds to run before any benchmarks start, + // but try this for now. + if testBench { + for i, a := range builds { + if i > 0 { + // Make build of test i depend on + // completing the run of test i-1. + a.deps = append(a.deps, runs[i-1]) + } + } + } + + // If we are building any out-of-date packages other + // than those under test, warn. + okBuild := map[*Package]bool{} + for _, p := range pkgs { + okBuild[p] = true + } + + warned := false + for _, a := range actionList(root) { + if a.p != nil && a.f != nil && !okBuild[a.p] && !a.p.fake { + okBuild[a.p] = true // don't warn again + if !warned { + fmt.Fprintf(os.Stderr, "warning: building out-of-date packages:\n") + warned = true + } + fmt.Fprintf(os.Stderr, "\t%s\n", a.p.ImportPath) + } + } + if warned { + fmt.Fprintf(os.Stderr, "installing these packages with 'go install' will speed future tests.\n\n") + } + + b.do(root) +} + +func (b *builder) test(p *Package) (buildAction, runAction, printAction *action, err error) { + if len(p.info.TestGoFiles)+len(p.info.XTestGoFiles) == 0 { + build := &action{p: p} + run := &action{p: p} + print := &action{f: (*builder).notest, p: p, deps: []*action{build}} + return build, run, print, nil + } + + // Build Package structs describing: + // ptest - package + test files + // pxtest - package of external test files + // pmain - test.out binary + var ptest, pxtest, pmain *Package + + // go/build does not distinguish the dependencies used + // by the TestGoFiles from the dependencies used by the + // XTestGoFiles, so we build one list and use it for both + // ptest and pxtest. No harm done. + var imports []*Package + var stk importStack + stk.push(p.ImportPath + "_test") + for _, path := range p.info.TestImports { + p1 := loadPackage(path, &stk) + if p1.Error != nil { + return nil, nil, nil, p1.Error + } + imports = append(imports, p1) + } + stk.pop() + + // The ptest package needs to be importable under the + // same import path that p has, but we cannot put it in + // the usual place in the temporary tree, because then + // other tests will see it as the real package. + // Instead we make a _test directory under the import path + // and then repeat the import path there. We tell the + // compiler and linker to look in that _test directory first. + // + // That is, if the package under test is unicode/utf8, + // then the normal place to write the package archive is + // $WORK/unicode/utf8.a, but we write the test package archive to + // $WORK/unicode/utf8/_test/unicode/utf8.a. + // We write the external test package archive to + // $WORK/unicode/utf8/_test/unicode/utf8_test.a. + testDir := filepath.Join(b.work, filepath.FromSlash(p.ImportPath+"/_test")) + ptestObj := filepath.Join(testDir, filepath.FromSlash(p.ImportPath+".a")) + pxtestObj := filepath.Join(testDir, filepath.FromSlash(p.ImportPath+"_test.a")) + + // Create the directory for the .a files. + ptestDir, _ := filepath.Split(ptestObj) + if err := b.mkdir(ptestDir); err != nil { + return nil, nil, nil, err + } + if err := writeTestmain(filepath.Join(testDir, "_testmain.go"), p); err != nil { + return nil, nil, nil, err + } + + // Test package. + if len(p.info.TestGoFiles) > 0 { + ptest = new(Package) + *ptest = *p + ptest.GoFiles = nil + ptest.GoFiles = append(ptest.GoFiles, p.GoFiles...) + ptest.GoFiles = append(ptest.GoFiles, p.info.TestGoFiles...) + ptest.target = "" + ptest.Imports = stringList(p.info.Imports, p.info.TestImports) + ptest.imports = append(append([]*Package{}, p.imports...), imports...) + ptest.pkgdir = testDir + ptest.fake = true + a := b.action(modeBuild, modeBuild, ptest) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = ptestObj + a.target = ptestObj + a.link = false + } else { + ptest = p + } + + // External test package. + if len(p.info.XTestGoFiles) > 0 { + pxtest = &Package{ + Name: p.Name + "_test", + ImportPath: p.ImportPath + "_test", + Dir: p.Dir, + GoFiles: p.info.XTestGoFiles, + Imports: p.info.TestImports, + t: p.t, + info: &build.DirInfo{}, + imports: imports, + pkgdir: testDir, + fake: true, + } + pxtest.imports = append(pxtest.imports, ptest) + a := b.action(modeBuild, modeBuild, pxtest) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = pxtestObj + a.target = pxtestObj + } + + // Action for building test.out. + pmain = &Package{ + Name: "main", + Dir: testDir, + GoFiles: []string{"_testmain.go"}, + t: p.t, + info: &build.DirInfo{}, + imports: []*Package{ptest}, + fake: true, + } + if pxtest != nil { + pmain.imports = append(pmain.imports, pxtest) + } + + // The generated main also imports testing and regexp. + stk.push("testmain") + ptesting := loadPackage("testing", &stk) + if ptesting.Error != nil { + return nil, nil, nil, ptesting.Error + } + pregexp := loadPackage("regexp", &stk) + if pregexp.Error != nil { + return nil, nil, nil, pregexp.Error + } + pmain.imports = append(pmain.imports, ptesting, pregexp) + + a := b.action(modeBuild, modeBuild, pmain) + a.objdir = testDir + string(filepath.Separator) + a.objpkg = filepath.Join(testDir, "main.a") + a.target = filepath.Join(testDir, "test.out") + b.exe + pmainAction := a + + if testC { + // -c flag: create action to copy binary to ./test.out. + runAction = &action{ + f: (*builder).install, + deps: []*action{pmainAction}, + p: pmain, + target: "test.out" + b.exe, + } + printAction = &action{p: p, deps: []*action{runAction}} // nop + } else { + // run test + runAction = &action{ + f: (*builder).runTest, + deps: []*action{pmainAction}, + p: p, + ignoreFail: true, + } + printAction = &action{ + f: (*builder).printTest, + deps: []*action{runAction}, + p: p, + } + } + + return pmainAction, runAction, printAction, nil +} + +// runTest is the action for running a test binary. +func (b *builder) runTest(a *action) error { + args := stringList(a.deps[0].target, testArgs) + a.testOutput = new(bytes.Buffer) + + if buildN || buildX { + b.showcmd("", "%s", strings.Join(args, " ")) + if buildN { + return nil + } + } + + if a.failed { + // We were unable to build the binary. + a.failed = false + fmt.Fprintf(a.testOutput, "FAIL\t%s [build failed]\n", a.p.ImportPath) + exitStatus = 1 + return nil + } + + cmd := exec.Command(args[0], args[1:]...) + cmd.Dir = a.p.Dir + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + + t0 := time.Now() + err := cmd.Start() + + // This is a last-ditch deadline to detect and + // stop wedged test binaries, to keep the builders + // running. + const deadline = 10 * time.Minute + + tick := time.NewTimer(deadline) + if err == nil { + done := make(chan error) + go func() { + done <- cmd.Wait() + }() + select { + case err = <-done: + // ok + case <-tick.C: + cmd.Process.Kill() + err = <-done + fmt.Fprintf(&buf, "*** Test killed: ran too long.\n") + } + tick.Stop() + } + out := buf.Bytes() + t1 := time.Now() + t := fmt.Sprintf("%.3fs", t1.Sub(t0).Seconds()) + if err == nil { + fmt.Fprintf(a.testOutput, "ok \t%s\t%s\n", a.p.ImportPath, t) + if testShowPass { + a.testOutput.Write(out) + } + return nil + } + + fmt.Fprintf(a.testOutput, "FAIL\t%s\t%s\n", a.p.ImportPath, t) + exitStatus = 1 + if len(out) > 0 { + a.testOutput.Write(out) + // assume printing the test binary's exit status is superfluous + } else { + fmt.Fprintf(a.testOutput, "%s\n", err) + } + return nil +} + +// printTest is the action for printing a test result. +func (b *builder) printTest(a *action) error { + run := a.deps[0] + os.Stdout.Write(run.testOutput.Bytes()) + run.testOutput = nil + return nil +} + +// notest is the action for testing a package with no test files. +func (b *builder) notest(a *action) error { + fmt.Printf("? \t%s\t[no test files]\n", a.p.ImportPath) + return nil +} + +// isTest tells whether name looks like a test (or benchmark, according to prefix). +// It is a Test (say) if there is a character after Test that is not a lower-case letter. +// We don't want TesticularCancer. +func isTest(name, prefix string) bool { + if !strings.HasPrefix(name, prefix) { + return false + } + if len(name) == len(prefix) { // "Test" is ok + return true + } + rune, _ := utf8.DecodeRuneInString(name[len(prefix):]) + return !unicode.IsLower(rune) +} + +// writeTestmain writes the _testmain.go file for package p to +// the file named out. +func writeTestmain(out string, p *Package) error { + t := &testFuncs{ + Package: p, + Info: p.info, + } + for _, file := range p.info.TestGoFiles { + if err := t.load(filepath.Join(p.Dir, file), "_test", &t.NeedTest); err != nil { + return err + } + } + for _, file := range p.info.XTestGoFiles { + if err := t.load(filepath.Join(p.Dir, file), "_xtest", &t.NeedXtest); err != nil { + return err + } + } + + f, err := os.Create(out) + if err != nil { + return err + } + defer f.Close() + + if err := testmainTmpl.Execute(f, t); err != nil { + return err + } + + return nil +} + +type testFuncs struct { + Tests []testFunc + Benchmarks []testFunc + Examples []testFunc + Package *Package + Info *build.DirInfo + NeedTest bool + NeedXtest bool +} + +type testFunc struct { + Package string // imported package name (_test or _xtest) + Name string // function name + Output string // output, for examples +} + +var testFileSet = token.NewFileSet() + +func (t *testFuncs) load(filename, pkg string, seen *bool) error { + f, err := parser.ParseFile(testFileSet, filename, nil, parser.ParseComments) + if err != nil { + return err + } + for _, d := range f.Decls { + n, ok := d.(*ast.FuncDecl) + if !ok { + continue + } + if n.Recv != nil { + continue + } + name := n.Name.String() + switch { + case isTest(name, "Test"): + t.Tests = append(t.Tests, testFunc{pkg, name, ""}) + *seen = true + case isTest(name, "Benchmark"): + t.Benchmarks = append(t.Benchmarks, testFunc{pkg, name, ""}) + *seen = true + case isTest(name, "Example"): + output := n.Doc.Text() + if output == "" { + // Don't run examples with no output. + continue + } + t.Examples = append(t.Examples, testFunc{pkg, name, output}) + *seen = true + } + } + + return nil +} + +var testmainTmpl = template.Must(template.New("main").Parse(` +package main + +import ( + "regexp" + "testing" + +{{if .NeedTest}} + _test {{.Package.ImportPath | printf "%q"}} +{{end}} +{{if .NeedXtest}} + _xtest {{.Package.ImportPath | printf "%s_test" | printf "%q"}} +{{end}} +) + +var tests = []testing.InternalTest{ +{{range .Tests}} + {"{{.Name}}", {{.Package}}.{{.Name}}}, +{{end}} +} + +var benchmarks = []testing.InternalBenchmark{ +{{range .Benchmarks}} + {"{{.Name}}", {{.Package}}.{{.Name}}}, +{{end}} +} + +var examples = []testing.InternalExample{ +{{range .Examples}} + {"{{.Name}}", {{.Package}}.{{.Name}}, {{.Output | printf "%q"}}}, +{{end}} +} + +var matchPat string +var matchRe *regexp.Regexp + +func matchString(pat, str string) (result bool, err error) { + if matchRe == nil || matchPat != pat { + matchPat = pat + matchRe, err = regexp.Compile(matchPat) + if err != nil { + return + } + } + return matchRe.MatchString(str), nil +} + +func main() { + testing.Main(matchString, tests, benchmarks, examples) +} + +`)) diff --git a/src/cmd/go/testflag.go b/src/cmd/go/testflag.go new file mode 100644 index 000000000..a3cacd657 --- /dev/null +++ b/src/cmd/go/testflag.go @@ -0,0 +1,215 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// The flag handling part of go test is large and distracting. +// We can't use the flag package because some of the flags from +// our command line are for us, and some are for 6.out, and +// some are for both. + +var usageMessage = `Usage of go test: + -c=false: compile but do not run the test binary + -file=file_test.go: specify file to use for tests; + use multiple times for multiple files + -p=n: build and test up to n packages in parallel + -x=false: print command lines as they are executed + + // These flags can be passed with or without a "test." prefix: -v or -test.v. + -bench="": passes -test.bench to test + -benchtime=1: passes -test.benchtime to test + -cpu="": passes -test.cpu to test + -cpuprofile="": passes -test.cpuprofile to test + -memprofile="": passes -test.memprofile to test + -memprofilerate=0: passes -test.memprofilerate to test + -parallel=0: passes -test.parallel to test + -run="": passes -test.run to test + -short=false: passes -test.short to test + -timeout=0: passes -test.timeout to test + -v=false: passes -test.v to test +` + +// usage prints a usage message and exits. +func testUsage() { + fmt.Fprint(os.Stderr, usageMessage) + exitStatus = 2 + exit() +} + +// testFlagSpec defines a flag we know about. +type testFlagSpec struct { + name string + isBool bool + passToTest bool // pass to Test + multiOK bool // OK to have multiple instances + present bool // flag has been seen +} + +// testFlagDefn is the set of flags we process. +var testFlagDefn = []*testFlagSpec{ + // local. + {name: "c", isBool: true}, + {name: "file", multiOK: true}, + {name: "p"}, + {name: "x", isBool: true}, + + // passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v. + {name: "bench", passToTest: true}, + {name: "benchtime", passToTest: true}, + {name: "cpu", passToTest: true}, + {name: "cpuprofile", passToTest: true}, + {name: "memprofile", passToTest: true}, + {name: "memprofilerate", passToTest: true}, + {name: "parallel", passToTest: true}, + {name: "run", passToTest: true}, + {name: "short", isBool: true, passToTest: true}, + {name: "timeout", passToTest: true}, + {name: "v", isBool: true, passToTest: true}, +} + +// testFlags processes the command line, grabbing -x and -c, rewriting known flags +// to have "test" before them, and reading the command line for the 6.out. +// Unfortunately for us, we need to do our own flag processing because go test +// grabs some flags but otherwise its command line is just a holding place for +// test.out's arguments. +// We allow known flags both before and after the package name list, +// to allow both +// go test fmt -custom-flag-for-fmt-test +// go test -x math +func testFlags(args []string) (packageNames, passToTest []string) { + inPkg := false + for i := 0; i < len(args); i++ { + if !strings.HasPrefix(args[i], "-") { + if !inPkg && packageNames == nil { + // First package name we've seen. + inPkg = true + } + if inPkg { + packageNames = append(packageNames, args[i]) + continue + } + } + + if inPkg { + // Found an argument beginning with "-"; end of package list. + inPkg = false + } + + f, value, extraWord := testFlag(args, i) + if f == nil { + // This is a flag we do not know; we must assume + // that any args we see after this might be flag + // arguments, not package names. + inPkg = false + if packageNames == nil { + // make non-nil: we have seen the empty package list + packageNames = []string{} + } + passToTest = append(passToTest, args[i]) + continue + } + switch f.name { + case "c": + setBoolFlag(&testC, value) + case "p": + setIntFlag(&testP, value) + case "x": + setBoolFlag(&testX, value) + case "v": + setBoolFlag(&testV, value) + case "file": + testFiles = append(testFiles, value) + case "bench": + // record that we saw the flag; don't care about the value + testBench = true + } + if extraWord { + i++ + } + if f.passToTest { + passToTest = append(passToTest, "-test."+f.name+"="+value) + } + } + return +} + +// testFlag sees if argument i is a known flag and returns its definition, value, and whether it consumed an extra word. +func testFlag(args []string, i int) (f *testFlagSpec, value string, extra bool) { + arg := args[i] + if strings.HasPrefix(arg, "--") { // reduce two minuses to one + arg = arg[1:] + } + switch arg { + case "-?", "-h", "-help": + usage() + } + if arg == "" || arg[0] != '-' { + return + } + name := arg[1:] + // If there's already "test.", drop it for now. + if strings.HasPrefix(name, "test.") { + name = name[5:] + } + equals := strings.Index(name, "=") + if equals >= 0 { + value = name[equals+1:] + name = name[:equals] + } + for _, f = range testFlagDefn { + if name == f.name { + // Booleans are special because they have modes -x, -x=true, -x=false. + if f.isBool { + if equals < 0 { // otherwise, it's been set and will be verified in setBoolFlag + value = "true" + } else { + // verify it parses + setBoolFlag(new(bool), value) + } + } else { // Non-booleans must have a value. + extra = equals < 0 + if extra { + if i+1 >= len(args) { + usage() + } + value = args[i+1] + } + } + if f.present && !f.multiOK { + usage() + } + f.present = true + return + } + } + f = nil + return +} + +// setBoolFlag sets the addressed boolean to the value. +func setBoolFlag(flag *bool, value string) { + x, err := strconv.ParseBool(value) + if err != nil { + fmt.Fprintf(os.Stderr, "go test: illegal bool flag value %s\n", value) + usage() + } + *flag = x +} + +// setIntFlag sets the addressed integer to the value. +func setIntFlag(flag *int, value string) { + x, err := strconv.Atoi(value) + if err != nil { + fmt.Fprintf(os.Stderr, "go test: illegal int flag value %s\n", value) + usage() + } + *flag = x +} diff --git a/src/cmd/go/vcs.go b/src/cmd/go/vcs.go new file mode 100644 index 000000000..da35048d5 --- /dev/null +++ b/src/cmd/go/vcs.go @@ -0,0 +1,425 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "os/exec" + "regexp" + "strings" +) + +// A vcsCmd describes how to use a version control system +// like Mercurial, Git, or Subversion. +type vcsCmd struct { + name string + cmd string // name of binary to invoke command + + createCmd string // command to download a fresh copy of a repository + downloadCmd string // command to download updates into an existing repository + + tagCmd []tagCmd // commands to list tags + tagDefault string // default tag to use + tagSyncCmd string // command to sync to specific tag +} + +// A tagCmd describes a command to list available tags +// that can be passed to tagSyncCmd. +type tagCmd struct { + cmd string // command to list tags + pattern string // regexp to extract tags from list +} + +// vcsList lists the known version control systems +var vcsList = []*vcsCmd{ + vcsHg, + vcsGit, + vcsSvn, + vcsBzr, +} + +// vcsByCmd returns the version control system for the given +// command name (hg, git, svn, bzr). +func vcsByCmd(cmd string) *vcsCmd { + for _, vcs := range vcsList { + if vcs.cmd == cmd { + return vcs + } + } + return nil +} + +// vcsHg describes how to use Mercurial. +var vcsHg = &vcsCmd{ + name: "Mercurial", + cmd: "hg", + + createCmd: "clone -U {repo} {dir}", + downloadCmd: "pull", + + // We allow both tag and branch names as 'tags' + // for selecting a version. This lets people have + // a go.release.r60 branch and a go.1 branch + // and make changes in both, without constantly + // editing .hgtags. + tagCmd: []tagCmd{ + {"tags", `^(\S+)`}, + {"branches", `^(\S+)`}, + }, + tagDefault: "default", + tagSyncCmd: "update -r {tag}", +} + +// vcsGit describes how to use Git. +var vcsGit = &vcsCmd{ + name: "Git", + cmd: "git", + + createCmd: "clone {repo} {dir}", + downloadCmd: "fetch", + + tagCmd: []tagCmd{{"tag", `^(\S+)$`}}, + tagDefault: "master", + tagSyncCmd: "checkout {tag}", +} + +// vcsBzr describes how to use Bazaar. +var vcsBzr = &vcsCmd{ + name: "Bazaar", + cmd: "bzr", + + createCmd: "branch {repo} {dir}", + + // Without --overwrite bzr will not pull tags that changed. + // Replace by --overwrite-tags after http://pad.lv/681792 goes in. + downloadCmd: "pull --overwrite", + + tagCmd: []tagCmd{{"tags", `^(\S+)`}}, + tagDefault: "revno:-1", + tagSyncCmd: "update -r {tag}", +} + +// vcsSvn describes how to use Subversion. +var vcsSvn = &vcsCmd{ + name: "Subversion", + cmd: "svn", + + createCmd: "checkout {repo} {dir}", + downloadCmd: "update", + + // There is no tag command in subversion. + // The branch information is all in the path names. +} + +func (v *vcsCmd) String() string { + return v.name +} + +// run runs the command line cmd in the given directory. +// keyval is a list of key, value pairs. run expands +// instances of {key} in cmd into value, but only after +// splitting cmd into individual arguments. +// If an error occurs, run prints the command line and the +// command's combined stdout+stderr to standard error. +// Otherwise run discards the command's output. +func (v *vcsCmd) run(dir string, cmd string, keyval ...string) error { + _, err := v.run1(dir, false, cmd, keyval) + return err +} + +// runOutput is like run but returns the output of the command. +func (v *vcsCmd) runOutput(dir string, cmd string, keyval ...string) ([]byte, error) { + return v.run1(dir, true, cmd, keyval) +} + +// run1 is the generalized implementation of run and runOutput. +func (v *vcsCmd) run1(dir string, output bool, cmdline string, keyval []string) ([]byte, error) { + m := make(map[string]string) + for i := 0; i < len(keyval); i += 2 { + m[keyval[i]] = keyval[i+1] + } + args := strings.Fields(cmdline) + for i, arg := range args { + args[i] = expand(m, arg) + } + + cmd := exec.Command(v.cmd, args...) + cmd.Dir = dir + if buildX { + fmt.Printf("cd %s\n", dir) + fmt.Printf("%s %s\n", v.cmd, strings.Join(args, " ")) + } + var buf bytes.Buffer + cmd.Stdout = &buf + cmd.Stderr = &buf + out := buf.Bytes() + err := cmd.Run() + if err != nil { + fmt.Fprintf(os.Stderr, "# cd %s; %s %s\n", dir, v.cmd, strings.Join(args, " ")) + os.Stderr.Write(out) + return nil, err + } + return out, nil +} + +// create creates a new copy of repo in dir. +// The parent of dir must exist; dir must not. +func (v *vcsCmd) create(dir, repo string) error { + return v.run(".", v.createCmd, "dir", dir, "repo", repo) +} + +// download downloads any new changes for the repo in dir. +func (v *vcsCmd) download(dir string) error { + return v.run(dir, v.downloadCmd) +} + +// tags returns the list of available tags for the repo in dir. +func (v *vcsCmd) tags(dir string) ([]string, error) { + var tags []string + for _, tc := range v.tagCmd { + out, err := v.runOutput(dir, tc.cmd) + if err != nil { + return nil, err + } + re := regexp.MustCompile(`(?m-s)` + tc.pattern) + tags = append(tags, re.FindAllString(string(out), -1)...) + } + return tags, nil +} + +// tagSync syncs the repo in dir to the named tag, +// which either is a tag returned by tags or is v.tagDefault. +func (v *vcsCmd) tagSync(dir, tag string) error { + if v.tagSyncCmd == "" { + return nil + } + return v.run(dir, v.tagSyncCmd, "tag", tag) +} + +// A vcsPath describes how to convert an import path into a +// version control system and repository name. +type vcsPath struct { + prefix string // prefix this description applies to + re string // pattern for import path + repo string // repository to use (expand with match of re) + vcs string // version control system to use (expand with match of re) + check func(match map[string]string) error // additional checks + + regexp *regexp.Regexp // cached compiled form of re +} + +// vcsForImportPath analyzes importPath to determine the +// version control system, and code repository to use. +// On return, repo is the repository URL and root is the +// import path corresponding to the root of the repository +// (thus root is a prefix of importPath). +func vcsForImportPath(importPath string) (vcs *vcsCmd, repo, root string, err error) { + for _, srv := range vcsPaths { + if !strings.HasPrefix(importPath, srv.prefix) { + continue + } + m := srv.regexp.FindStringSubmatch(importPath) + if m == nil { + if srv.prefix != "" { + return nil, "", "", fmt.Errorf("invalid %s import path %q", srv.prefix, importPath) + } + continue + } + + // Build map of named subexpression matches for expand. + match := map[string]string{ + "prefix": srv.prefix, + "import": importPath, + } + for i, name := range srv.regexp.SubexpNames() { + if name != "" && match[name] == "" { + match[name] = m[i] + } + } + if srv.vcs != "" { + match["vcs"] = expand(match, srv.vcs) + } + if srv.repo != "" { + match["repo"] = expand(match, srv.repo) + } + if srv.check != nil { + if err := srv.check(match); err != nil { + return nil, "", "", err + } + } + vcs := vcsByCmd(match["vcs"]) + if vcs == nil { + return nil, "", "", fmt.Errorf("unknown version control system %q", match["vcs"]) + } + return vcs, match["repo"], match["root"], nil + } + return nil, "", "", fmt.Errorf("unrecognized import path %q", importPath) +} + +// expand rewrites s to replace {k} with match[k] for each key k in match. +func expand(match map[string]string, s string) string { + for k, v := range match { + s = strings.Replace(s, "{"+k+"}", v, -1) + } + return s +} + +// vcsPaths lists the known vcs paths. +var vcsPaths = []*vcsPath{ + // Google Code - new syntax + { + prefix: "code.google.com/", + re: `^(?P<root>code\.google\.com/p/(?P<project>[a-z0-9\-]+)(\.(?P<subrepo>[a-z0-9\-]+))?)(/[A-Za-z0-9_.\-]+)*$`, + repo: "https://{root}", + check: googleCodeVCS, + }, + + // Google Code - old syntax + { + re: `^(?P<project>[a-z0-9_\-.]+)\.googlecode\.com/(git|hg|svn)(?P<path>/.*)?$`, + check: oldGoogleCode, + }, + + // Github + { + prefix: "github.com/", + re: `^(?P<root>github\.com/[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+)(/[A-Za-z0-9_.\-]+)*$`, + vcs: "git", + repo: "https://{root}", + check: noVCSSuffix, + }, + + // Bitbucket + { + prefix: "bitbucket.org/", + re: `^(?P<root>bitbucket\.org/(?P<bitname>[A-Za-z0-9_.\-]+/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`, + repo: "https://{root}", + check: bitbucketVCS, + }, + + // Launchpad + { + prefix: "launchpad.net/", + re: `^(?P<root>launchpad\.net/((?P<project>[A-Za-z0-9_.\-]+)(?P<series>/[A-Za-z0-9_.\-]+)?|~[A-Za-z0-9_.\-]+/(\+junk|[A-Za-z0-9_.\-]+)/[A-Za-z0-9_.\-]+))(/[A-Za-z0-9_.\-]+)*$`, + vcs: "bzr", + repo: "https://{root}", + check: launchpadVCS, + }, + + // General syntax for any server. + { + re: `^(?P<root>(?P<repo>([a-z0-9.\-]+\.)+[a-z0-9.\-]+(:[0-9]+)?/[A-Za-z0-9_.\-/]*?)\.(?P<vcs>bzr|git|hg|svn))(/[A-Za-z0-9_.\-]+)*$`, + }, +} + +func init() { + // fill in cached regexps. + // Doing this eagerly discovers invalid regexp syntax + // without having to run a command that needs that regexp. + for _, srv := range vcsPaths { + srv.regexp = regexp.MustCompile(srv.re) + } +} + +// noVCSSuffix checks that the repository name does not +// end in .foo for any version control system foo. +// The usual culprit is ".git". +func noVCSSuffix(match map[string]string) error { + repo := match["repo"] + for _, vcs := range vcsList { + if strings.HasSuffix(repo, "."+vcs.cmd) { + return fmt.Errorf("invalid version control suffix in %s path", match["prefix"]) + } + } + return nil +} + +var googleCheckout = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`) + +// googleCodeVCS determines the version control system for +// a code.google.com repository, by scraping the project's +// /source/checkout page. +func googleCodeVCS(match map[string]string) error { + if err := noVCSSuffix(match); err != nil { + return err + } + data, err := httpGET(expand(match, "https://code.google.com/p/{project}/source/checkout?repo={subrepo}")) + if err != nil { + return err + } + + if m := googleCheckout.FindSubmatch(data); m != nil { + if vcs := vcsByCmd(string(m[1])); vcs != nil { + // Subversion requires the old URLs. + // TODO: Test. + if vcs == vcsSvn { + if match["subrepo"] != "" { + return fmt.Errorf("sub-repositories not supported in Google Code Subversion projects") + } + match["repo"] = expand(match, "https://{project}.googlecode.com/svn") + } + match["vcs"] = vcs.cmd + return nil + } + } + + return fmt.Errorf("unable to detect version control system for code.google.com/ path") +} + +// oldGoogleCode is invoked for old-style foo.googlecode.com paths. +// It prints an error giving the equivalent new path. +func oldGoogleCode(match map[string]string) error { + return fmt.Errorf("invalid Google Code import path: use %s instead", + expand(match, "code.google.com/p/{project}{path}")) +} + +// bitbucketVCS determines the version control system for a +// BitBucket repository, by using the BitBucket API. +func bitbucketVCS(match map[string]string) error { + if err := noVCSSuffix(match); err != nil { + return err + } + + var resp struct { + SCM string `json:"scm"` + } + url := expand(match, "https://api.bitbucket.org/1.0/repositories/{bitname}") + data, err := httpGET(url) + if err != nil { + return err + } + if err := json.Unmarshal(data, &resp); err != nil { + return fmt.Errorf("decoding %s: %v", url, err) + } + + if vcsByCmd(resp.SCM) != nil { + match["vcs"] = resp.SCM + if resp.SCM == "git" { + match["repo"] += ".git" + } + return nil + } + + return fmt.Errorf("unable to detect version control system for bitbucket.org/ path") +} + +// launchpadVCS solves the ambiguity for "lp.net/project/foo". In this case, +// "foo" could be a series name registered in Launchpad with its own branch, +// and it could also be the name of a directory within the main project +// branch one level up. +func launchpadVCS(match map[string]string) error { + if match["project"] == "" || match["series"] == "" { + return nil + } + _, err := httpGET(expand(match, "https://code.launchpad.net/{project}{series}/.bzr/branch-format")) + if err != nil { + match["root"] = expand(match, "launchpad.net/{project}") + match["repo"] = expand(match, "https://{root}") + } + return nil +} diff --git a/src/cmd/go/version.go b/src/cmd/go/version.go new file mode 100644 index 000000000..09e2f1633 --- /dev/null +++ b/src/cmd/go/version.go @@ -0,0 +1,25 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "runtime" +) + +var cmdVersion = &Command{ + Run: runVersion, + UsageLine: "version", + Short: "print Go version", + Long: `Version prints the Go version, as reported by runtime.Version.`, +} + +func runVersion(cmd *Command, args []string) { + if len(args) != 0 { + cmd.Usage() + } + + fmt.Printf("go version %s\n", runtime.Version()) +} diff --git a/src/cmd/go/vet.go b/src/cmd/go/vet.go new file mode 100644 index 000000000..52c320032 --- /dev/null +++ b/src/cmd/go/vet.go @@ -0,0 +1,30 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +var cmdVet = &Command{ + Run: runVet, + UsageLine: "vet [importpath...]", + Short: "run govet on packages", + Long: ` +Vet runs the govet command on the packages named by the import paths. + +For more about govet, see 'godoc govet'. +For more about import paths, see 'go help importpath'. + +To run govet with specific options, run govet itself. + +See also: go fmt, go fix. + `, +} + +func runVet(cmd *Command, args []string) { + for _, pkg := range packages(args) { + // Use pkg.gofiles instead of pkg.Dir so that + // the command only applies to this package, + // not to packages in subdirectories. + run("govet", relPaths(pkg.gofiles)) + } +} diff --git a/src/cmd/goapi/goapi.go b/src/cmd/goapi/goapi.go new file mode 100644 index 000000000..a64edcae7 --- /dev/null +++ b/src/cmd/goapi/goapi.go @@ -0,0 +1,722 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Goapi computes the exported API of a set of Go packages. +package main + +import ( + "bufio" + "bytes" + "errors" + "flag" + "fmt" + "go/ast" + "go/build" + "go/doc" + "go/parser" + "go/printer" + "go/token" + "io/ioutil" + "log" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" +) + +// Flags +var ( + checkFile = flag.String("c", "", "optional filename to check API against") + verbose = flag.Bool("v", false, "Verbose debugging") +) + +func main() { + flag.Parse() + + var pkgs []string + if flag.NArg() > 0 { + pkgs = flag.Args() + } else { + stds, err := exec.Command("go", "list", "std").Output() + if err != nil { + log.Fatal(err) + } + pkgs = strings.Fields(string(stds)) + } + + w := NewWalker() + tree, _, err := build.FindTree("os") // some known package + if err != nil { + log.Fatalf("failed to find tree: %v", err) + } + + for _, pkg := range pkgs { + if strings.HasPrefix(pkg, "cmd/") || + strings.HasPrefix(pkg, "exp/") || + strings.HasPrefix(pkg, "old/") { + continue + } + if !tree.HasSrc(pkg) { + log.Fatalf("no source in tree for package %q", pkg) + } + pkgSrcDir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) + w.WalkPackage(pkg, pkgSrcDir) + } + + bw := bufio.NewWriter(os.Stdout) + defer bw.Flush() + + if *checkFile != "" { + bs, err := ioutil.ReadFile(*checkFile) + if err != nil { + log.Fatalf("Error reading file %s: %v", *checkFile, err) + } + v1 := strings.Split(string(bs), "\n") + sort.Strings(v1) + v2 := w.Features() + take := func(sl *[]string) string { + s := (*sl)[0] + *sl = (*sl)[1:] + return s + } + for len(v1) > 0 || len(v2) > 0 { + switch { + case len(v2) == 0 || v1[0] < v2[0]: + fmt.Fprintf(bw, "-%s\n", take(&v1)) + case len(v1) == 0 || v1[0] > v2[0]: + fmt.Fprintf(bw, "+%s\n", take(&v2)) + default: + take(&v1) + take(&v2) + } + } + } else { + for _, f := range w.Features() { + fmt.Fprintf(bw, "%s\n", f) + } + } +} + +type Walker struct { + fset *token.FileSet + scope []string + features map[string]bool // set + lastConstType string + curPackageName string + curPackage *ast.Package + prevConstType map[string]string // identifer -> "ideal-int" +} + +func NewWalker() *Walker { + return &Walker{ + fset: token.NewFileSet(), + features: make(map[string]bool), + } +} + +// hardCodedConstantType is a hack until the type checker is sufficient for our needs. +// Rather than litter the code with unnecessary type annotations, we'll hard-code +// the cases we can't handle yet. +func (w *Walker) hardCodedConstantType(name string) (typ string, ok bool) { + switch w.scope[0] { + case "pkg compress/gzip", "pkg compress/zlib": + switch name { + case "NoCompression", "BestSpeed", "BestCompression", "DefaultCompression": + return "ideal-int", true + } + case "pkg os": + switch name { + case "WNOHANG", "WSTOPPED", "WUNTRACED": + return "ideal-int", true + } + case "pkg path/filepath": + switch name { + case "Separator", "ListSeparator": + return "char", true + } + case "pkg unicode/utf8": + switch name { + case "RuneError": + return "char", true + } + case "pkg text/scanner": + // TODO: currently this tool only resolves const types + // that reference other constant types if they appear + // in the right order. the scanner package has + // ScanIdents and such coming before the Ident/Int/etc + // tokens, hence this hack. + if strings.HasPrefix(name, "Scan") || name == "SkipComments" { + return "ideal-int", true + } + } + return "", false +} + +func (w *Walker) Features() (fs []string) { + for f := range w.features { + fs = append(fs, f) + } + sort.Strings(fs) + return +} + +func (w *Walker) WalkPackage(name, dir string) { + log.Printf("package %s", name) + pop := w.pushScope("pkg " + name) + defer pop() + + info, err := build.ScanDir(dir) + if err != nil { + log.Fatalf("pkg %q, dir %q: ScanDir: %v", name, dir, err) + } + + apkg := &ast.Package{ + Files: make(map[string]*ast.File), + } + + files := append(append([]string{}, info.GoFiles...), info.CgoFiles...) + for _, file := range files { + f, err := parser.ParseFile(w.fset, filepath.Join(dir, file), nil, 0) + if err != nil { + log.Fatalf("error parsing package %s, file %s: %v", name, file, err) + } + apkg.Files[file] = f + } + + w.curPackageName = name + w.curPackage = apkg + w.prevConstType = map[string]string{} + for name, afile := range apkg.Files { + w.walkFile(filepath.Join(dir, name), afile) + } + + // Now that we're done walking types, vars and consts + // in the *ast.Package, use go/doc to do the rest + // (functions and methods). This is done here because + // go/doc is destructive. We can't use the + // *ast.Package after this. + dpkg := doc.New(apkg, name, 0) + + for _, t := range dpkg.Types { + // Move funcs up to the top-level, not hiding in the Types. + dpkg.Funcs = append(dpkg.Funcs, t.Funcs...) + + for _, m := range t.Methods { + w.walkFuncDecl(m.Decl) + } + } + + for _, f := range dpkg.Funcs { + w.walkFuncDecl(f.Decl) + } +} + +// pushScope enters a new scope (walking a package, type, node, etc) +// and returns a function that will leave the scope (with sanity checking +// for mismatched pushes & pops) +func (w *Walker) pushScope(name string) (popFunc func()) { + w.scope = append(w.scope, name) + return func() { + if len(w.scope) == 0 { + log.Fatalf("attempt to leave scope %q with empty scope list", name) + } + if w.scope[len(w.scope)-1] != name { + log.Fatalf("attempt to leave scope %q, but scope is currently %#v", name, w.scope) + } + w.scope = w.scope[:len(w.scope)-1] + } +} + +func (w *Walker) walkFile(name string, file *ast.File) { + // Not entering a scope here; file boundaries aren't interesting. + + for _, di := range file.Decls { + switch d := di.(type) { + case *ast.GenDecl: + switch d.Tok { + case token.IMPORT: + continue + case token.CONST: + for _, sp := range d.Specs { + w.walkConst(sp.(*ast.ValueSpec)) + } + case token.TYPE: + for _, sp := range d.Specs { + w.walkTypeSpec(sp.(*ast.TypeSpec)) + } + case token.VAR: + for _, sp := range d.Specs { + w.walkVar(sp.(*ast.ValueSpec)) + } + default: + log.Fatalf("unknown token type %d in GenDecl", d.Tok) + } + case *ast.FuncDecl: + // Ignore. Handled in subsequent pass, by go/doc. + default: + log.Printf("unhandled %T, %#v\n", di, di) + printer.Fprint(os.Stderr, w.fset, di) + os.Stderr.Write([]byte("\n")) + } + } +} + +var constType = map[token.Token]string{ + token.INT: "ideal-int", + token.FLOAT: "ideal-float", + token.STRING: "ideal-string", + token.CHAR: "ideal-char", + token.IMAG: "ideal-imag", +} + +var varType = map[token.Token]string{ + token.INT: "int", + token.FLOAT: "float64", + token.STRING: "string", + token.CHAR: "rune", + token.IMAG: "complex128", +} + +var errTODO = errors.New("TODO") + +func (w *Walker) constValueType(vi interface{}) (string, error) { + switch v := vi.(type) { + case *ast.BasicLit: + litType, ok := constType[v.Kind] + if !ok { + return "", fmt.Errorf("unknown basic literal kind %#v", v) + } + return litType, nil + case *ast.UnaryExpr: + return w.constValueType(v.X) + case *ast.SelectorExpr: + // e.g. compress/gzip's BestSpeed == flate.BestSpeed + return "", errTODO + case *ast.Ident: + if v.Name == "iota" { + return "ideal-int", nil // hack. + } + if v.Name == "false" || v.Name == "true" { + return "ideal-bool", nil + } + if v.Name == "intSize" && w.curPackageName == "strconv" { + // Hack. + return "ideal-int", nil + } + if t, ok := w.prevConstType[v.Name]; ok { + return t, nil + } + return "", fmt.Errorf("can't resolve existing constant %q", v.Name) + case *ast.BinaryExpr: + left, err := w.constValueType(v.X) + if err != nil { + return "", err + } + right, err := w.constValueType(v.Y) + if err != nil { + return "", err + } + if left != right { + if left == "ideal-int" && right == "ideal-float" { + return "ideal-float", nil // math.Log2E + } + if left == "ideal-char" && right == "ideal-int" { + return "ideal-int", nil // math/big.MaxBase + } + if left == "ideal-int" && right == "ideal-char" { + return "ideal-int", nil // text/scanner.GoWhitespace + } + if left == "ideal-int" && right == "Duration" { + // Hack, for package time. + return "Duration", nil + } + return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right) + } + return left, nil + case *ast.CallExpr: + // Not a call, but a type conversion. + return w.nodeString(v.Fun), nil + case *ast.ParenExpr: + return w.constValueType(v.X) + } + return "", fmt.Errorf("unknown const value type %T", vi) +} + +func (w *Walker) varValueType(vi interface{}) (string, error) { + valStr := w.nodeString(vi) + if strings.HasPrefix(valStr, "errors.New(") { + return "error", nil + } + + switch v := vi.(type) { + case *ast.BasicLit: + litType, ok := varType[v.Kind] + if !ok { + return "", fmt.Errorf("unknown basic literal kind %#v", v) + } + return litType, nil + case *ast.CompositeLit: + return w.nodeString(v.Type), nil + case *ast.FuncLit: + return w.nodeString(w.namelessType(v.Type)), nil + case *ast.UnaryExpr: + if v.Op == token.AND { + typ, err := w.varValueType(v.X) + return "*" + typ, err + } + return "", fmt.Errorf("unknown unary expr: %#v", v) + case *ast.SelectorExpr: + return "", errTODO + case *ast.Ident: + node, _, ok := w.resolveName(v.Name) + if !ok { + return "", fmt.Errorf("unresolved identifier: %q", v.Name) + } + return w.varValueType(node) + case *ast.BinaryExpr: + left, err := w.varValueType(v.X) + if err != nil { + return "", err + } + right, err := w.varValueType(v.Y) + if err != nil { + return "", err + } + if left != right { + return "", fmt.Errorf("in BinaryExpr, unhandled type mismatch; left=%q, right=%q", left, right) + } + return left, nil + case *ast.ParenExpr: + return w.varValueType(v.X) + case *ast.CallExpr: + funStr := w.nodeString(v.Fun) + node, _, ok := w.resolveName(funStr) + if !ok { + return "", fmt.Errorf("unresolved named %q", funStr) + } + if funcd, ok := node.(*ast.FuncDecl); ok { + // Assume at the top level that all functions have exactly 1 result + return w.nodeString(w.namelessType(funcd.Type.Results.List[0].Type)), nil + } + // maybe a function call; maybe a conversion. Need to lookup type. + return "", fmt.Errorf("resolved name %q to a %T: %#v", funStr, node, node) + default: + return "", fmt.Errorf("unknown const value type %T", vi) + } + panic("unreachable") +} + +// resolveName finds a top-level node named name and returns the node +// v and its type t, if known. +func (w *Walker) resolveName(name string) (v interface{}, t interface{}, ok bool) { + for _, file := range w.curPackage.Files { + for _, di := range file.Decls { + switch d := di.(type) { + case *ast.FuncDecl: + if d.Name.Name == name { + return d, d.Type, true + } + case *ast.GenDecl: + switch d.Tok { + case token.TYPE: + for _, sp := range d.Specs { + ts := sp.(*ast.TypeSpec) + if ts.Name.Name == name { + return ts, ts.Type, true + } + } + case token.VAR: + for _, sp := range d.Specs { + vs := sp.(*ast.ValueSpec) + for i, vname := range vs.Names { + if vname.Name == name { + if len(vs.Values) > i { + return vs.Values[i], vs.Type, true + } + return nil, vs.Type, true + } + } + } + } + } + } + } + return nil, nil, false +} + +func (w *Walker) walkConst(vs *ast.ValueSpec) { + for _, ident := range vs.Names { + if !ast.IsExported(ident.Name) { + continue + } + litType := "" + if vs.Type != nil { + litType = w.nodeString(vs.Type) + } else { + litType = w.lastConstType + if vs.Values != nil { + if len(vs.Values) != 1 { + log.Fatalf("const %q, values: %#v", ident.Name, vs.Values) + } + var err error + litType, err = w.constValueType(vs.Values[0]) + if err != nil { + if t, ok := w.hardCodedConstantType(ident.Name); ok { + litType = t + err = nil + } else { + log.Fatalf("unknown kind in const %q (%T): %v", ident.Name, vs.Values[0], err) + } + } + } + } + if litType == "" { + log.Fatalf("unknown kind in const %q", ident.Name) + } + w.lastConstType = litType + + w.emitFeature(fmt.Sprintf("const %s %s", ident, litType)) + w.prevConstType[ident.Name] = litType + } +} + +func (w *Walker) walkVar(vs *ast.ValueSpec) { + for i, ident := range vs.Names { + if !ast.IsExported(ident.Name) { + continue + } + + typ := "" + if vs.Type != nil { + typ = w.nodeString(vs.Type) + } else { + if len(vs.Values) == 0 { + log.Fatalf("no values for var %q", ident.Name) + } + if len(vs.Values) > 1 { + log.Fatalf("more than 1 values in ValueSpec not handled, var %q", ident.Name) + } + var err error + typ, err = w.varValueType(vs.Values[i]) + if err != nil { + log.Fatalf("unknown type of variable %q, type %T, error = %v\ncode: %s", + ident.Name, vs.Values[i], err, w.nodeString(vs.Values[i])) + } + } + w.emitFeature(fmt.Sprintf("var %s %s", ident, typ)) + } +} + +func (w *Walker) nodeString(node interface{}) string { + if node == nil { + return "" + } + var b bytes.Buffer + printer.Fprint(&b, w.fset, node) + return b.String() +} + +func (w *Walker) nodeDebug(node interface{}) string { + if node == nil { + return "" + } + var b bytes.Buffer + ast.Fprint(&b, w.fset, node, nil) + return b.String() +} + +func (w *Walker) walkTypeSpec(ts *ast.TypeSpec) { + name := ts.Name.Name + if !ast.IsExported(name) { + return + } + + switch t := ts.Type.(type) { + case *ast.StructType: + w.walkStructType(name, t) + case *ast.InterfaceType: + w.walkInterfaceType(name, t) + default: + w.emitFeature(fmt.Sprintf("type %s %s", name, w.nodeString(ts.Type))) + //log.Fatalf("unknown typespec %T", ts.Type) + } +} + +func (w *Walker) walkStructType(name string, t *ast.StructType) { + typeStruct := fmt.Sprintf("type %s struct", name) + w.emitFeature(typeStruct) + pop := w.pushScope(typeStruct) + defer pop() + for _, f := range t.Fields.List { + typ := f.Type + for _, name := range f.Names { + if ast.IsExported(name.Name) { + w.emitFeature(fmt.Sprintf("%s %s", name, w.nodeString(w.namelessType(typ)))) + } + } + if f.Names == nil { + switch v := typ.(type) { + case *ast.Ident: + if ast.IsExported(v.Name) { + w.emitFeature(fmt.Sprintf("embedded %s", v.Name)) + } + case *ast.StarExpr: + switch vv := v.X.(type) { + case *ast.Ident: + if ast.IsExported(vv.Name) { + w.emitFeature(fmt.Sprintf("embedded *%s", vv.Name)) + } + case *ast.SelectorExpr: + w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ))) + default: + log.Fatal("unable to handle embedded starexpr before %T", typ) + } + case *ast.SelectorExpr: + w.emitFeature(fmt.Sprintf("embedded %s", w.nodeString(typ))) + default: + log.Fatalf("unable to handle embedded %T", typ) + } + } + } +} + +func (w *Walker) walkInterfaceType(name string, t *ast.InterfaceType) { + methods := []string{} + + pop := w.pushScope("type " + name + " interface") + for _, f := range t.Methods.List { + typ := f.Type + for _, name := range f.Names { + if ast.IsExported(name.Name) { + ft := typ.(*ast.FuncType) + w.emitFeature(fmt.Sprintf("%s%s", name, w.funcSigString(ft))) + methods = append(methods, name.Name) + } + } + } + pop() + + sort.Strings(methods) + if len(methods) == 0 { + w.emitFeature(fmt.Sprintf("type %s interface {}", name)) + } else { + w.emitFeature(fmt.Sprintf("type %s interface { %s }", name, strings.Join(methods, ", "))) + } +} + +func (w *Walker) walkFuncDecl(f *ast.FuncDecl) { + if !ast.IsExported(f.Name.Name) { + return + } + if f.Recv != nil { + // Method. + recvType := w.nodeString(f.Recv.List[0].Type) + keep := ast.IsExported(recvType) || + (strings.HasPrefix(recvType, "*") && + ast.IsExported(recvType[1:])) + if !keep { + return + } + w.emitFeature(fmt.Sprintf("method (%s) %s%s", recvType, f.Name.Name, w.funcSigString(f.Type))) + return + } + // Else, a function + w.emitFeature(fmt.Sprintf("func %s%s", f.Name.Name, w.funcSigString(f.Type))) +} + +func (w *Walker) funcSigString(ft *ast.FuncType) string { + var b bytes.Buffer + b.WriteByte('(') + if ft.Params != nil { + for i, f := range ft.Params.List { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(w.nodeString(w.namelessType(f.Type))) + } + } + b.WriteByte(')') + if ft.Results != nil { + if nr := len(ft.Results.List); nr > 0 { + b.WriteByte(' ') + if nr > 1 { + b.WriteByte('(') + } + for i, f := range ft.Results.List { + if i > 0 { + b.WriteString(", ") + } + b.WriteString(w.nodeString(w.namelessType(f.Type))) + } + if nr > 1 { + b.WriteByte(')') + } + } + } + return b.String() +} + +// namelessType returns a type node that lacks any variable names. +func (w *Walker) namelessType(t interface{}) interface{} { + ft, ok := t.(*ast.FuncType) + if !ok { + return t + } + return &ast.FuncType{ + Params: w.namelessFieldList(ft.Params), + Results: w.namelessFieldList(ft.Results), + } +} + +// namelessFieldList returns a deep clone of fl, with the cloned fields +// lacking names. +func (w *Walker) namelessFieldList(fl *ast.FieldList) *ast.FieldList { + fl2 := &ast.FieldList{} + if fl != nil { + for _, f := range fl.List { + fl2.List = append(fl2.List, w.namelessField(f)) + } + } + return fl2 +} + +// namelessField clones f, but not preserving the names of fields. +// (comments and tags are also ignored) +func (w *Walker) namelessField(f *ast.Field) *ast.Field { + return &ast.Field{ + Type: f.Type, + } +} + +func (w *Walker) emitFeature(feature string) { + f := strings.Join(w.scope, ", ") + ", " + feature + if _, dup := w.features[f]; dup { + panic("duplicate feature inserted: " + f) + } + + if strings.Contains(f, "\n") { + // TODO: for now, just skip over the + // runtime.MemStatsType.BySize type, which this tool + // doesn't properly handle. It's pretty low-level, + // though, so not super important to protect against. + if strings.HasPrefix(f, "pkg runtime") && strings.Contains(f, "BySize [61]struct") { + return + } + panic("feature contains newlines: " + f) + } + w.features[f] = true + if *verbose { + log.Printf("feature: %s", f) + } +} + +func strListContains(l []string, s string) bool { + for _, v := range l { + if v == s { + return true + } + } + return false +} diff --git a/src/cmd/goapi/goapi_test.go b/src/cmd/goapi/goapi_test.go new file mode 100644 index 000000000..1f23b1d68 --- /dev/null +++ b/src/cmd/goapi/goapi_test.go @@ -0,0 +1,73 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strings" + "testing" +) + +var ( + updateGolden = flag.Bool("updategolden", false, "update golden files") +) + +func TestGolden(t *testing.T) { + td, err := os.Open("testdata") + if err != nil { + t.Fatal(err) + } + fis, err := td.Readdir(0) + if err != nil { + t.Fatal(err) + } + for _, fi := range fis { + if !fi.IsDir() { + continue + } + w := NewWalker() + goldenFile := filepath.Join("testdata", fi.Name(), "golden.txt") + + w.WalkPackage(fi.Name(), filepath.Join("testdata", fi.Name())) + + if *updateGolden { + os.Remove(goldenFile) + f, err := os.Create(goldenFile) + if err != nil { + t.Fatal(err) + } + for _, feat := range w.Features() { + fmt.Fprintf(f, "%s\n", feat) + } + f.Close() + } + + bs, err := ioutil.ReadFile(goldenFile) + if err != nil { + t.Fatalf("opening golden.txt for package %q: %v", fi.Name(), err) + } + wanted := strings.Split(string(bs), "\n") + sort.Strings(wanted) + for _, feature := range wanted { + if feature == "" { + continue + } + _, ok := w.features[feature] + if !ok { + t.Errorf("package %s: missing feature %q", fi.Name(), feature) + } + delete(w.features, feature) + } + + for _, feature := range w.Features() { + t.Errorf("package %s: extra feature not in golden file: %q", fi.Name(), feature) + } + } +} diff --git a/src/cmd/goapi/testdata/p1/golden.txt b/src/cmd/goapi/testdata/p1/golden.txt new file mode 100644 index 000000000..5b2aff548 --- /dev/null +++ b/src/cmd/goapi/testdata/p1/golden.txt @@ -0,0 +1,57 @@ +pkg p1, const A ideal-int +pkg p1, const A64 int64 +pkg p1, const B ideal-int +pkg p1, const ConversionConst MyInt +pkg p1, const FloatConst ideal-float +pkg p1, const StrConst ideal-string +pkg p1, func Bar(int8, int16, int64) +pkg p1, func Bar1(int8, int16, int64) uint64 +pkg p1, func Bar2(int8, int16, int64) (uint8, uint64) +pkg p1, func TakesFunc(func(int) int) +pkg p1, method (*B) JustOnB() +pkg p1, method (*B) OnBothTandBPtr() +pkg p1, method (*Embedded) OnEmbedded() +pkg p1, method (*S2) SMethod(int8, int16, int64) +pkg p1, method (*T) JustOnT() +pkg p1, method (*T) OnBothTandBPtr() +pkg p1, method (B) OnBothTandBVal() +pkg p1, method (S) StructValueMethod() +pkg p1, method (S) StructValueMethodNamedRecv() +pkg p1, method (S2) StructValueMethod() +pkg p1, method (S2) StructValueMethodNamedRecv() +pkg p1, method (T) OnBothTandBVal() +pkg p1, method (TPtrExported) OnEmbedded() +pkg p1, method (TPtrUnexported) OnBothTandBPtr() +pkg p1, method (TPtrUnexported) OnBothTandBVal() +pkg p1, type B struct +pkg p1, type Codec struct +pkg p1, type Codec struct, Func func(int, int) int +pkg p1, type EmbedSelector struct +pkg p1, type EmbedSelector struct, embedded time.Time +pkg p1, type EmbedURLPtr struct +pkg p1, type EmbedURLPtr struct, embedded *url.URL +pkg p1, type Embedded struct +pkg p1, type I interface { Get, GetNamed, Set } +pkg p1, type I interface, Get(string) int64 +pkg p1, type I interface, GetNamed(string) int64 +pkg p1, type I interface, Set(string, int64) +pkg p1, type MyInt int +pkg p1, type S struct +pkg p1, type S struct, Public *int +pkg p1, type S struct, PublicTime time.Time +pkg p1, type S2 struct +pkg p1, type S2 struct, Extra bool +pkg p1, type S2 struct, embedded S +pkg p1, type SI struct +pkg p1, type SI struct, I int +pkg p1, type T struct +pkg p1, type TPtrExported struct +pkg p1, type TPtrExported struct, embedded *Embedded +pkg p1, type TPtrUnexported struct +pkg p1, var ChecksumError error +pkg p1, var SIPtr *SI +pkg p1, var SIPtr2 *SI +pkg p1, var SIVal SI +pkg p1, var X I +pkg p1, var X int64 +pkg p1, var Y int diff --git a/src/cmd/goapi/testdata/p1/p1.go b/src/cmd/goapi/testdata/p1/p1.go new file mode 100644 index 000000000..67a0ed9a4 --- /dev/null +++ b/src/cmd/goapi/testdata/p1/p1.go @@ -0,0 +1,123 @@ +package foo + +import ( + "time" + "url" +) + +const ( + A = 1 + a = 11 + A64 int64 = 1 +) + +const ( + ConversionConst = MyInt(5) +) + +var ChecksumError = errors.New("gzip checksum error") + +const B = 2 +const StrConst = "foo" +const FloatConst = 1.5 + +type myInt int + +type MyInt int + +type S struct { + Public *int + private *int + PublicTime time.Time +} + +type EmbedURLPtr struct { + *url.URL +} + +type S2 struct { + S + Extra bool +} + +var X int64 + +var ( + Y int + X I // todo: resolve this to foo.I? probably doesn't matter. +) + +type I interface { + Set(name string, balance int64) + Get(string) int64 + GetNamed(string) (balance int64) + private() +} + +func (myInt) privateTypeMethod() {} +func (myInt) CapitalMethodUnexportedType() {} + +func (s *S2) SMethod(x int8, y int16, z int64) {} + +type s struct{} + +func (s) method() +func (s) Method() + +func (S) StructValueMethod() +func (ignored S) StructValueMethodNamedRecv() + +func (s *S2) unexported(x int8, y int16, z int64) {} + +func Bar(x int8, y int16, z int64) {} +func Bar1(x int8, y int16, z int64) uint64 {} +func Bar2(x int8, y int16, z int64) (uint8, uint64) {} + +func unexported(x int8, y int16, z int64) {} + +func TakesFunc(f func(dontWantName int) int) + +type Codec struct { + Func func(x int, y int) (z int) +} + +type SI struct { + I int +} + +var SIVal = SI{} +var SIPtr = &SI{} +var SIPtr2 *SI + +type T struct { + common +} + +type B struct { + common +} + +type common struct { + i int +} + +type TPtrUnexported struct { + *common +} + +type TPtrExported struct { + *Embedded +} + +type Embedded struct{} + +func (*Embedded) OnEmbedded() {} + +func (*T) JustOnT() {} +func (*B) JustOnB() {} +func (*common) OnBothTandBPtr() {} +func (common) OnBothTandBVal() {} + +type EmbedSelector struct { + time.Time +} diff --git a/src/cmd/godefs/Makefile b/src/cmd/godefs/Makefile deleted file mode 100644 index 77cd26c04..000000000 --- a/src/cmd/godefs/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2009 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include ../../Make.inc -O:=$(HOST_O) - -TARG=godefs -OFILES=\ - main.$O\ - stabs.$O\ - util.$O\ - -HFILES=a.h - -include ../../Make.ccmd - -test: $(TARG) - ./test.sh diff --git a/src/cmd/godefs/a.h b/src/cmd/godefs/a.h deleted file mode 100644 index 9b4957467..000000000 --- a/src/cmd/godefs/a.h +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include <u.h> -#include <libc.h> -#include <bio.h> - -enum -{ - Void = 1, - Int8, - Uint8, - Int16, - Uint16, - Int32, - Uint32, - Int64, - Uint64, - Float32, - Float64, - Ptr, - Struct, - Array, - Union, - Typedef, -}; - -typedef struct Field Field; -typedef struct Type Type; - -struct Type -{ - Type *next; // next in hash table - - // stabs name and two-integer id - char *name; - int n1; - int n2; - - // int kind - int kind; - - // sub-type for ptr, array - Type *type; - - // struct fields - Field *f; - int nf; - int size; - - int saved; // recorded in typ array - int warned; // warned about needing type - int printed; // has the definition been printed yet? -}; - -struct Field -{ - char *name; - Type *type; - int offset; - int size; -}; - -// Constants -typedef struct Const Const; -struct Const -{ - char *name; - vlong value; -}; - -// Recorded constants and types, to be printed. -extern Const *con; -extern int ncon; -extern Type **typ; -extern int ntyp; -extern int kindsize[]; - -// Language output -typedef struct Lang Lang; -struct Lang -{ - char *constbegin; - char *constfmt; - char *constend; - - char *typdef; - char *typdefend; - - char *structbegin; - char *unionbegin; - char *structpadfmt; - char *structend; - - int (*typefmt)(Fmt*); -}; - -extern Lang go, c; - -void* emalloc(int); -char* estrdup(char*); -void* erealloc(void*, int); -void parsestabtype(char*); diff --git a/src/cmd/godefs/doc.go b/src/cmd/godefs/doc.go deleted file mode 100644 index 365c7cf6e..000000000 --- a/src/cmd/godefs/doc.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - -Godefs is a bootstrapping tool for porting the Go runtime to new systems. -It translates C type declarations into C or Go type declarations -with the same memory layout. - -Usage: godefs [-g package] [-c cc] [-f cc-arg]... [defs.c ...] - -Godefs takes as input a host-compilable C file that includes -standard system headers. From that input file, it generates -a standalone (no #includes) C or Go file containing equivalent -definitions. - -The input to godefs is a C input file that can be compiled by -the host system's standard C compiler (typically gcc). -This file is expected to define new types and enumerated constants -whose names begin with $ (a legal identifier character in gcc). -Godefs compile the given input file with the host compiler and -then parses the debug info embedded in the assembly output. -This is far easier than reading system headers on most machines. - -The output from godefs is either C output intended for the -Plan 9 C compiler tool chain (6c, 8c, or 5c) or Go output. - -The options are: - - -g package - generate Go output using the given package name. - In the Go output, struct fields have leading xx_ prefixes - removed and the first character capitalized (exported). - - -c cc - set the name of the host system's C compiler (default "gcc") - - -f cc-arg - add cc-arg to the command line when invoking the system C compiler - (for example, -f -m64 to invoke gcc -m64). - Repeating this option adds multiple flags to the command line. - -For example, if this is x.c: - - #include <sys/stat.h> - - typedef struct timespec $Timespec; - enum { - $S_IFMT = S_IFMT, - $S_IFIFO = S_IFIFO, - $S_IFCHR = S_IFCHR, - }; - -then "godefs x.c" generates: - - // godefs x.c - // MACHINE GENERATED - DO NOT EDIT. - - // Constants - enum { - S_IFMT = 0xf000, - S_IFIFO = 0x1000, - S_IFCHR = 0x2000, - }; - - // Types - #pragma pack on - - typedef struct Timespec Timespec; - struct Timespec { - int64 tv_sec; - int64 tv_nsec; - }; - #pragma pack off - -and "godefs -g MyPackage x.c" generates: - - // godefs -g MyPackage x.c - // MACHINE GENERATED - DO NOT EDIT. - - package MyPackage - - // Constants - const ( - S_IFMT = 0xf000; - S_IFIFO = 0x1000; - S_IFCHR = 0x2000; - ) - - // Types - - type Timespec struct { - Sec int64; - Nsec int64; - } - -*/ -package documentation diff --git a/src/cmd/godefs/main.c b/src/cmd/godefs/main.c deleted file mode 100644 index 38b2962fa..000000000 --- a/src/cmd/godefs/main.c +++ /dev/null @@ -1,609 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Godefs takes as input a host-compilable C file that includes -// standard system headers. From that input file, it generates -// a standalone (no #includes) C or Go file containing equivalent -// definitions. -// -// The input C file is expected to define new types and enumerated -// constants whose names begin with $ (a legal identifier character -// in gcc). The output is the standalone definitions of those names, -// with the $ removed. -// -// For example, if this is x.c: -// -// #include <sys/stat.h> -// -// typedef struct timespec $Timespec; -// typedef struct stat $Stat; -// enum { -// $S_IFMT = S_IFMT, -// $S_IFIFO = S_IFIFO, -// $S_IFCHR = S_IFCHR, -// }; -// -// then "godefs x.c" generates: -// -// // godefs x.c -// -// // MACHINE GENERATED - DO NOT EDIT. -// -// // Constants -// enum { -// S_IFMT = 0xf000, -// S_IFIFO = 0x1000, -// S_IFCHR = 0x2000, -// }; -// -// // Types -// #pragma pack on -// -// typedef struct Timespec Timespec; -// struct Timespec { -// int32 tv_sec; -// int32 tv_nsec; -// }; -// -// typedef struct Stat Stat; -// struct Stat { -// int32 st_dev; -// uint32 st_ino; -// uint16 st_mode; -// uint16 st_nlink; -// uint32 st_uid; -// uint32 st_gid; -// int32 st_rdev; -// Timespec st_atimespec; -// Timespec st_mtimespec; -// Timespec st_ctimespec; -// int64 st_size; -// int64 st_blocks; -// int32 st_blksize; -// uint32 st_flags; -// uint32 st_gen; -// int32 st_lspare; -// int64 st_qspare[2]; -// }; -// #pragma pack off -// -// The -g flag to godefs causes it to generate Go output, not C. -// In the Go output, struct fields have leading xx_ prefixes removed -// and the first character capitalized (exported). -// -// Godefs works by invoking gcc to compile the given input file -// and then parses the debug info embedded in the assembly output. -// This is far easier than reading system headers on most machines. -// -// The -c flag sets the compiler (default "gcc"). -// -// The -f flag adds a flag to pass to the compiler (e.g., -f -m64). - -#include "a.h" - -#ifdef _WIN32 -int -spawn(char *prog, char **argv) -{ - return _spawnvp(P_NOWAIT, prog, (const char**)argv); -} -#undef waitfor -void -waitfor(int pid) -{ - _cwait(0, pid, 0); -} -#else -int -spawn(char *prog, char **argv) -{ - int pid; - - USED(prog); - pid = fork(); - if(pid < 0) - sysfatal("fork: %r"); - if(pid == 0) { - exec(argv[0], argv); - fprint(2, "exec gcc: %r\n"); - exit(1); - } - return pid; -} -#endif - -void -usage(void) -{ - fprint(2, "usage: godefs [-g package] [-c cc] [-f cc-arg] [defs.c ...]\n"); - exit(1); -} - -int gotypefmt(Fmt*); -int ctypefmt(Fmt*); -int prefixlen(Type*); -int cutprefix(char*); - -Lang go = -{ - "const (\n", - "\t%s = %#llx;\n", - ")\n", - - "type", - "\n", - - "type %s struct {\n", - "type %s struct {\n", - "\tPad_godefs_%d [%d]byte;\n", - "}\n", - - gotypefmt, -}; - -Lang c = -{ - "enum {\n", - "\t%s = %#llx,\n", - "};\n", - - "typedef", - ";\n", - - "typedef struct %s %s;\nstruct %s {\n", - "typedef union %s %s;\nunion %s {\n", - "\tbyte pad_godefs_%d[%d];\n", - "};\n", - - ctypefmt, -}; - -char *pkg; - -int oargc; -char **oargv; -Lang *lang = &c; - -Const *con; -int ncon; - -Type **typ; -int ntyp; - -void -waitforgcc(void) -{ - waitpid(); -} - -void -main(int argc, char **argv) -{ - int p[2], pid, i, j, n, off, npad, prefix; - char **av, *q, *r, *tofree, *name; - char nambuf[100]; - Biobuf *bin, *bout; - Type *t, *tt; - Field *f; - int orig_output_fd; - - quotefmtinstall(); - - oargc = argc; - oargv = argv; - av = emalloc((30+argc)*sizeof av[0]); - atexit(waitforgcc); - - n = 0; - av[n++] = "gcc"; - av[n++] = "-fdollars-in-identifiers"; - av[n++] = "-S"; // write assembly - av[n++] = "-gstabs+"; // include stabs info - av[n++] = "-o"; // to ... - av[n++] = "-"; // ... stdout - av[n++] = "-xc"; // read C - - ARGBEGIN{ - case 'g': - lang = &go; - pkg = EARGF(usage()); - break; - case 'c': - av[0] = EARGF(usage()); - break; - case 'f': - av[n++] = EARGF(usage()); - break; - default: - usage(); - }ARGEND - - if(argc == 0) - av[n++] = "-"; - else - av[n++] = argv[0]; - av[n] = nil; - - orig_output_fd = dup(1, -1); - for(i=0; i==0 || i < argc; i++) { - // Some versions of gcc do not accept -S with multiple files. - // Run gcc once for each file. - // Write assembly and stabs debugging to p[1]. - if(pipe(p) < 0) - sysfatal("pipe: %r"); - dup(p[1], 1); - close(p[1]); - if (argc) - av[n-1] = argv[i]; - pid = spawn(av[0], av); - dup(orig_output_fd, 1); - - // Read assembly, pulling out .stabs lines. - bin = Bfdopen(p[0], OREAD); - while((q = Brdstr(bin, '\n', 1)) != nil) { - // .stabs "float:t(0,12)=r(0,1);4;0;",128,0,0,0 - tofree = q; - while(*q == ' ' || *q == '\t') - q++; - if(strncmp(q, ".stabs", 6) != 0) - goto Continue; - q += 6; - while(*q == ' ' || *q == '\t') - q++; - if(*q++ != '\"') { -Bad: - sysfatal("cannot parse .stabs line:\n%s", tofree); - } - - r = strchr(q, '\"'); - if(r == nil) - goto Bad; - *r++ = '\0'; - if(*r++ != ',') - goto Bad; - if(*r < '0' || *r > '9') - goto Bad; - if(atoi(r) != 128) // stabs kind = local symbol - goto Continue; - - parsestabtype(q); - -Continue: - free(tofree); - } - Bterm(bin); - waitfor(pid); - } - close(orig_output_fd); - - // Write defs to standard output. - bout = Bfdopen(1, OWRITE); - fmtinstall('T', lang->typefmt); - - // Echo original command line in header. - Bprint(bout, "//"); - for(i=0; i<oargc; i++) - Bprint(bout, " %q", oargv[i]); - Bprint(bout, "\n"); - Bprint(bout, "\n"); - Bprint(bout, "// MACHINE GENERATED - DO NOT EDIT.\n"); - Bprint(bout, "\n"); - - if(pkg) - Bprint(bout, "package %s\n\n", pkg); - - // Constants. - Bprint(bout, "// Constants\n"); - if(ncon > 0) { - Bprint(bout, lang->constbegin); - for(i=0; i<ncon; i++) { - // Go can handle negative constants, - // but C enums may not be able to. - if(lang == &go) - Bprint(bout, lang->constfmt, con[i].name, con[i].value); - else - Bprint(bout, lang->constfmt, con[i].name, con[i].value & 0xFFFFFFFF); - } - Bprint(bout, lang->constend); - } - Bprint(bout, "\n"); - - // Types - - // push our names down - for(i=0; i<ntyp; i++) { - t = typ[i]; - name = t->name; - while(t && t->kind == Typedef) - t = t->type; - if(t) - t->name = name; - } - - Bprint(bout, "// Types\n"); - - // Have to turn off structure padding in Plan 9 compiler, - // mainly because it is more aggressive than gcc tends to be. - if(lang == &c) - Bprint(bout, "#pragma pack on\n"); - - for(i=0; i<ntyp; i++) { - Bprint(bout, "\n"); - t = typ[i]; - name = t->name; - while(t && t->kind == Typedef) { - if(name == nil && t->name != nil) { - name = t->name; - if(t->printed) - break; - } - t = t->type; - } - if(name == nil && t->name != nil) { - name = t->name; - if(t->printed) - continue; - t->printed = 1; - } - if(name == nil) { - fprint(2, "unknown name for %T", typ[i]); - continue; - } - if(name[0] == '$') - name++; - npad = 0; - off = 0; - switch(t->kind) { - case 0: - fprint(2, "unknown type definition for %s\n", name); - break; - default: // numeric, array, or pointer - case Array: - case Ptr: - Bprint(bout, "%s %lT%s", lang->typdef, name, t, lang->typdefend); - break; - case Union: - // In Go, print union as struct with only first element, - // padded the rest of the way. - Bprint(bout, lang->unionbegin, name, name, name); - goto StructBody; - case Struct: - Bprint(bout, lang->structbegin, name, name, name); - StructBody: - prefix = 0; - if(lang == &go) - prefix = prefixlen(t); - for(j=0; j<t->nf; j++) { - f = &t->f[j]; - if(f->type->kind == 0 && f->size <= 64 && (f->size&(f->size-1)) == 0) { - // unknown type but <= 64 bits and bit size is a power of two. - // could be enum - make Uint64 and then let it reduce - tt = emalloc(sizeof *tt); - *tt = *f->type; - f->type = tt; - tt->kind = Uint64; - while(tt->kind > Uint8 && kindsize[tt->kind] > f->size) - tt->kind -= 2; - } - // padding - if(t->kind == Struct || lang == &go) { - if(f->offset%8 != 0 || f->size%8 != 0) { - fprint(2, "ignoring bitfield %s.%s\n", t->name, f->name); - continue; - } - if(f->offset < off) - sysfatal("%s: struct fields went backward", t->name); - if(off < f->offset) { - Bprint(bout, lang->structpadfmt, npad++, (f->offset - off) / 8); - off = f->offset; - } - off += f->size; - } - name = f->name; - if(cutprefix(name)) - name += prefix; - if(strcmp(name, "") == 0) { - snprint(nambuf, sizeof nambuf, "Pad_godefs_%d", npad++); - name = nambuf; - } - Bprint(bout, "\t%#lT;\n", name, f->type); - if(t->kind == Union && lang == &go) - break; - } - // final padding - if(t->kind == Struct || lang == &go) { - if(off/8 < t->size) - Bprint(bout, lang->structpadfmt, npad++, t->size - off/8); - } - Bprint(bout, lang->structend); - } - } - if(lang == &c) - Bprint(bout, "#pragma pack off\n"); - Bterm(bout); - exit(0); -} - -char *kindnames[] = { - "void", // actually unknown, but byte is good for pointers - "void", - "int8", - "uint8", - "int16", - "uint16", - "int32", - "uint32", - "int64", - "uint64", - "float32", - "float64", - "ptr", - "struct", - "array", - "union", - "typedef", -}; - -int -ctypefmt(Fmt *f) -{ - char *name, *s; - Type *t; - - name = nil; - if(f->flags & FmtLong) { - name = va_arg(f->args, char*); - if(name == nil || name[0] == '\0') - name = "_anon_"; - } - t = va_arg(f->args, Type*); - while(t && t->kind == Typedef) - t = t->type; - switch(t->kind) { - case Struct: - case Union: - // must be named - s = t->name; - if(s == nil) { - fprint(2, "need name for anonymous struct\n"); - goto bad; - } - else if(s[0] != '$') - fprint(2, "need name for struct %s\n", s); - else - s++; - fmtprint(f, "%s", s); - if(name) - fmtprint(f, " %s", name); - break; - - case Array: - if(name) - fmtprint(f, "%T %s[%d]", t->type, name, t->size); - else - fmtprint(f, "%T[%d]", t->type, t->size); - break; - - case Ptr: - if(name) - fmtprint(f, "%T *%s", t->type, name); - else - fmtprint(f, "%T*", t->type); - break; - - default: - fmtprint(f, "%s", kindnames[t->kind]); - if(name) - fmtprint(f, " %s", name); - break; - - bad: - if(name) - fmtprint(f, "byte %s[%d]", name, t->size); - else - fmtprint(f, "byte[%d]", t->size); - break; - } - - return 0; -} - -int -gotypefmt(Fmt *f) -{ - char *name, *s; - Type *t; - - if(f->flags & FmtLong) { - name = va_arg(f->args, char*); - if('a' <= name[0] && name[0] <= 'z') - name[0] += 'A' - 'a'; - if(name[0] == '_' && (f->flags & FmtSharp)) - fmtprint(f, "X"); - fmtprint(f, "%s ", name); - } - t = va_arg(f->args, Type*); - while(t && t->kind == Typedef) - t = t->type; - - switch(t->kind) { - case Struct: - case Union: - // must be named - s = t->name; - if(s == nil) { - fprint(2, "need name for anonymous struct\n"); - fmtprint(f, "STRUCT"); - } - else if(s[0] != '$') { - fprint(2, "warning: missing name for struct %s\n", s); - fmtprint(f, "[%d]byte /* %s */", t->size, s); - } else - fmtprint(f, "%s", s+1); - break; - - case Array: - fmtprint(f, "[%d]%T", t->size, t->type); - break; - - case Ptr: - fmtprint(f, "*%T", t->type); - break; - - default: - s = kindnames[t->kind]; - if(strcmp(s, "void") == 0) - s = "byte"; - fmtprint(f, "%s", s); - } - - return 0; -} - -// Is this the kind of name we should cut a prefix from? -// The rule is that the name cannot begin with underscore -// and must have an underscore eventually. -int -cutprefix(char *name) -{ - char *p; - - // special case: orig_ in register struct - if(strncmp(name, "orig_", 5) == 0) - return 0; - - for(p=name; *p; p++) { - if(*p == '_') - return p-name > 0; - } - return 0; -} - -// Figure out common struct prefix len -int -prefixlen(Type *t) -{ - int i; - int len; - char *p, *name; - Field *f; - - len = 0; - name = nil; - for(i=0; i<t->nf; i++) { - f = &t->f[i]; - if(!cutprefix(f->name)) - continue; - p = strchr(f->name, '_'); - if(p == nil) - return 0; - if(name == nil) { - name = f->name; - len = p+1 - name; - } - else if(strncmp(f->name, name, len) != 0) - return 0; - } - return len; -} diff --git a/src/cmd/godefs/stabs.c b/src/cmd/godefs/stabs.c deleted file mode 100644 index 2c3d431b8..000000000 --- a/src/cmd/godefs/stabs.c +++ /dev/null @@ -1,456 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Parse stabs debug info. - -#include "a.h" - -int stabsdebug = 1; - -// Hash table for type lookup by number. -Type *hash[1024]; - -// Look up type by number pair. -// TODO(rsc): Iant points out that n1 and n2 are always small and dense, -// so an array of arrays would be a better representation. -Type* -typebynum(uint n1, uint n2) -{ - uint h; - Type *t; - - h = (n1*53+n2) % nelem(hash); - for(t=hash[h]; t; t=t->next) - if(t->n1 == n1 && t->n2 == n2) - return t; - t = emalloc(sizeof *t); - t->next = hash[h]; - hash[h] = t; - t->n1 = n1; - t->n2 = n2; - return t; -} - -// Parse name and colon from *pp, leaving copy in *sp. -static int -parsename(char **pp, char **sp) -{ - char *p; - char *s; - - p = *pp; - while(*p != '\0' && *p != ':') - p++; - if(*p == '\0') { - fprint(2, "parsename expected colon\n"); - return -1; - } - s = emalloc(p - *pp + 1); - memmove(s, *pp, p - *pp); - *sp = s; - *pp = p+1; - return 0; -} - -// Parse single number from *pp. -static int -parsenum1(char **pp, vlong *np) -{ - char *p; - - p = *pp; - if(*p != '-' && (*p < '0' || *p > '9')) { - fprint(2, "parsenum expected minus or digit\n"); - return -1; - } - *np = strtoll(p, pp, 10); - return 0; -} - -// Parse type number - either single number or (n1, n2). -static int -parsetypenum(char **pp, vlong *n1p, vlong *n2p) -{ - char *p; - - p = *pp; - if(*p == '(') { - p++; - if(parsenum1(&p, n1p) < 0) - return -1; - if(*p++ != ',') { - if(stabsdebug) - fprint(2, "parsetypenum expected comma\n"); - return -1; - } - if(parsenum1(&p, n2p) < 0) - return -1; - if(*p++ != ')') { - if(stabsdebug) - fprint(2, "parsetypenum expected right paren\n"); - return -1; - } - *pp = p; - return 0; - } - - if(parsenum1(&p, n1p) < 0) - return -1; - *n2p = 0; - *pp = p; - return 0; -} - -// Written to parse max/min of vlong correctly. -static vlong -parseoctal(char **pp) -{ - char *p; - vlong n; - - p = *pp; - if(*p++ != '0') - return 0; - n = 0; - while(*p >= '0' && *p <= '9') - n = n << 3 | *p++ - '0'; - *pp = p; - return n; -} - -// Integer types are represented in stabs as a "range" -// type with a lo and a hi value. The lo and hi used to -// be lo and hi for the type, but there are now odd -// extensions for floating point and 64-bit numbers. -// -// Have to keep signs separate from values because -// Int64's lo is -0. -typedef struct Intrange Intrange; -struct Intrange -{ - vlong lo; - vlong hi; - int kind; -}; - -Intrange intranges[] = { - 0, 127, Int8, // char - -128, 127, Int8, // signed char - 0, 255, Uint8, - -32768, 32767, Int16, - 0, 65535, Uint16, - -2147483648LL, 2147483647LL, Int32, - 0, 4294967295LL, Uint32, - 1LL << 63, ~(1LL << 63), Int64, - 0, -1, Uint64, - 4, 0, Float32, - 8, 0, Float64, - 16, 0, Void, -}; - -int kindsize[] = { - 0, - 0, - 8, - 8, - 16, - 16, - 32, - 32, - 64, - 64, -}; - -// Parse a single type definition from *pp. -static Type* -parsedef(char **pp, char *name) -{ - char *p; - Type *t, *tt; - int i; - vlong n1, n2, lo, hi; - Field *f; - Intrange *r; - - p = *pp; - - // reference to another type? - if(isdigit(*p) || *p == '(') { - if(parsetypenum(&p, &n1, &n2) < 0) - return nil; - t = typebynum(n1, n2); - if(name && t->name == nil) { - t->name = name; - // save definitions of names beginning with $ - if(name[0] == '$' && !t->saved) { - typ = erealloc(typ, (ntyp+1)*sizeof typ[0]); - typ[ntyp] = t; - ntyp++; - } - } - - // is there an =def suffix? - if(*p == '=') { - p++; - tt = parsedef(&p, name); - if(tt == nil) - return nil; - - if(tt == t) { - tt->kind = Void; - } else { - t->type = tt; - t->kind = Typedef; - } - - // assign given name, but do not record in typ. - // assume the name came from a typedef - // which will be recorded. - if(name) - tt->name = name; - } - - *pp = p; - return t; - } - - // otherwise a type literal. first letter identifies kind - t = emalloc(sizeof *t); - switch(*p) { - default: - fprint(2, "unknown type char %c in %s\n", *p, p); - *pp = ""; - return t; - - case '@': // type attribute - while (*++p != ';'); - *pp = ++p; - return parsedef(pp, nil); - - case '*': // pointer - p++; - t->kind = Ptr; - tt = parsedef(&p, nil); - if(tt == nil) - return nil; - t->type = tt; - break; - - case 'a': // array - p++; - t->kind = Array; - // index type - tt = parsedef(&p, nil); - if(tt == nil) - return nil; - t->size = tt->size; - // element type - tt = parsedef(&p, nil); - if(tt == nil) - return nil; - t->type = tt; - break; - - case 'e': // enum type - record $names in con array. - p++; - for(;;) { - if(*p == '\0') - return nil; - if(*p == ';') { - p++; - break; - } - if(parsename(&p, &name) < 0) - return nil; - if(parsenum1(&p, &n1) < 0) - return nil; - if(name[0] == '$') { - con = erealloc(con, (ncon+1)*sizeof con[0]); - name++; - con[ncon].name = name; - con[ncon].value = n1; - ncon++; - } - if(*p != ',') - return nil; - p++; - } - break; - - case 'f': // function - p++; - if(parsedef(&p, nil) == nil) - return nil; - break; - - case 'B': // volatile - case 'k': // const - ++*pp; - return parsedef(pp, nil); - - case 'r': // sub-range (used for integers) - p++; - if(parsedef(&p, nil) == nil) - return nil; - // usually, the return from parsedef == t, but not always. - - if(*p != ';' || *++p == ';') { - if(stabsdebug) - fprint(2, "range expected number: %s\n", p); - return nil; - } - if(*p == '0') - lo = parseoctal(&p); - else - lo = strtoll(p, &p, 10); - if(*p != ';' || *++p == ';') { - if(stabsdebug) - fprint(2, "range expected number: %s\n", p); - return nil; - } - if(*p == '0') - hi = parseoctal(&p); - else - hi = strtoll(p, &p, 10); - if(*p != ';') { - if(stabsdebug) - fprint(2, "range expected trailing semi: %s\n", p); - return nil; - } - p++; - t->size = hi+1; // might be array size - for(i=0; i<nelem(intranges); i++) { - r = &intranges[i]; - if(r->lo == lo && r->hi == hi) { - t->kind = r->kind; - break; - } - } - break; - - case 's': // struct - case 'u': // union - t->kind = Struct; - if(*p == 'u') - t->kind = Union; - - // assign given name, but do not record in typ. - // assume the name came from a typedef - // which will be recorded. - if(name) - t->name = name; - p++; - if(parsenum1(&p, &n1) < 0) - return nil; - t->size = n1; - for(;;) { - if(*p == '\0') - return nil; - if(*p == ';') { - p++; - break; - } - t->f = erealloc(t->f, (t->nf+1)*sizeof t->f[0]); - f = &t->f[t->nf]; - if(parsename(&p, &f->name) < 0) - return nil; - f->type = parsedef(&p, nil); - if(f->type == nil) - return nil; - if(*p != ',') { - fprint(2, "expected comma after def of %s:\n%s\n", f->name, p); - return nil; - } - p++; - if(parsenum1(&p, &n1) < 0) - return nil; - f->offset = n1; - if(*p != ',') { - fprint(2, "expected comma after offset of %s:\n%s\n", f->name, p); - return nil; - } - p++; - if(parsenum1(&p, &n1) < 0) - return nil; - f->size = n1; - if(*p != ';') { - fprint(2, "expected semi after size of %s:\n%s\n", f->name, p); - return nil; - } - - while(f->type->kind == Typedef) - f->type = f->type->type; - - // rewrite - // uint32 x : 8; - // into - // uint8 x; - // hooray for bitfields. - while(Int16 <= f->type->kind && f->type->kind <= Uint64 && kindsize[f->type->kind] > f->size) { - tt = emalloc(sizeof *tt); - *tt = *f->type; - f->type = tt; - f->type->kind -= 2; - } - p++; - t->nf++; - } - break; - - case 'x': - // reference to struct, union not yet defined. - p++; - switch(*p) { - case 's': - t->kind = Struct; - break; - case 'u': - t->kind = Union; - break; - default: - fprint(2, "unknown x type char x%c", *p); - *pp = ""; - return t; - } - if(parsename(&p, &t->name) < 0) - return nil; - break; - } - *pp = p; - return t; -} - - -// Parse a stab type in p, saving info in the type hash table -// and also in the list of recorded types if appropriate. -void -parsestabtype(char *p) -{ - char *p0, *name; - - p0 = p; - - // p is the quoted string output from gcc -gstabs on a .stabs line. - // name:t(1,2) - // name:t(1,2)=def - if(parsename(&p, &name) < 0) { - Bad: - // Use fprint instead of sysfatal to avoid - // sysfatal's internal buffer size limit. - fprint(2, "cannot parse stabs type:\n%s\n(at %s)\n", p0, p); - sysfatal("stabs parse"); - } - if(*p != 't' && *p != 'T') - goto Bad; - p++; - - // parse the definition. - if(name[0] == '\0') - name = nil; - if(parsedef(&p, name) == nil) - goto Bad; - if(*p != '\0') - goto Bad; -} - diff --git a/src/cmd/godefs/test.sh b/src/cmd/godefs/test.sh deleted file mode 100755 index c035af8f4..000000000 --- a/src/cmd/godefs/test.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2011 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -eval $(gomake --no-print-directory -f ../../Make.inc go-env) - -TMP="testdata_tmp.go" -TEST="testdata.c" -GOLDEN="testdata_${GOOS}_${GOARCH}.golden" - -case ${GOARCH} in -"amd64") CCARG="-f-m64";; -"386") CCARG="-f-m32";; -*) CCARG="";; -esac - -cleanup() { - rm ${TMP} -} - -error() { - cleanup - echo $1 - exit 1 -} - -if [ ! -e ${GOLDEN} ]; then - echo "skipping - no golden defined for this platform" - exit -fi - -./godefs -g test ${CCARG} ${TEST} > ${TMP} -if [ $? != 0 ]; then - error "Error: Could not run godefs for ${TEST}" -fi - -diff ${TMP} ${GOLDEN} -if [ $? != 0 ]; then - error "FAIL: godefs for ${TEST} did not match ${GOLDEN}" -fi - -cleanup - -echo "PASS" diff --git a/src/cmd/godefs/testdata.c b/src/cmd/godefs/testdata.c deleted file mode 100644 index 3f459c41b..000000000 --- a/src/cmd/godefs/testdata.c +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include <stdint.h> - -// Issue 432 - enum fields in struct can cause misaligned struct fields -typedef enum { - a -} T1; - -struct T2 { - uint8_t a; - T1 b; - T1 c; - uint16_t d; -}; - -typedef struct T2 T2; -typedef T2 $T2; - -// Issue 1162 - structs with fields named Pad[0-9]+ conflict with field -// names used by godefs for padding -struct T3 { - uint8_t a; - int Pad0; -}; - -typedef struct T3 $T3; - -// Issue 1466 - forward references to types in stabs debug info were -// always treated as enums -struct T4 {}; - -struct T5 { - struct T4 *a; -}; - -typedef struct T5 T5; -typedef struct T4 $T4; -typedef T5 $T5;
\ No newline at end of file diff --git a/src/cmd/godefs/testdata_darwin_386.golden b/src/cmd/godefs/testdata_darwin_386.golden deleted file mode 100644 index d929238b0..000000000 --- a/src/cmd/godefs/testdata_darwin_386.golden +++ /dev/null @@ -1,31 +0,0 @@ -// ./godefs -g test -f-m32 testdata.c - -// MACHINE GENERATED - DO NOT EDIT. - -package test - -// Constants - -// Types - -type T2 struct { - A uint8; - Pad_godefs_0 [3]byte; - B uint32; - C uint32; - D uint16; - Pad_godefs_1 [2]byte; -} - -type T3 struct { - A uint8; - Pad_godefs_0 [3]byte; - Pad0 int32; -} - -type T4 struct { -} - -type T5 struct { - A *T4; -} diff --git a/src/cmd/godefs/testdata_darwin_amd64.golden b/src/cmd/godefs/testdata_darwin_amd64.golden deleted file mode 100644 index a694f4a73..000000000 --- a/src/cmd/godefs/testdata_darwin_amd64.golden +++ /dev/null @@ -1,31 +0,0 @@ -// ./godefs -g test -f-m64 testdata.c - -// MACHINE GENERATED - DO NOT EDIT. - -package test - -// Constants - -// Types - -type T2 struct { - A uint8; - Pad_godefs_0 [3]byte; - B uint32; - C uint32; - D uint16; - Pad_godefs_1 [2]byte; -} - -type T3 struct { - A uint8; - Pad_godefs_0 [3]byte; - Pad0 int32; -} - -type T4 struct { -} - -type T5 struct { - A *T4; -} diff --git a/src/cmd/godefs/util.c b/src/cmd/godefs/util.c deleted file mode 100644 index 18be00453..000000000 --- a/src/cmd/godefs/util.c +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -#include "a.h" - -void* -emalloc(int n) -{ - void *p; - - p = malloc(n); - if(p == nil) - sysfatal("out of memory"); - memset(p, 0, n); - return p; -} - -char* -estrdup(char *s) -{ - s = strdup(s); - if(s == nil) - sysfatal("out of memory"); - return s; -} - -void* -erealloc(void *v, int n) -{ - v = realloc(v, n); - if(v == nil) - sysfatal("out of memory"); - return v; -} - diff --git a/src/cmd/godoc/README.godoc-app b/src/cmd/godoc/README.godoc-app new file mode 100644 index 000000000..88cfee41e --- /dev/null +++ b/src/cmd/godoc/README.godoc-app @@ -0,0 +1,80 @@ +Copyright 2011 The Go Authors. All rights reserved. +Use of this source code is governed by a BSD-style +license that can be found in the LICENSE file. + +godoc on appengine +------------------ + +Prerequisites +------------- + +* Go appengine SDK 1.5.5 - 2011-10-11 + http://code.google.com/appengine/downloads.html#Google_App_Engine_SDK_for_Go + +* Go sources at tip under $GOROOT + + +Directory structure +------------------- + +* Let $APPDIR be the directory containing the app engine files. + (e.g., $APPDIR=$HOME/godoc-app) + +* $APPDIR contains the following entries (this may change depending on + app-engine release and version of godoc): + + alt/ + encoding/binary/ + go/* + index/suffixarray/ + app.yaml + godoc.zip + godoc/ + index.split.* + +* The app.yaml file is set up per app engine documentation. + For instance: + + application: godoc-app + version: 1-5-5 + runtime: go + api_version: 3 + + handlers: + - url: /.* + script: _go_app + +* The godoc/ directory contains a copy of the files under $GOROOT/src/cmd/godoc + with modifications: + + - doc.go is excluded (it belongs to pseudo-package ÒdocumentationÓ) + - main.go is excluded (appinit.go is taking its place) + + Additional manual modifications are required to refer to the alt/ packages + where the app-engine library is not up-to-date with the godoc version. + +* The alt/ directory contains up-to-date copies of Go packages that a tip-based + godoc is dependent on but which do not yet exist in the current app-engine SDK. + At the time of this writing (10/14/2011) this is the entire go directory tree + (for the missing FileSet serialization code in go/token) as well as the + index/suffixarray package (for the missing suffix array serialization code). + The latest (alt/)index/suffixarray package internally requires the latest + version of encoding/binary, which is why it also needs to be present under + alt/. + + +Configuring and running godoc +----------------------------- + +To configure godoc, run + + bash setup-godoc-app.bash + +to create the godoc.zip, index.split.*, and godoc/appconfig.go files +based on $GOROOT and $APPDIR. See the script for details on usage. + +To run godoc locally, using the app-engine emulator, run + + <path to google_appengine>/dev_appserver.py $APPDIR + +godoc should come up at http://localhost:8080 . diff --git a/src/cmd/godoc/appconfig.go b/src/cmd/godoc/appconfig.go deleted file mode 100644 index 052a9ebc8..000000000 --- a/src/cmd/godoc/appconfig.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// This file contains configuration information used by -// godoc when running on app engine. Adjust as needed -// (typically when the .zip file changes). - -package main - -const ( - // zipFilename is the name of the .zip file - // containing the file system served by godoc. - zipFilename = "godoc.zip" - - // zipGoroot is the path of the goroot directory - // in the .zip file. - zipGoroot = "/home/user/go" - - // If indexFilenames != "", the search index is - // initialized with the index stored in these - // files (otherwise it will be built at run-time, - // eventually). indexFilenames is a glob pattern; - // the specified files are concatenated in sorted - // order (by filename). - // app-engine limit: file sizes must be <= 10MB; - // use "split -b8m indexfile index.split." to get - // smaller files. - indexFilenames = "index.split.*" -) diff --git a/src/cmd/godoc/appinit.go b/src/cmd/godoc/appinit.go index 8c93425f3..3113498c8 100644 --- a/src/cmd/godoc/appinit.go +++ b/src/cmd/godoc/appinit.go @@ -2,52 +2,21 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// To run godoc under app engine, substitute main.go with -// this file (appinit.go), provide a .zip file containing -// the file system to serve, the index file (or files) -// containing the pre-computed search index and adjust -// the configuration parameters in appconfig.go accordingly. -// -// The current app engine SDK may be based on an older Go -// release version. To correct for version skew, copy newer -// packages into the alt directory (e.g. alt/strings) and -// adjust the imports in the godoc source files (e.g. from -// `import "strings"` to `import "alt/strings"`). Both old -// and new packages may be used simultaneously as long as -// there is no package global state that needs to be shared. -// -// The directory structure should look as follows: -// -// godoc-app // directory containing the app engine app -// alt // alternative packages directory to -// // correct for version skew -// strings // never version of the strings package -// ... // -// app.yaml // app engine control file -// godoc.zip // .zip file containing the file system to serve -// godoc // contains godoc sources -// appinit.go // this file instead of godoc/main.go -// appconfig.go // godoc for app engine configuration -// ... // -// index.split.* // index file(s) containing the search index to serve -// -// To run app the engine emulator locally: -// -// dev_appserver.py -a 0 godoc-app -// -// The godoc home page is served at: <hostname>:8080 and localhost:8080. +// +build ignore package main +// This file replaces main.go when running godoc under app-engine. +// See README.godoc-app for details. + import ( "archive/zip" - "http" "log" - "os" + "net/http" "path" ) -func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) { +func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) { contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path! w.WriteHeader(http.StatusNotFound) servePage(w, "File "+relpath, "", "", contents) diff --git a/src/cmd/godoc/codewalk.go b/src/cmd/godoc/codewalk.go index fb5f27596..b3bc79abe 100644 --- a/src/cmd/godoc/codewalk.go +++ b/src/cmd/godoc/codewalk.go @@ -13,18 +13,19 @@ package main import ( + "encoding/xml" + "errors" "fmt" - "http" "io" "log" + "net/http" "os" - "exp/regexp" + "regexp" "sort" "strconv" "strings" - "template" - "utf8" - "xml" + "text/template" + "unicode/utf8" ) // Handler for /doc/codewalk/ and below. @@ -40,7 +41,7 @@ func codewalk(w http.ResponseWriter, r *http.Request) { // If directory exists, serve list of code walks. dir, err := fs.Lstat(abspath) - if err == nil && dir.IsDirectory() { + if err == nil && dir.IsDir() { codewalkDir(w, r, relpath, abspath) return } @@ -84,7 +85,7 @@ type Codestep struct { XML string `xml:"innerxml"` // Derived from Src; not in XML. - Err os.Error + Err error File string Lo int LoByte int @@ -107,16 +108,16 @@ func (st *Codestep) String() string { } // loadCodewalk reads a codewalk from the named XML file. -func loadCodewalk(filename string) (*Codewalk, os.Error) { +func loadCodewalk(filename string) (*Codewalk, error) { f, err := fs.Open(filename) if err != nil { return nil, err } defer f.Close() cw := new(Codewalk) - p := xml.NewParser(f) - p.Entity = xml.HTMLEntity - err = p.Unmarshal(cw, nil) + d := xml.NewDecoder(f) + d.Entity = xml.HTMLEntity + err = d.Decode(cw) if err != nil { return nil, &os.PathError{"parsing", filename, err} } @@ -185,7 +186,7 @@ func codewalkDir(w http.ResponseWriter, r *http.Request, relpath, abspath string var v []interface{} for _, fi := range dir { name := fi.Name() - if fi.IsDirectory() { + if fi.IsDir() { v = append(v, &elem{name + "/", ""}) } else if strings.HasSuffix(name, ".xml") { cw, err := loadCodewalk(abspath + "/" + name) @@ -252,7 +253,7 @@ func codewalkFileprint(w http.ResponseWriter, r *http.Request, f string) { // It returns the lo and hi byte offset of the matched region within data. // See http://plan9.bell-labs.com/sys/doc/sam/sam.html Table II // for details on the syntax. -func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Error) { +func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err error) { var ( dir byte prevc byte @@ -264,7 +265,7 @@ func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Er c := addr[0] switch c { default: - err = os.NewError("invalid address syntax near " + string(c)) + err = errors.New("invalid address syntax near " + string(c)) case ',': if len(addr) == 1 { hi = len(data) @@ -348,7 +349,7 @@ func addrToByteRange(addr string, start int, data []byte) (lo, hi int, err os.Er // (or characters) after hi. Applying -n (or -#n) means to back up n lines // (or characters) before lo. // The return value is the new lo, hi. -func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, os.Error) { +func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, int, error) { switch dir { case 0: lo = 0 @@ -424,13 +425,13 @@ func addrNumber(data []byte, lo, hi int, dir byte, n int, charOffset bool) (int, } } - return 0, 0, os.NewError("address out of range") + return 0, 0, errors.New("address out of range") } // addrRegexp searches for pattern in the given direction starting at lo, hi. // The direction dir is '+' (search forward from hi) or '-' (search backward from lo). // Backward searches are unimplemented. -func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os.Error) { +func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, error) { re, err := regexp.Compile(pattern) if err != nil { return 0, 0, err @@ -438,7 +439,7 @@ func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os if dir == '-' { // Could implement reverse search using binary search // through file, but that seems like overkill. - return 0, 0, os.NewError("reverse search not implemented") + return 0, 0, errors.New("reverse search not implemented") } m := re.FindIndex(data[hi:]) if len(m) > 0 { @@ -449,7 +450,7 @@ func addrRegexp(data []byte, lo, hi int, dir byte, pattern string) (int, int, os m = re.FindIndex(data) } if len(m) == 0 { - return 0, 0, os.NewError("no match for " + pattern) + return 0, 0, errors.New("no match for " + pattern) } return m[0], m[1], nil } diff --git a/src/cmd/godoc/dirtrees.go b/src/cmd/godoc/dirtrees.go index 7595ef96f..c61f791dc 100644 --- a/src/cmd/godoc/dirtrees.go +++ b/src/cmd/godoc/dirtrees.go @@ -8,10 +8,10 @@ package main import ( "bytes" - "go/doc" "go/parser" "go/token" "log" + "os" "path/filepath" "strings" "unicode" @@ -25,21 +25,21 @@ type Directory struct { Dirs []*Directory // subdirectories } -func isGoFile(fi FileInfo) bool { +func isGoFile(fi os.FileInfo) bool { name := fi.Name() - return fi.IsRegular() && + return !fi.IsDir() && len(name) > 0 && name[0] != '.' && // ignore .files filepath.Ext(name) == ".go" } -func isPkgFile(fi FileInfo) bool { +func isPkgFile(fi os.FileInfo) bool { return isGoFile(fi) && !strings.HasSuffix(fi.Name(), "_test.go") // ignore test files } -func isPkgDir(fi FileInfo) bool { +func isPkgDir(fi os.FileInfo) bool { name := fi.Name() - return fi.IsDirectory() && len(name) > 0 && + return fi.IsDir() && len(name) > 0 && name[0] != '_' && name[0] != '.' // ignore _files and .files } @@ -135,7 +135,7 @@ func (b *treeBuilder) newDirTree(fset *token.FileSet, path, name string, depth i i = 3 // none of the above } if 0 <= i && i < len(synopses) && synopses[i] == "" { - synopses[i] = firstSentence(doc.CommentText(file.Doc)) + synopses[i] = firstSentence(file.Doc.Text()) } } } diff --git a/src/cmd/godoc/doc.go b/src/cmd/godoc/doc.go index 3f0b8e458..acea2b5d0 100644 --- a/src/cmd/godoc/doc.go +++ b/src/cmd/godoc/doc.go @@ -80,6 +80,10 @@ The flags are: repository holding the source files. -sync_minutes=0 sync interval in minutes; sync is disabled if <= 0 + -templates="" + directory containing alternate template files; if set, + the directory may provide alternative template files + for the files in $GOROOT/lib/godoc -filter="" filter file containing permitted package directory paths -filter_minutes=0 @@ -124,6 +128,18 @@ via regular expressions). The maximum number of full text search results shown can be set with the -maxresults flag; if set to 0, no full text results are shown, and only an identifier index but no full text search index is created. +The presentation mode of web pages served by godoc can be controlled with the +"m" URL parameter; it accepts a comma-separated list of flag names as value: + + all show documentation for all (not just exported) declarations + src show the original source code rather then the extracted documentation + text present the page in textual (command-line) form rather than HTML + flat present flat (not indented) directory listings using full paths + +For instance, http://golang.org/pkg/math/big/?m=all,text shows the documentation +for all (not just the exported) declarations of package big, in textual form (as +it would appear when using godoc from the command line: "godoc -src math/big .*"). + By default, godoc serves files from the file system of the underlying OS. Instead, a .zip file may be provided via the -zip flag, which contains the file system to serve. The file paths stored in the .zip file must use @@ -137,7 +153,6 @@ one may run godoc as follows: godoc -http=:6060 -zip=go.zip -goroot=$HOME/go - See "Godoc: documenting Go code" for how to write good comments for godoc: http://blog.golang.org/2011/03/godoc-documenting-go-code.html */ diff --git a/src/cmd/godoc/filesystem.go b/src/cmd/godoc/filesystem.go index 011977af9..4e48c9e68 100644 --- a/src/cmd/godoc/filesystem.go +++ b/src/cmd/godoc/filesystem.go @@ -15,26 +15,17 @@ import ( "os" ) -// The FileInfo interface provides access to file information. -type FileInfo interface { - Name() string - Size() int64 - Mtime_ns() int64 - IsRegular() bool - IsDirectory() bool -} - // The FileSystem interface specifies the methods godoc is using // to access the file system for which it serves documentation. type FileSystem interface { - Open(path string) (io.ReadCloser, os.Error) - Lstat(path string) (FileInfo, os.Error) - Stat(path string) (FileInfo, os.Error) - ReadDir(path string) ([]FileInfo, os.Error) + Open(path string) (io.ReadCloser, error) + Lstat(path string) (os.FileInfo, error) + Stat(path string) (os.FileInfo, error) + ReadDir(path string) ([]os.FileInfo, error) } // ReadFile reads the file named by path from fs and returns the contents. -func ReadFile(fs FileSystem, path string) ([]byte, os.Error) { +func ReadFile(fs FileSystem, path string) ([]byte, error) { rc, err := fs.Open(path) if err != nil { return nil, err @@ -48,30 +39,10 @@ func ReadFile(fs FileSystem, path string) ([]byte, os.Error) { var OS FileSystem = osFS{} -// osFI is the OS-specific implementation of FileInfo. -type osFI struct { - *os.FileInfo -} - -func (fi osFI) Name() string { - return fi.FileInfo.Name -} - -func (fi osFI) Size() int64 { - if fi.IsDirectory() { - return 0 - } - return fi.FileInfo.Size -} - -func (fi osFI) Mtime_ns() int64 { - return fi.FileInfo.Mtime_ns -} - // osFS is the OS-specific implementation of FileSystem type osFS struct{} -func (osFS) Open(path string) (io.ReadCloser, os.Error) { +func (osFS) Open(path string) (io.ReadCloser, error) { f, err := os.Open(path) if err != nil { return nil, err @@ -80,30 +51,20 @@ func (osFS) Open(path string) (io.ReadCloser, os.Error) { if err != nil { return nil, err } - if fi.IsDirectory() { + if fi.IsDir() { return nil, fmt.Errorf("Open: %s is a directory", path) } return f, nil } -func (osFS) Lstat(path string) (FileInfo, os.Error) { - fi, err := os.Lstat(path) - return osFI{fi}, err +func (osFS) Lstat(path string) (os.FileInfo, error) { + return os.Lstat(path) } -func (osFS) Stat(path string) (FileInfo, os.Error) { - fi, err := os.Stat(path) - return osFI{fi}, err +func (osFS) Stat(path string) (os.FileInfo, error) { + return os.Stat(path) } -func (osFS) ReadDir(path string) ([]FileInfo, os.Error) { - l0, err := ioutil.ReadDir(path) // l0 is sorted - if err != nil { - return nil, err - } - l1 := make([]FileInfo, len(l0)) - for i, e := range l0 { - l1[i] = osFI{e} - } - return l1, nil +func (osFS) ReadDir(path string) ([]os.FileInfo, error) { + return ioutil.ReadDir(path) // is sorted } diff --git a/src/cmd/godoc/format.go b/src/cmd/godoc/format.go index 91b746034..3b1b9a822 100644 --- a/src/cmd/godoc/format.go +++ b/src/cmd/godoc/format.go @@ -15,9 +15,9 @@ import ( "go/scanner" "go/token" "io" - "exp/regexp" + "regexp" "strconv" - "template" + "text/template" ) // ---------------------------------------------------------------------------- @@ -231,7 +231,7 @@ func commentSelection(src []byte) Selection { var s scanner.Scanner fset := token.NewFileSet() file := fset.AddFile("", fset.Base(), len(src)) - s.Init(file, src, nil, scanner.ScanComments+scanner.InsertSemis) + s.Init(file, src, nil, scanner.ScanComments) return func() (seg []int) { for { pos, tok, lit := s.Scan() diff --git a/src/cmd/godoc/godoc.go b/src/cmd/godoc/godoc.go index 3bf721bcc..86983fbe1 100644 --- a/src/cmd/godoc/godoc.go +++ b/src/cmd/godoc/godoc.go @@ -6,6 +6,7 @@ package main import ( "bytes" + "encoding/json" "flag" "fmt" "go/ast" @@ -13,18 +14,21 @@ import ( "go/doc" "go/printer" "go/token" - "http" "io" "log" + "net/http" + "net/url" "os" "path" "path/filepath" - "exp/regexp" + "regexp" "runtime" "sort" "strings" - "template" + "text/template" "time" + "unicode" + "unicode/utf8" ) // ---------------------------------------------------------------------------- @@ -34,9 +38,9 @@ type delayTime struct { RWValue } -func (dt *delayTime) backoff(max int) { +func (dt *delayTime) backoff(max time.Duration) { dt.mutex.Lock() - v := dt.value.(int) * 2 + v := dt.value.(time.Duration) * 2 if v > max { v = max } @@ -70,12 +74,13 @@ var ( indexThrottle = flag.Float64("index_throttle", 0.75, "index throttle value; 0.0 = no time allocated, 1.0 = full throttle") // file system mapping - fs FileSystem // the underlying file system for godoc - fsHttp http.FileSystem // the underlying file system for http - fsMap Mapping // user-defined mapping - fsTree RWValue // *Directory tree of packages, updated with each sync - pathFilter RWValue // filter used when building fsMap directory trees - fsModified RWValue // timestamp of last call to invalidateIndex + fs FileSystem // the underlying file system for godoc + fsHttp http.FileSystem // the underlying file system for http + fsMap Mapping // user-defined mapping + fsTree RWValue // *Directory tree of packages, updated with each sync + pathFilter RWValue // filter used when building fsMap directory trees + fsModified RWValue // timestamp of last call to invalidateIndex + docMetadata RWValue // mapping from paths to *Metadata // http handlers fileServer http.Handler // default file server @@ -104,6 +109,7 @@ func registerPublicHandlers(mux *http.ServeMux) { mux.HandleFunc("/doc/codewalk/", codewalk) mux.HandleFunc("/search", search) mux.Handle("/robots.txt", fileServer) + mux.HandleFunc("/opensearch.xml", serveSearchDesc) mux.HandleFunc("/", serveFile) } @@ -148,7 +154,7 @@ func getPathFilter() func(string) bool { // readDirList reads a file containing a newline-separated list // of directory paths and returns the list of paths. -func readDirList(filename string) ([]string, os.Error) { +func readDirList(filename string) ([]string, error) { contents, err := ReadFile(fs, filename) if err != nil { return nil, err @@ -206,7 +212,7 @@ func updateFilterFile() { // update filter file if err := writeFileAtomically(*filter, buf.Bytes()); err != nil { log.Printf("writeFileAtomically(%s): %s", *filter, err) - filterDelay.backoff(24 * 60) // back off exponentially, but try at least once a day + filterDelay.backoff(24 * time.Hour) // back off exponentially, but try at least once a day } else { filterDelay.set(*filterMin) // revert to regular filter update schedule } @@ -229,7 +235,7 @@ func initDirTrees() { // start filter update goroutine, if enabled. if *filter != "" && *filterMin > 0 { - filterDelay.set(*filterMin) // initial filter update delay + filterDelay.set(time.Duration(*filterMin) * time.Minute) // initial filter update delay go func() { for { if *verbose { @@ -237,10 +243,11 @@ func initDirTrees() { } updateFilterFile() delay, _ := filterDelay.get() + dt := delay.(time.Duration) if *verbose { - log.Printf("next filter update in %dmin", delay.(int)) + log.Printf("next filter update in %s", dt) } - time.Sleep(int64(delay.(int)) * 60e9) + time.Sleep(dt) } }() } @@ -299,7 +306,7 @@ type tconv struct { indent int // valid if state == indenting } -func (p *tconv) writeIndent() (err os.Error) { +func (p *tconv) writeIndent() (err error) { i := p.indent for i >= len(spaces) { i -= len(spaces) @@ -314,7 +321,7 @@ func (p *tconv) writeIndent() (err os.Error) { return } -func (p *tconv) Write(data []byte) (n int, err os.Error) { +func (p *tconv) Write(data []byte) (n int, err error) { if len(data) == 0 { return } @@ -371,7 +378,10 @@ func writeNode(w io.Writer, fset *token.FileSet, x interface{}) { // with an another printer mode (which is more efficiently // implemented in the printer than here with another layer) mode := printer.TabIndent | printer.UseSpaces - (&printer.Config{mode, *tabwidth}).Fprint(&tconv{output: w}, fset, x) + err := (&printer.Config{mode, *tabwidth}).Fprint(&tconv{output: w}, fset, x) + if err != nil { + log.Print(err) + } } func filenameFunc(path string) string { @@ -379,17 +389,17 @@ func filenameFunc(path string) string { return localname } -func fileInfoNameFunc(fi FileInfo) string { +func fileInfoNameFunc(fi os.FileInfo) string { name := fi.Name() - if fi.IsDirectory() { + if fi.IsDir() { name += "/" } return name } -func fileInfoTimeFunc(fi FileInfo) string { - if t := fi.Mtime_ns(); t != 0 { - return time.SecondsToLocalTime(t / 1e9).String() +func fileInfoTimeFunc(fi os.FileInfo) string { + if t := fi.ModTime(); t.Unix() != 0 { + return t.Local().String() } return "" // don't return epoch if time is obviously not set } @@ -454,7 +464,65 @@ func comment_htmlFunc(comment string) string { var buf bytes.Buffer // TODO(gri) Provide list of words (e.g. function parameters) // to be emphasized by ToHTML. - doc.ToHTML(&buf, []byte(comment), nil) // does html-escaping + doc.ToHTML(&buf, comment, nil) // does html-escaping + return buf.String() +} + +// punchCardWidth is the number of columns of fixed-width +// characters to assume when wrapping text. Very few people +// use terminals or cards smaller than 80 characters, so 80 it is. +// We do not try to sniff the environment or the tty to adapt to +// the situation; instead, by using a constant we make sure that +// godoc always produces the same output regardless of context, +// a consistency that is lost otherwise. For example, if we sniffed +// the environment or tty, then http://golang.org/pkg/math/?m=text +// would depend on the width of the terminal where godoc started, +// which is clearly bogus. More generally, the Unix tools that behave +// differently when writing to a tty than when writing to a file have +// a history of causing confusion (compare `ls` and `ls | cat`), and we +// want to avoid that mistake here. +const punchCardWidth = 80 + +func comment_textFunc(comment, indent, preIndent string) string { + var buf bytes.Buffer + doc.ToText(&buf, comment, indent, preIndent, punchCardWidth-2*len(indent)) + return buf.String() +} + +func startsWithUppercase(s string) bool { + r, _ := utf8.DecodeRuneInString(s) + return unicode.IsUpper(r) +} + +func example_htmlFunc(funcName string, examples []*doc.Example, fset *token.FileSet) string { + var buf bytes.Buffer + for _, eg := range examples { + name := eg.Name + + // strip lowercase braz in Foo_braz or Foo_Bar_braz from name + // while keeping uppercase Braz in Foo_Braz + if i := strings.LastIndex(name, "_"); i != -1 { + if i < len(name)-1 && !startsWithUppercase(name[i+1:]) { + name = name[:i] + } + } + + if name != funcName { + continue + } + + // print code, unindent and remove surrounding braces + code := node_htmlFunc(eg.Body, fset) + code = strings.Replace(code, "\n ", "\n", -1) + code = code[2 : len(code)-2] + + err := exampleHTML.Execute(&buf, struct { + Code, Output string + }{code, eg.Output}) + if err != nil { + log.Print(err) + } + } return buf.String() } @@ -526,11 +594,15 @@ var fmap = template.FuncMap{ "node": nodeFunc, "node_html": node_htmlFunc, "comment_html": comment_htmlFunc, + "comment_text": comment_textFunc, // support for URL attributes "pkgLink": pkgLinkFunc, "srcLink": relativeURL, "posLink_url": posLink_urlFunc, + + // formatting of Examples + "example_html": example_htmlFunc, } func readTemplate(name string) *template.Template { @@ -563,11 +635,13 @@ var ( codewalkdirHTML, dirlistHTML, errorHTML, + exampleHTML, godocHTML, packageHTML, packageText, searchHTML, - searchText *template.Template + searchText, + searchDescXML *template.Template ) func readTemplates() { @@ -576,11 +650,13 @@ func readTemplates() { codewalkdirHTML = readTemplate("codewalkdir.html") dirlistHTML = readTemplate("dirlist.html") errorHTML = readTemplate("error.html") + exampleHTML = readTemplate("example.html") godocHTML = readTemplate("godoc.html") packageHTML = readTemplate("package.html") packageText = readTemplate("package.txt") searchHTML = readTemplate("search.html") searchText = readTemplate("search.txt") + searchDescXML = readTemplate("opensearch.xml") } // ---------------------------------------------------------------------------- @@ -621,19 +697,11 @@ func serveText(w http.ResponseWriter, text []byte) { // Files var ( - titleRx = regexp.MustCompile(`<!-- title ([^\-]*)-->`) - subtitleRx = regexp.MustCompile(`<!-- subtitle ([^\-]*)-->`) - firstCommentRx = regexp.MustCompile(`<!--([^\-]*)-->`) + doctype = []byte("<!DOCTYPE ") + jsonStart = []byte("<!--{") + jsonEnd = []byte("}-->") ) -func extractString(src []byte, rx *regexp.Regexp) (s string) { - m := rx.FindSubmatch(src) - if m != nil { - s = strings.TrimSpace(string(m[1])) - } - return -} - func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath string) { // get HTML body contents src, err := ReadFile(fs, abspath) @@ -645,27 +713,25 @@ func serveHTMLDoc(w http.ResponseWriter, r *http.Request, abspath, relpath strin // if it begins with "<!DOCTYPE " assume it is standalone // html that doesn't need the template wrapping. - if bytes.HasPrefix(src, []byte("<!DOCTYPE ")) { + if bytes.HasPrefix(src, doctype) { w.Write(src) return } + // if it begins with a JSON blob, read in the metadata. + meta, src, err := extractMetadata(src) + if err != nil { + log.Printf("decoding metadata %s: %v", relpath, err) + } + // if it's the language spec, add tags to EBNF productions if strings.HasSuffix(abspath, "go_spec.html") { var buf bytes.Buffer - linkify(&buf, src) + Linkify(&buf, src) src = buf.Bytes() } - // get title and subtitle, if any - title := extractString(src, titleRx) - if title == "" { - // no title found; try first comment for backward-compatibility - title = extractString(src, firstCommentRx) - } - subtitle := extractString(src, subtitleRx) - - servePage(w, title, subtitle, "", src) + servePage(w, meta.Title, meta.Subtitle, "", src) } func applyTemplate(t *template.Template, name string, data interface{}) []byte { @@ -717,21 +783,22 @@ func serveDirectory(w http.ResponseWriter, r *http.Request, abspath, relpath str } func serveFile(w http.ResponseWriter, r *http.Request) { - relpath := r.URL.Path[1:] // serveFile URL paths start with '/' - abspath := absolutePath(relpath, *goroot) + relpath := r.URL.Path - // pick off special cases and hand the rest to the standard file server - switch r.URL.Path { - case "/": - serveHTMLDoc(w, r, filepath.Join(*goroot, "doc", "root.html"), "doc/root.html") - return - - case "/doc/root.html": - // hide landing page from its real name - http.Redirect(w, r, "/", http.StatusMovedPermanently) - return + // Check to see if we need to redirect or serve another file. + if m := metadataFor(relpath); m != nil { + if m.Path != relpath { + // Redirect to canonical path. + http.Redirect(w, r, m.Path, http.StatusMovedPermanently) + return + } + // Serve from the actual filesystem path. + relpath = m.filePath } + relpath = relpath[1:] // strip leading slash + abspath := absolutePath(relpath, *goroot) + switch path.Ext(relpath) { case ".html": if strings.HasSuffix(relpath, "/index.html") { @@ -755,7 +822,7 @@ func serveFile(w http.ResponseWriter, r *http.Request) { return } - if dir != nil && dir.IsDirectory() { + if dir != nil && dir.IsDir() { if redirect(w, r) { return } @@ -775,6 +842,16 @@ func serveFile(w http.ResponseWriter, r *http.Request) { fileServer.ServeHTTP(w, r) } +func serveSearchDesc(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/opensearchdescription+xml") + data := map[string]interface{}{ + "BaseURL": fmt.Sprintf("http://%s", r.Host), + } + if err := searchDescXML.Execute(w, &data); err != nil { + log.Printf("searchDescXML.Execute: %s", err) + } +} + // ---------------------------------------------------------------------------- // Packages @@ -784,25 +861,69 @@ const fakePkgName = "documentation" // Fake relative package path for built-ins. Documentation for all globals // (not just exported ones) will be shown for packages in this directory. -const builtinPkgPath = "builtin/" +const builtinPkgPath = "builtin" type PageInfoMode uint const ( - exportsOnly PageInfoMode = 1 << iota // only keep exported stuff - genDoc // generate documentation + noFiltering PageInfoMode = 1 << iota // do not filter exports + showSource // show source code, do not extract documentation + noHtml // show result in textual form, do not generate HTML + flatDir // show directory in a flat (non-indented) manner ) +// modeNames defines names for each PageInfoMode flag. +var modeNames = map[string]PageInfoMode{ + "all": noFiltering, + "src": showSource, + "text": noHtml, + "flat": flatDir, +} + +// getPageInfoMode computes the PageInfoMode flags by analyzing the request +// URL form value "m". It is value is a comma-separated list of mode names +// as defined by modeNames (e.g.: m=src,text). +func getPageInfoMode(r *http.Request) PageInfoMode { + var mode PageInfoMode + for _, k := range strings.Split(r.FormValue("m"), ",") { + if m, found := modeNames[strings.TrimSpace(k)]; found { + mode |= m + } + } + return adjustPageInfoMode(r, mode) +} + +// Specialized versions of godoc may adjust the PageInfoMode by overriding +// this variable. +var adjustPageInfoMode = func(_ *http.Request, mode PageInfoMode) PageInfoMode { + return mode +} + +// remoteSearchURL returns the search URL for a given query as needed by +// remoteSearch. If html is set, an html result is requested; otherwise +// the result is in textual form. +// Adjust this function as necessary if modeNames or FormValue parameters +// change. +func remoteSearchURL(query string, html bool) string { + s := "/search?m=text&q=" + if html { + s = "/search?q=" + } + return s + url.QueryEscape(query) +} + type PageInfo struct { - Dirname string // directory containing the package - PList []string // list of package names found - FSet *token.FileSet // corresponding file set - PAst *ast.File // nil if no single AST with package exports - PDoc *doc.PackageDoc // nil if no single package documentation - Dirs *DirList // nil if no directory information - DirTime int64 // directory time stamp in seconds since epoch - IsPkg bool // false if this is not documenting a real package - Err os.Error // directory read error or nil + Dirname string // directory containing the package + PList []string // list of package names found + FSet *token.FileSet // corresponding file set + PAst *ast.File // nil if no single AST with package exports + PDoc *doc.Package // nil if no single package documentation + Examples []*doc.Example // nil if no example code + Dirs *DirList // nil if no directory information + DirTime time.Time // directory time stamp + DirFlat bool // if set, show directory in a flat (non-indented) manner + IsPkg bool // false if this is not documenting a real package + Err error // I/O error or nil } func (info *PageInfo) IsEmpty() bool { @@ -816,26 +937,12 @@ type httpHandler struct { } // fsReadDir implements ReadDir for the go/build package. -func fsReadDir(dir string) ([]*os.FileInfo, os.Error) { - fi, err := fs.ReadDir(dir) - if err != nil { - return nil, err - } - - // Convert []FileInfo to []*os.FileInfo. - osfi := make([]*os.FileInfo, len(fi)) - for i, f := range fi { - mode := uint32(S_IFREG) - if f.IsDirectory() { - mode = S_IFDIR - } - osfi[i] = &os.FileInfo{Name: f.Name(), Size: f.Size(), Mtime_ns: f.Mtime_ns(), Mode: mode} - } - return osfi, nil +func fsReadDir(dir string) ([]os.FileInfo, error) { + return fs.ReadDir(dir) } // fsReadFile implements ReadFile for the go/build package. -func fsReadFile(dir, name string) (path string, data []byte, err os.Error) { +func fsReadFile(dir, name string) (path string, data []byte, err error) { path = filepath.Join(dir, name) data, err = ReadFile(fs, path) return @@ -881,7 +988,7 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf } // filter function to select the desired .go files - filter := func(d FileInfo) bool { + filter := func(d os.FileInfo) bool { // Only Go files. if !isPkgFile(d) { return false @@ -958,23 +1065,44 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf plist = plist[0:i] } + // get examples from *_test.go files + var examples []*doc.Example + filter = func(d os.FileInfo) bool { + return isGoFile(d) && strings.HasSuffix(d.Name(), "_test.go") + } + if testpkgs, err := parseDir(fset, abspath, filter); err != nil { + log.Println("parsing test files:", err) + } else { + for _, testpkg := range testpkgs { + examples = append(examples, doc.Examples(testpkg)...) + } + } + // compute package documentation var past *ast.File - var pdoc *doc.PackageDoc + var pdoc *doc.Package if pkg != nil { - if mode&exportsOnly != 0 { - ast.PackageExports(pkg) - } - if mode&genDoc != 0 { - pdoc = doc.NewPackageDoc(pkg, path.Clean(relpath)) // no trailing '/' in importpath + if mode&showSource == 0 { + // show extracted documentation + var m doc.Mode + if mode&noFiltering != 0 { + m = doc.AllDecls + } + pdoc = doc.New(pkg, path.Clean(relpath), m) // no trailing '/' in importpath } else { + // show source code + // TODO(gri) Consider eliminating export filtering in this mode, + // or perhaps eliminating the mode altogether. + if mode&noFiltering == 0 { + ast.PackageExports(pkg) + } past = ast.MergePackageFiles(pkg, ast.FilterUnassociatedComments) } } // get directory information var dir *Directory - var timestamp int64 + var timestamp time.Time if tree, ts := fsTree.get(); tree != nil && tree.(*Directory) != nil { // directory tree is present; lookup respective directory // (may still fail if the file system was updated and the @@ -1011,10 +1139,22 @@ func (h *httpHandler) getPageInfo(abspath, relpath, pkgname string, mode PageInf // note: cannot use path filter here because in general // it doesn't contain the fsTree path dir = newDirectory(abspath, nil, 1) - timestamp = time.Seconds() + timestamp = time.Now() + } + + return PageInfo{ + Dirname: abspath, + PList: plist, + FSet: fset, + PAst: past, + PDoc: pdoc, + Examples: examples, + Dirs: dir.listing(true), + DirTime: timestamp, + DirFlat: mode&flatDir != 0, + IsPkg: h.isPkg, + Err: nil, } - - return PageInfo{abspath, plist, fset, past, pdoc, dir.listing(true), timestamp, h.isPkg, nil} } func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -1022,14 +1162,11 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - relpath := r.URL.Path[len(h.pattern):] + relpath := path.Clean(r.URL.Path[len(h.pattern):]) abspath := absolutePath(relpath, h.fsRoot) - var mode PageInfoMode - if relpath != builtinPkgPath { - mode = exportsOnly - } - if r.FormValue("m") != "src" { - mode |= genDoc + mode := getPageInfoMode(r) + if relpath == builtinPkgPath { + mode = noFiltering } info := h.getPageInfo(abspath, relpath, r.FormValue("p"), mode) if info.Err != nil { @@ -1038,7 +1175,7 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } - if r.FormValue("f") == "text" { + if mode&noHtml != 0 { contents := applyTemplate(packageText, "packageText", info) serveText(w, contents) return @@ -1051,18 +1188,18 @@ func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { case info.PDoc != nil: switch { case info.IsPkg: - title = "Package " + info.PDoc.PackageName - case info.PDoc.PackageName == fakePkgName: + title = "Package " + info.PDoc.Name + case info.PDoc.Name == fakePkgName: // assume that the directory name is the command name - _, pkgname := path.Split(path.Clean(relpath)) + _, pkgname := path.Split(relpath) title = "Command " + pkgname default: - title = "Command " + info.PDoc.PackageName + title = "Command " + info.PDoc.Name } default: title = "Directory " + relativeURL(info.Dirname) if *showTimestamps { - subtitle = "Last update: " + time.SecondsToLocalTime(info.DirTime).String() + subtitle = "Last update: " + info.DirTime.String() } } @@ -1098,12 +1235,12 @@ func lookup(query string) (result SearchResult) { index := index.(*Index) // identifier search - var err os.Error + var err error result.Pak, result.Hit, result.Alt, err = index.Lookup(query) if err != nil && *maxResults <= 0 { // ignore the error if full text search is enabled // since the query may be a valid regular expression - result.Alert = "Error in query string: " + err.String() + result.Alert = "Error in query string: " + err.Error() return } @@ -1111,7 +1248,7 @@ func lookup(query string) (result SearchResult) { if *maxResults > 0 && query != "" { rx, err := regexp.Compile(query) if err != nil { - result.Alert = "Error in query regular expression: " + err.String() + result.Alert = "Error in query regular expression: " + err.Error() return } // If we get maxResults+1 results we know that there are more than @@ -1128,7 +1265,7 @@ func lookup(query string) (result SearchResult) { // is the result accurate? if *indexEnabled { - if _, ts := fsModified.get(); timestamp < ts { + if _, ts := fsModified.get(); timestamp.Before(ts) { // The index is older than the latest file system change under godoc's observation. result.Alert = "Indexing in progress: result may be inaccurate" } @@ -1143,7 +1280,7 @@ func search(w http.ResponseWriter, r *http.Request) { query := strings.TrimSpace(r.FormValue("q")) result := lookup(query) - if r.FormValue("f") == "text" { + if getPageInfoMode(r)&noHtml != 0 { contents := applyTemplate(searchText, "searchText", result) serveText(w, contents) return @@ -1161,6 +1298,120 @@ func search(w http.ResponseWriter, r *http.Request) { } // ---------------------------------------------------------------------------- +// Documentation Metadata + +type Metadata struct { + Title string + Subtitle string + Path string // canonical path for this page + filePath string // filesystem path relative to goroot +} + +// extractMetadata extracts the Metadata from a byte slice. +// It returns the Metadata value and the remaining data. +// If no metadata is present the original byte slice is returned. +// +func extractMetadata(b []byte) (meta Metadata, tail []byte, err error) { + tail = b + if !bytes.HasPrefix(b, jsonStart) { + return + } + end := bytes.Index(b, jsonEnd) + if end < 0 { + return + } + b = b[len(jsonStart)-1 : end+1] // drop leading <!-- and include trailing } + if err = json.Unmarshal(b, &meta); err != nil { + return + } + tail = tail[end+len(jsonEnd):] + return +} + +// updateMetadata scans $GOROOT/doc for HTML files, reads their metadata, +// and updates the docMetadata map. +// +func updateMetadata() { + metadata := make(map[string]*Metadata) + var scan func(string) // scan is recursive + scan = func(dir string) { + fis, err := fs.ReadDir(dir) + if err != nil { + log.Println("updateMetadata:", err) + return + } + for _, fi := range fis { + name := filepath.Join(dir, fi.Name()) + if fi.IsDir() { + scan(name) // recurse + continue + } + if !strings.HasSuffix(name, ".html") { + continue + } + // Extract metadata from the file. + b, err := ReadFile(fs, name) + if err != nil { + log.Printf("updateMetadata %s: %v", name, err) + continue + } + meta, _, err := extractMetadata(b) + if err != nil { + log.Printf("updateMetadata: %s: %v", name, err) + continue + } + // Store relative filesystem path in Metadata. + meta.filePath = filepath.Join("/", name[len(*goroot):]) + if meta.Path == "" { + // If no Path, canonical path is actual path. + meta.Path = meta.filePath + } + // Store under both paths. + metadata[meta.Path] = &meta + metadata[meta.filePath] = &meta + } + } + scan(filepath.Join(*goroot, "doc")) + docMetadata.set(metadata) +} + +// Send a value on this channel to trigger a metadata refresh. +// It is buffered so that if a signal is not lost if sent during a refresh. +// +var refreshMetadataSignal = make(chan bool, 1) + +// refreshMetadata sends a signal to update docMetadata. If a refresh is in +// progress the metadata will be refreshed again afterward. +// +func refreshMetadata() { + select { + case refreshMetadataSignal <- true: + default: + } +} + +// refreshMetadataLoop runs forever, updating docMetadata when the underlying +// file system changes. It should be launched in a goroutine by main. +// +func refreshMetadataLoop() { + for { + <-refreshMetadataSignal + updateMetadata() + time.Sleep(10 * time.Second) // at most once every 10 seconds + } +} + +// metadataFor returns the *Metadata for a given relative path or nil if none +// exists. +// +func metadataFor(relpath string) *Metadata { + if m, _ := docMetadata.get(); m != nil { + return m.(map[string]*Metadata)[relpath] + } + return nil +} + +// ---------------------------------------------------------------------------- // Indexer // invalidateIndex should be called whenever any of the file systems @@ -1168,6 +1419,7 @@ func search(w http.ResponseWriter, r *http.Request) { // func invalidateIndex() { fsModified.set(nil) + refreshMetadata() } // indexUpToDate() returns true if the search index is not older @@ -1176,7 +1428,7 @@ func invalidateIndex() { func indexUpToDate() bool { _, fsTime := fsModified.get() _, siTime := searchIndex.get() - return fsTime <= siTime + return !fsTime.After(siTime) } // feedDirnames feeds the directory names of all directories @@ -1206,7 +1458,7 @@ func fsDirnames() <-chan string { return c } -func readIndex(filenames string) os.Error { +func readIndex(filenames string) error { matches, err := filepath.Glob(filenames) if err != nil { return err @@ -1233,12 +1485,12 @@ func updateIndex() { if *verbose { log.Printf("updating index...") } - start := time.Nanoseconds() + start := time.Now() index := NewIndex(fsDirnames(), *maxResults > 0, *indexThrottle) - stop := time.Nanoseconds() + stop := time.Now() searchIndex.set(index) if *verbose { - secs := float64((stop-start)/1e6) / 1e3 + secs := stop.Sub(start).Seconds() stats := index.Stats() log.Printf("index updated (%gs, %d bytes of source, %d files, %d lines, %d unique words, %d spots)", secs, stats.Bytes, stats.Files, stats.Lines, stats.Words, stats.Spots) @@ -1262,10 +1514,10 @@ func indexer() { // index possibly out of date - make a new one updateIndex() } - var delay int64 = 60 * 1e9 // by default, try every 60s + delay := 60 * time.Second // by default, try every 60s if *testDir != "" { // in test mode, try once a second for fast startup - delay = 1 * 1e9 + delay = 1 * time.Second } time.Sleep(delay) } diff --git a/src/cmd/godoc/httpzip.go b/src/cmd/godoc/httpzip.go index cb8322ee4..9f3da0874 100644 --- a/src/cmd/godoc/httpzip.go +++ b/src/cmd/godoc/httpzip.go @@ -26,21 +26,27 @@ package main import ( "archive/zip" "fmt" - "http" "io" + "net/http" "os" "path" "sort" "strings" + "time" ) -// We cannot import syscall on app engine. -// TODO(gri) Once we have a truly abstract FileInfo implementation -// this won't be needed anymore. -const ( - S_IFDIR = 0x4000 // == syscall.S_IFDIR - S_IFREG = 0x8000 // == syscall.S_IFREG -) +type fileInfo struct { + name string + mode os.FileMode + size int64 + mtime time.Time +} + +func (fi *fileInfo) Name() string { return fi.name } +func (fi *fileInfo) Mode() os.FileMode { return fi.mode } +func (fi *fileInfo) Size() int64 { return fi.size } +func (fi *fileInfo) ModTime() time.Time { return fi.mtime } +func (fi *fileInfo) IsDir() bool { return fi.mode.IsDir() } // httpZipFile is the zip-file based implementation of http.File type httpZipFile struct { @@ -50,19 +56,19 @@ type httpZipFile struct { list zipList } -func (f *httpZipFile) Close() os.Error { - if f.info.IsRegular() { +func (f *httpZipFile) Close() error { + if !f.info.IsDir() { return f.ReadCloser.Close() } f.list = nil return nil } -func (f *httpZipFile) Stat() (*os.FileInfo, os.Error) { - return &f.info, nil +func (f *httpZipFile) Stat() (os.FileInfo, error) { + return f.info, nil } -func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) { +func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, error) { var list []os.FileInfo dirname := f.path + "/" prevname := "" @@ -76,29 +82,30 @@ func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) { break // not in the same directory anymore } name := e.Name[len(dirname):] // local name - var mode uint32 - var size, mtime_ns int64 + var mode os.FileMode + var size int64 + var mtime time.Time if i := strings.IndexRune(name, '/'); i >= 0 { // We infer directories from files in subdirectories. // If we have x/y, return a directory entry for x. name = name[0:i] // keep local directory name only - mode = S_IFDIR - // no size or mtime_ns for directories + mode = os.ModeDir + // no size or mtime for directories } else { - mode = S_IFREG + mode = 0 size = int64(e.UncompressedSize) - mtime_ns = e.Mtime_ns() + mtime = e.ModTime() } // If we have x/y and x/z, don't return two directory entries for x. // TODO(gri): It should be possible to do this more efficiently // by determining the (fs.list) range of local directory entries // (via two binary searches). if name != prevname { - list = append(list, os.FileInfo{ - Name: name, - Mode: mode, - Size: size, - Mtime_ns: mtime_ns, + list = append(list, &fileInfo{ + name, + mode, + size, + mtime, }) prevname = name count-- @@ -106,14 +113,14 @@ func (f *httpZipFile) Readdir(count int) ([]os.FileInfo, os.Error) { } if count >= 0 && len(list) == 0 { - return nil, os.EOF + return nil, io.EOF } return list, nil } -func (f *httpZipFile) Seek(offset int64, whence int) (int64, os.Error) { - return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.info.Name) +func (f *httpZipFile) Seek(offset int64, whence int) (int64, error) { + return 0, fmt.Errorf("Seek not implemented for zip file entry: %s", f.info.Name()) } // httpZipFS is the zip-file based implementation of http.FileSystem @@ -123,7 +130,7 @@ type httpZipFS struct { root string } -func (fs *httpZipFS) Open(name string) (http.File, os.Error) { +func (fs *httpZipFS) Open(name string) (http.File, error) { // fs.root does not start with '/'. path := path.Join(fs.root, name) // path is clean index, exact := fs.list.lookup(path) @@ -141,11 +148,11 @@ func (fs *httpZipFS) Open(name string) (http.File, os.Error) { } return &httpZipFile{ path, - os.FileInfo{ - Name: name, - Mode: S_IFREG, - Size: int64(f.UncompressedSize), - Mtime_ns: f.Mtime_ns(), + &fileInfo{ + name, + 0, + int64(f.UncompressedSize), + f.ModTime(), }, rc, nil, @@ -155,17 +162,18 @@ func (fs *httpZipFS) Open(name string) (http.File, os.Error) { // not an exact match - must be a directory return &httpZipFile{ path, - os.FileInfo{ - Name: name, - Mode: S_IFDIR, - // no size or mtime_ns for directories + &fileInfo{ + name, + os.ModeDir, + 0, // no size for directory + time.Time{}, // no mtime for directory }, nil, fs.list[index:], }, nil } -func (fs *httpZipFS) Close() os.Error { +func (fs *httpZipFS) Close() error { fs.list = nil return fs.ReadCloser.Close() } diff --git a/src/cmd/godoc/index.go b/src/cmd/godoc/index.go index 2543f9216..3d2c3ff96 100644 --- a/src/cmd/godoc/index.go +++ b/src/cmd/godoc/index.go @@ -38,19 +38,22 @@ package main import ( + "bufio" "bytes" + "encoding/gob" + "errors" "go/ast" "go/parser" - "go/token" "go/scanner" - "gob" + "go/token" "index/suffixarray" "io" "os" "path/filepath" - "exp/regexp" + "regexp" "sort" "strings" + "time" ) // ---------------------------------------------------------------------------- @@ -700,8 +703,8 @@ func isWhitelisted(filename string) bool { return whitelisted[key] } -func (x *Indexer) visitFile(dirname string, f FileInfo, fulltextIndex bool) { - if !f.IsRegular() { +func (x *Indexer) visitFile(dirname string, f os.FileInfo, fulltextIndex bool) { + if f.IsDir() { return } @@ -765,7 +768,7 @@ func canonical(w string) string { return strings.ToLower(w) } // func NewIndex(dirnames <-chan string, fulltextIndex bool, throttle float64) *Index { var x Indexer - th := NewThrottle(throttle, 0.1e9) // run at least 0.1s at a time + th := NewThrottle(throttle, 100*time.Millisecond) // run at least 0.1s at a time // initialize Indexer // (use some reasonably sized maps to start) @@ -780,7 +783,7 @@ func NewIndex(dirnames <-chan string, fulltextIndex bool, throttle float64) *Ind continue // ignore this directory } for _, f := range list { - if !f.IsDirectory() { + if !f.IsDir() { x.visitFile(dirname, f, fulltextIndex) } th.Throttle() @@ -840,8 +843,16 @@ type fileIndex struct { Fulltext bool } +func (x *fileIndex) Write(w io.Writer) error { + return gob.NewEncoder(w).Encode(x) +} + +func (x *fileIndex) Read(r io.Reader) error { + return gob.NewDecoder(r).Decode(x) +} + // Write writes the index x to w. -func (x *Index) Write(w io.Writer) os.Error { +func (x *Index) Write(w io.Writer) error { fulltext := false if x.suffixes != nil { fulltext = true @@ -852,7 +863,7 @@ func (x *Index) Write(w io.Writer) os.Error { x.snippets, fulltext, } - if err := gob.NewEncoder(w).Encode(fx); err != nil { + if err := fx.Write(w); err != nil { return err } if fulltext { @@ -867,9 +878,14 @@ func (x *Index) Write(w io.Writer) os.Error { } // Read reads the index from r into x; x must not be nil. -func (x *Index) Read(r io.Reader) os.Error { +// If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader. +func (x *Index) Read(r io.Reader) error { + // We use the ability to read bytes as a plausible surrogate for buffering. + if _, ok := r.(io.ByteReader); !ok { + r = bufio.NewReader(r) + } var fx fileIndex - if err := gob.NewDecoder(r).Decode(&fx); err != nil { + if err := fx.Read(r); err != nil { return err } x.words = fx.Words @@ -920,13 +936,13 @@ func isIdentifier(s string) bool { // identifier, Lookup returns a list of packages, a LookupResult, and a // list of alternative spellings, if any. Any and all results may be nil. // If the query syntax is wrong, an error is reported. -func (x *Index) Lookup(query string) (paks HitList, match *LookupResult, alt *AltWords, err os.Error) { +func (x *Index) Lookup(query string) (paks HitList, match *LookupResult, alt *AltWords, err error) { ss := strings.Split(query, ".") // check query syntax for _, s := range ss { if !isIdentifier(s) { - err = os.NewError("all query parts must be identifiers") + err = errors.New("all query parts must be identifiers") return } } @@ -954,7 +970,7 @@ func (x *Index) Lookup(query string) (paks HitList, match *LookupResult, alt *Al } default: - err = os.NewError("query is not a (qualified) identifier") + err = errors.New("query is not a (qualified) identifier") } return diff --git a/src/cmd/godoc/main.go b/src/cmd/godoc/main.go index 15d70c49b..f74b6f404 100644 --- a/src/cmd/godoc/main.go +++ b/src/cmd/godoc/main.go @@ -28,23 +28,23 @@ package main import ( "archive/zip" "bytes" + "errors" _ "expvar" // to serve /debug/vars "flag" "fmt" "go/ast" "go/build" - "http" - _ "http/pprof" // to serve /debug/pprof/* "io" "log" + "net/http" + _ "net/http/pprof" // to serve /debug/pprof/* "os" "path" "path/filepath" - "exp/regexp" + "regexp" "runtime" "strings" "time" - "url" ) const defaultAddr = ":6060" // default webserver address @@ -74,7 +74,7 @@ var ( query = flag.Bool("q", false, "arguments are considered search queries") ) -func serveError(w http.ResponseWriter, r *http.Request, relpath string, err os.Error) { +func serveError(w http.ResponseWriter, r *http.Request, relpath string, err error) { contents := applyTemplate(errorHTML, "errorHTML", err) // err may contain an absolute path! w.WriteHeader(http.StatusNotFound) servePage(w, "File "+relpath, "", "", contents) @@ -141,10 +141,10 @@ func dosync(w http.ResponseWriter, r *http.Request) { case 1: // sync failed because no files changed; // don't change the package tree - syncDelay.set(*syncMin) // revert to regular sync schedule + syncDelay.set(time.Duration(*syncMin) * time.Minute) // revert to regular sync schedule default: // sync failed because of an error - back off exponentially, but try at least once a day - syncDelay.backoff(24 * 60) + syncDelay.backoff(24 * time.Hour) } } @@ -163,9 +163,7 @@ func loggingHandler(h http.Handler) http.Handler { }) } -func remoteSearch(query string) (res *http.Response, err os.Error) { - search := "/search?f=text&q=" + url.QueryEscape(query) - +func remoteSearch(query string) (res *http.Response, err error) { // list of addresses to try var addrs []string if *serverAddr != "" { @@ -179,6 +177,7 @@ func remoteSearch(query string) (res *http.Response, err os.Error) { } // remote search + search := remoteSearchURL(query, *html) for _, addr := range addrs { url := "http://" + addr + search res, err = http.Get(url) @@ -188,7 +187,7 @@ func remoteSearch(query string) (res *http.Response, err os.Error) { } if err == nil && res.StatusCode != http.StatusOK { - err = os.NewError(res.Status) + err = errors.New(res.Status) } return @@ -329,14 +328,20 @@ func main() { for { dosync(nil, nil) delay, _ := syncDelay.get() + dt := delay.(time.Duration) if *verbose { - log.Printf("next sync in %dmin", delay.(int)) + log.Printf("next sync in %s", dt) } - time.Sleep(int64(delay.(int)) * 60e9) + time.Sleep(dt) } }() } + // Immediately update metadata. + updateMetadata() + // Periodically refresh metadata. + go refreshMetadataLoop() + // Initialize search index. if *indexEnabled { go indexer() @@ -387,13 +392,15 @@ func main() { } var mode PageInfoMode + if relpath == builtinPkgPath { + mode = noFiltering + } if *srcMode { // only filter exports if we don't have explicit command-line filter arguments - if flag.NArg() == 1 { - mode |= exportsOnly + if flag.NArg() > 1 { + mode |= noFiltering } - } else { - mode = exportsOnly | genDoc + mode |= showSource } // TODO(gri): Provide a mechanism (flag?) to select a package // if there are multiple packages in a directory. diff --git a/src/cmd/godoc/mapping.go b/src/cmd/godoc/mapping.go index 51f23ab98..89e531e2f 100644 --- a/src/cmd/godoc/mapping.go +++ b/src/cmd/godoc/mapping.go @@ -139,13 +139,18 @@ func (m *Mapping) Fprint(w io.Writer) { } } +// splitFirst splits a path at the first path separator and returns +// the path's head (the top-most directory specified by the path) and +// its tail (the rest of the path). If there is no path separator, +// splitFirst returns path as head, and the the empty string as tail. +// Specifically, splitFirst("foo") == splitFirst("foo/"). +// func splitFirst(path string) (head, tail string) { - i := strings.Index(path, string(filepath.Separator)) - if i > 0 { + if i := strings.Index(path, string(filepath.Separator)); i > 0 { // 0 < i < len(path) return path[0:i], path[i+1:] } - return "", path + return path, "" } // ToAbsolute maps a slash-separated relative path to an absolute filesystem @@ -156,20 +161,14 @@ func (m *Mapping) ToAbsolute(spath string) string { fpath := filepath.FromSlash(spath) prefix, tail := splitFirst(fpath) for _, e := range m.list { - switch { - case e.prefix == prefix: - // use tail - case e.prefix == "": - tail = fpath - default: - continue // no match - } - abspath := filepath.Join(e.path, tail) - if _, err := fs.Stat(abspath); err == nil { - return abspath + if e.prefix == prefix { + // found potential mapping + abspath := filepath.Join(e.path, tail) + if _, err := fs.Stat(abspath); err == nil { + return abspath + } } } - return "" // no match } diff --git a/src/cmd/godoc/parser.go b/src/cmd/godoc/parser.go index a2920539f..da38c5265 100644 --- a/src/cmd/godoc/parser.go +++ b/src/cmd/godoc/parser.go @@ -17,7 +17,7 @@ import ( "path/filepath" ) -func parseFile(fset *token.FileSet, filename string, mode uint) (*ast.File, os.Error) { +func parseFile(fset *token.FileSet, filename string, mode parser.Mode) (*ast.File, error) { src, err := ReadFile(fs, filename) if err != nil { return nil, err @@ -25,7 +25,7 @@ func parseFile(fset *token.FileSet, filename string, mode uint) (*ast.File, os.E return parser.ParseFile(fset, filename, src, mode) } -func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.Package, first os.Error) { +func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.Package, first error) { pkgs = make(map[string]*ast.Package) for _, filename := range filenames { file, err := parseFile(fset, filename, parser.ParseComments) @@ -48,7 +48,7 @@ func parseFiles(fset *token.FileSet, filenames []string) (pkgs map[string]*ast.P return } -func parseDir(fset *token.FileSet, path string, filter func(FileInfo) bool) (map[string]*ast.Package, os.Error) { +func parseDir(fset *token.FileSet, path string, filter func(os.FileInfo) bool) (map[string]*ast.Package, error) { list, err := fs.ReadDir(path) if err != nil { return nil, err diff --git a/src/cmd/godoc/setup-godoc-app.bash b/src/cmd/godoc/setup-godoc-app.bash new file mode 100644 index 000000000..755d965d5 --- /dev/null +++ b/src/cmd/godoc/setup-godoc-app.bash @@ -0,0 +1,121 @@ +#!/usr/bin/env bash + +# Copyright 2011 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# This script creates the .zip, index, and configuration files for running +# godoc on app-engine. +# +# If an argument is provided it is assumed to be the app-engine godoc directory. +# Without an argument, $APPDIR is used instead. If GOROOT is not set, the +# current working directory is assumed to be $GOROOT. Various sanity checks +# prevent accidents. +# +# The script creates a .zip file representing the $GOROOT file system +# and computes the correspondig search index files. These files are then +# copied to $APPDIR. A corresponding godoc configuration file is created +# in $APPDIR/appconfig.go. + +ZIPFILE=godoc.zip +INDEXFILE=godoc.index +SPLITFILES=index.split. +CONFIGFILE=godoc/appconfig.go + +error() { + echo "error: $1" + exit 2 +} + +getArgs() { + if [ -z $GOROOT ]; then + GOROOT=$(pwd) + echo "GOROOT not set, using cwd instead" + fi + if [ -z $APPDIR ]; then + if [ $# == 0 ]; then + error "APPDIR not set, and no argument provided" + fi + APPDIR=$1 + echo "APPDIR not set, using argument instead" + fi + + # safety checks + if [ ! -d $GOROOT ]; then + error "$GOROOT is not a directory" + fi + if [ ! -x $GOROOT/src/cmd/godoc/godoc ]; then + error "$GOROOT/src/cmd/godoc/godoc does not exist or is not executable" + fi + if [ ! -d $APPDIR ]; then + error "$APPDIR is not a directory" + fi + if [ ! -e $APPDIR/app.yaml ]; then + error "$APPDIR is not an app-engine directory; missing file app.yaml" + fi + if [ ! -d $APPDIR/godoc ]; then + error "$APPDIR is missing directory godoc" + fi + + # reporting + echo "GOROOT = $GOROOT" + echo "APPDIR = $APPDIR" +} + +cleanup() { + echo "*** cleanup $APPDIR" + rm $APPDIR/$ZIPFILE + rm $APPDIR/$INDEXFILE + rm $APPDIR/$SPLITFILES* + rm $APPDIR/$CONFIGFILE +} + +makeZipfile() { + echo "*** make $APPDIR/$ZIPFILE" + zip -q -r $APPDIR/$ZIPFILE $GOROOT -i \*.go -i \*.html -i \*.css -i \*.js -i \*.txt -i \*.c -i \*.h -i \*.s -i \*.png -i \*.jpg -i \*.sh -i \*.ico +} + +makeIndexfile() { + echo "*** make $APPDIR/$INDEXFILE" + OUT=/tmp/godoc.out + $GOROOT/src/cmd/godoc/godoc -write_index -index_files=$APPDIR/$INDEXFILE -zip=$APPDIR/$ZIPFILE 2> $OUT + if [ $? != 0 ]; then + error "$GOROOT/src/cmd/godoc/godoc failed - see $OUT for details" + fi +} + +splitIndexfile() { + echo "*** split $APPDIR/$INDEXFILE" + split -b8m $APPDIR/$INDEXFILE $APPDIR/$SPLITFILES +} + +makeConfigfile() { + echo "*** make $APPDIR/$CONFIGFILE" + cat > $APPDIR/$CONFIGFILE <<EOF +package main + +// GENERATED FILE - DO NOT MODIFY BY HAND. +// (generated by $GOROOT/src/cmd/godoc/setup-godoc-app.bash) + +const ( + // .zip filename + zipFilename = "$ZIPFILE" + + // goroot directory in .zip file + zipGoroot = "$GOROOT" + + // glob pattern describing search index files + // (if empty, the index is built at run-time) + indexFilenames = "$SPLITFILES*" +) +EOF +} + +getArgs "$@" +cleanup +makeZipfile +makeIndexfile +splitIndexfile +makeConfigfile + +echo "*** setup complete" diff --git a/src/cmd/godoc/snippet.go b/src/cmd/godoc/snippet.go index 68e27d9a0..c2b74ee52 100644 --- a/src/cmd/godoc/snippet.go +++ b/src/cmd/godoc/snippet.go @@ -11,9 +11,9 @@ package main import ( "bytes" + "fmt" "go/ast" "go/token" - "fmt" ) type Snippet struct { diff --git a/src/cmd/godoc/spec.go b/src/cmd/godoc/spec.go index 3f69add86..c11f25d20 100644 --- a/src/cmd/godoc/spec.go +++ b/src/cmd/godoc/spec.go @@ -2,118 +2,103 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +package main + // This file contains the mechanism to "linkify" html source // text containing EBNF sections (as found in go_spec.html). // The result is the input source text with the EBNF sections // modified such that identifiers are linked to the respective // definitions. -package main - import ( "bytes" "fmt" - "go/scanner" - "go/token" "io" + "text/scanner" ) type ebnfParser struct { - out io.Writer // parser output - src []byte // parser source - file *token.File // for position information + out io.Writer // parser output + src []byte // parser input scanner scanner.Scanner - prev int // offset of previous token - pos token.Pos // token position - tok token.Token // one token look-ahead - lit string // token literal + prev int // offset of previous token + pos int // offset of current token + tok rune // one token look-ahead + lit string // token literal } func (p *ebnfParser) flush() { - offs := p.file.Offset(p.pos) - p.out.Write(p.src[p.prev:offs]) - p.prev = offs + p.out.Write(p.src[p.prev:p.pos]) + p.prev = p.pos } func (p *ebnfParser) next() { - if p.pos.IsValid() { - p.flush() - } - p.pos, p.tok, p.lit = p.scanner.Scan() - if p.tok.IsKeyword() { - // TODO Should keyword mapping always happen outside scanner? - // Or should there be a flag to scanner to enable keyword mapping? - p.tok = token.IDENT - } + p.tok = p.scanner.Scan() + p.pos = p.scanner.Position.Offset + p.lit = p.scanner.TokenText() } -func (p *ebnfParser) Error(pos token.Position, msg string) { - fmt.Fprintf(p.out, `<span class="alert">error: %s</span>`, msg) +func (p *ebnfParser) printf(format string, args ...interface{}) { + p.flush() + fmt.Fprintf(p.out, format, args...) } -func (p *ebnfParser) errorExpected(pos token.Pos, msg string) { - msg = "expected " + msg - if pos == p.pos { - // the error happened at the current position; - // make the error message more specific - msg += ", found '" + p.tok.String() + "'" - if p.tok.IsLiteral() { - msg += " " + p.lit - } - } - p.Error(p.file.Position(pos), msg) +func (p *ebnfParser) errorExpected(msg string) { + p.printf(`<span class="highlight">error: expected %s, found %s</span>`, msg, scanner.TokenString(p.tok)) } -func (p *ebnfParser) expect(tok token.Token) token.Pos { - pos := p.pos +func (p *ebnfParser) expect(tok rune) { if p.tok != tok { - p.errorExpected(pos, "'"+tok.String()+"'") + p.errorExpected(scanner.TokenString(tok)) } p.next() // make progress in any case - return pos } func (p *ebnfParser) parseIdentifier(def bool) { - name := p.lit - p.expect(token.IDENT) - if def { - fmt.Fprintf(p.out, `<a id="%s">%s</a>`, name, name) + if p.tok == scanner.Ident { + name := p.lit + if def { + p.printf(`<a id="%s">%s</a>`, name, name) + } else { + p.printf(`<a href="#%s" class="noline">%s</a>`, name, name) + } + p.prev += len(name) // skip identifier when printing next time + p.next() } else { - fmt.Fprintf(p.out, `<a href="#%s" class="noline">%s</a>`, name, name) + p.expect(scanner.Ident) } - p.prev += len(name) // skip identifier when calling flush } func (p *ebnfParser) parseTerm() bool { switch p.tok { - case token.IDENT: + case scanner.Ident: p.parseIdentifier(false) - case token.STRING: + case scanner.String: p.next() - const ellipsis = "…" // U+2026, the horizontal ellipsis character - if p.tok == token.ILLEGAL && p.lit == ellipsis { + const ellipsis = '…' // U+2026, the horizontal ellipsis character + if p.tok == ellipsis { p.next() - p.expect(token.STRING) + p.expect(scanner.String) } - case token.LPAREN: + case '(': p.next() p.parseExpression() - p.expect(token.RPAREN) + p.expect(')') - case token.LBRACK: + case '[': p.next() p.parseExpression() - p.expect(token.RBRACK) + p.expect(']') - case token.LBRACE: + case '{': p.next() p.parseExpression() - p.expect(token.RBRACE) + p.expect('}') default: - return false + return false // no term found } return true @@ -121,7 +106,7 @@ func (p *ebnfParser) parseTerm() bool { func (p *ebnfParser) parseSequence() { if !p.parseTerm() { - p.errorExpected(p.pos, "term") + p.errorExpected("term") } for p.parseTerm() { } @@ -130,7 +115,7 @@ func (p *ebnfParser) parseSequence() { func (p *ebnfParser) parseExpression() { for { p.parseSequence() - if p.tok != token.OR { + if p.tok != '|' { break } p.next() @@ -139,23 +124,22 @@ func (p *ebnfParser) parseExpression() { func (p *ebnfParser) parseProduction() { p.parseIdentifier(true) - p.expect(token.ASSIGN) - if p.tok != token.PERIOD { + p.expect('=') + if p.tok != '.' { p.parseExpression() } - p.expect(token.PERIOD) + p.expect('.') } -func (p *ebnfParser) parse(fset *token.FileSet, out io.Writer, src []byte) { +func (p *ebnfParser) parse(out io.Writer, src []byte) { // initialize ebnfParser p.out = out p.src = src - p.file = fset.AddFile("", fset.Base(), len(src)) - p.scanner.Init(p.file, src, p, scanner.AllowIllegalChars) + p.scanner.Init(bytes.NewBuffer(src)) p.next() // initializes pos, tok, lit // process source - for p.tok != token.EOF { + for p.tok != scanner.EOF { p.parseProduction() } p.flush() @@ -167,32 +151,29 @@ var ( closeTag = []byte(`</pre>`) ) -func linkify(out io.Writer, src []byte) { - fset := token.NewFileSet() +func Linkify(out io.Writer, src []byte) { for len(src) > 0 { - n := len(src) - // i: beginning of EBNF text (or end of source) i := bytes.Index(src, openTag) if i < 0 { - i = n - len(openTag) + i = len(src) - len(openTag) } i += len(openTag) // j: end of EBNF text (or end of source) - j := bytes.Index(src[i:n], closeTag) // close marker + j := bytes.Index(src[i:], closeTag) // close marker if j < 0 { - j = n - i + j = len(src) - i } j += i // write text before EBNF out.Write(src[0:i]) - // parse and write EBNF + // process EBNF var p ebnfParser - p.parse(fset, out, src[i:j]) + p.parse(out, src[i:j]) // advance - src = src[j:n] + src = src[j:] } } diff --git a/src/cmd/godoc/throttle.go b/src/cmd/godoc/throttle.go index 193492802..ac18b44e0 100644 --- a/src/cmd/godoc/throttle.go +++ b/src/cmd/godoc/throttle.go @@ -10,15 +10,15 @@ import "time" // calling the Throttle method repeatedly. // type Throttle struct { - f float64 // f = (1-r)/r for 0 < r < 1 - tm int64 // minimum run time slice; >= 0 - tr int64 // accumulated time running - ts int64 // accumulated time stopped - tt int64 // earliest throttle time (= time Throttle returned + tm) + f float64 // f = (1-r)/r for 0 < r < 1 + dt time.Duration // minimum run time slice; >= 0 + tr time.Duration // accumulated time running + ts time.Duration // accumulated time stopped + tt time.Time // earliest throttle time (= time Throttle returned + tm) } // NewThrottle creates a new Throttle with a throttle value r and -// a minimum allocated run time slice of tm nanoseconds: +// a minimum allocated run time slice of dt: // // r == 0: "empty" throttle; the goroutine is always sleeping // r == 1: full throttle; the goroutine is never sleeping @@ -26,9 +26,9 @@ type Throttle struct { // A value of r == 0.6 throttles a goroutine such that it runs // approx. 60% of the time, and sleeps approx. 40% of the time. // Values of r < 0 or r > 1 are clamped down to values between 0 and 1. -// Values of tm < 0 are set to 0. +// Values of dt < 0 are set to 0. // -func NewThrottle(r float64, tm int64) *Throttle { +func NewThrottle(r float64, dt time.Duration) *Throttle { var f float64 switch { case r <= 0: @@ -39,10 +39,10 @@ func NewThrottle(r float64, tm int64) *Throttle { // 0 < r < 1 f = (1 - r) / r } - if tm < 0 { - tm = 0 + if dt < 0 { + dt = 0 } - return &Throttle{f: f, tm: tm, tt: time.Nanoseconds() + tm} + return &Throttle{f: f, dt: dt, tt: time.Now().Add(dt)} } // Throttle calls time.Sleep such that over time the ratio tr/ts between @@ -55,13 +55,13 @@ func (p *Throttle) Throttle() { select {} // always sleep } - t0 := time.Nanoseconds() - if t0 < p.tt { + t0 := time.Now() + if t0.Before(p.tt) { return // keep running (minimum time slice not exhausted yet) } // accumulate running time - p.tr += t0 - (p.tt - p.tm) + p.tr += t0.Sub(p.tt) + p.dt // compute sleep time // Over time we want: @@ -75,14 +75,14 @@ func (p *Throttle) Throttle() { // After some incremental run time δr added to the total run time // tr, the incremental sleep-time δs to get to the same ratio again // after waking up from time.Sleep is: - if δs := int64(float64(p.tr)*p.f) - p.ts; δs > 0 { + if δs := time.Duration(float64(p.tr)*p.f) - p.ts; δs > 0 { time.Sleep(δs) } // accumulate (actual) sleep time - t1 := time.Nanoseconds() - p.ts += t1 - t0 + t1 := time.Now() + p.ts += t1.Sub(t0) // set earliest next throttle time - p.tt = t1 + p.tm + p.tt = t1.Add(p.dt) } diff --git a/src/cmd/godoc/utils.go b/src/cmd/godoc/utils.go index 11e46aee5..be0bdc306 100644 --- a/src/cmd/godoc/utils.go +++ b/src/cmd/godoc/utils.go @@ -15,7 +15,7 @@ import ( "strings" "sync" "time" - "utf8" + "unicode/utf8" ) // An RWValue wraps a value and permits mutually exclusive @@ -24,17 +24,17 @@ import ( type RWValue struct { mutex sync.RWMutex value interface{} - timestamp int64 // time of last set(), in seconds since epoch + timestamp time.Time // time of last set() } func (v *RWValue) set(value interface{}) { v.mutex.Lock() v.value = value - v.timestamp = time.Seconds() + v.timestamp = time.Now() v.mutex.Unlock() } -func (v *RWValue) get() (interface{}, int64) { +func (v *RWValue) get() (interface{}, time.Time) { v.mutex.RLock() defer v.mutex.RUnlock() return v.value, v.timestamp @@ -93,7 +93,7 @@ func canonicalizePaths(list []string, filter func(path string) bool) []string { // writeFileAtomically writes data to a temporary file and then // atomically renames that file to the file named by filename. // -func writeFileAtomically(filename string, data []byte) os.Error { +func writeFileAtomically(filename string, data []byte) error { // TODO(gri) this won't work on appengine f, err := ioutil.TempFile(filepath.Split(filename)) if err != nil { diff --git a/src/cmd/godoc/zip.go b/src/cmd/godoc/zip.go index 86cd79b17..cd38ed92b 100644 --- a/src/cmd/godoc/zip.go +++ b/src/cmd/godoc/zip.go @@ -26,6 +26,7 @@ import ( "path" "sort" "strings" + "time" ) // zipFI is the zip-file based implementation of FileInfo @@ -45,19 +46,23 @@ func (fi zipFI) Size() int64 { return 0 // directory } -func (fi zipFI) Mtime_ns() int64 { +func (fi zipFI) ModTime() time.Time { if f := fi.file; f != nil { - return f.Mtime_ns() + return f.ModTime() } - return 0 // directory has no modified time entry + return time.Time{} // directory has no modified time entry } -func (fi zipFI) IsDirectory() bool { - return fi.file == nil +func (fi zipFI) Mode() os.FileMode { + if fi.file == nil { + // Unix directories typically are executable, hence 555. + return os.ModeDir | 0555 + } + return 0444 } -func (fi zipFI) IsRegular() bool { - return fi.file != nil +func (fi zipFI) IsDir() bool { + return fi.file == nil } // zipFS is the zip-file based implementation of FileSystem @@ -66,7 +71,7 @@ type zipFS struct { list zipList } -func (fs *zipFS) Close() os.Error { +func (fs *zipFS) Close() error { fs.list = nil return fs.ReadCloser.Close() } @@ -79,7 +84,7 @@ func zipPath(name string) string { return name[1:] // strip leading '/' } -func (fs *zipFS) stat(abspath string) (int, zipFI, os.Error) { +func (fs *zipFS) stat(abspath string) (int, zipFI, error) { i, exact := fs.list.lookup(abspath) if i < 0 { // abspath has leading '/' stripped - print it explicitly @@ -93,38 +98,38 @@ func (fs *zipFS) stat(abspath string) (int, zipFI, os.Error) { return i, zipFI{name, file}, nil } -func (fs *zipFS) Open(abspath string) (io.ReadCloser, os.Error) { +func (fs *zipFS) Open(abspath string) (io.ReadCloser, error) { _, fi, err := fs.stat(zipPath(abspath)) if err != nil { return nil, err } - if fi.IsDirectory() { + if fi.IsDir() { return nil, fmt.Errorf("Open: %s is a directory", abspath) } return fi.file.Open() } -func (fs *zipFS) Lstat(abspath string) (FileInfo, os.Error) { +func (fs *zipFS) Lstat(abspath string) (os.FileInfo, error) { _, fi, err := fs.stat(zipPath(abspath)) return fi, err } -func (fs *zipFS) Stat(abspath string) (FileInfo, os.Error) { +func (fs *zipFS) Stat(abspath string) (os.FileInfo, error) { _, fi, err := fs.stat(zipPath(abspath)) return fi, err } -func (fs *zipFS) ReadDir(abspath string) ([]FileInfo, os.Error) { +func (fs *zipFS) ReadDir(abspath string) ([]os.FileInfo, error) { path := zipPath(abspath) i, fi, err := fs.stat(path) if err != nil { return nil, err } - if !fi.IsDirectory() { + if !fi.IsDir() { return nil, fmt.Errorf("ReadDir: %s is not a directory", abspath) } - var list []FileInfo + var list []os.FileInfo dirname := path + "/" prevname := "" for _, e := range fs.list[i:] { diff --git a/src/cmd/gofix/Makefile b/src/cmd/gofix/Makefile index b2725c572..553f4f78e 100644 --- a/src/cmd/gofix/Makefile +++ b/src/cmd/gofix/Makefile @@ -6,14 +6,27 @@ include ../../Make.inc TARG=gofix GOFILES=\ + error.go\ filepath.go\ fix.go\ + go1pkgrename.go\ + googlecode.go\ + hashsum.go\ + hmacnew.go\ + htmlerr.go\ httpfinalurl.go\ httpfs.go\ httpheaders.go\ httpserver.go\ + httputil.go\ + imagecolor.go\ imagenew.go\ + imagetiled.go\ + imageycbcr.go\ + iocopyn.go\ main.go\ + mapdelete.go\ + math.go\ netdial.go\ netudpgroup.go\ oserrorstring.go\ @@ -23,9 +36,13 @@ GOFILES=\ signal.go\ sorthelpers.go\ sortslice.go\ + strconv.go\ stringssplit.go\ + template.go\ + timefileinfo.go\ typecheck.go\ url.go\ + xmlapi.go\ include ../../Make.cmd diff --git a/src/cmd/gofix/error.go b/src/cmd/gofix/error.go new file mode 100644 index 000000000..55613210a --- /dev/null +++ b/src/cmd/gofix/error.go @@ -0,0 +1,353 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "regexp" + "strings" +) + +func init() { + register(errorFix) +} + +var errorFix = fix{ + "error", + "2011-11-02", + errorFn, + `Use error instead of os.Error. + +This fix rewrites code using os.Error to use error: + + os.Error -> error + os.NewError -> errors.New + os.EOF -> io.EOF + +Seeing the old names above (os.Error and so on) triggers the following +heuristic rewrites. The heuristics can be forced using the -force=error flag. + +A top-level function, variable, or constant named error is renamed error_. + +Error implementations—those types used as os.Error or named +XxxError—have their String methods renamed to Error. Any existing +Error field or method is renamed to Err. + +Error values—those with type os.Error or named e, err, error, err1, +and so on—have method calls and field references rewritten just +as the types do (String to Error, Error to Err). Also, a type assertion +of the form err.(*os.Waitmsg) becomes err.(*exec.ExitError). + +http://codereview.appspot.com/5305066 +`, +} + +// At minimum, this fix applies the following rewrites: +// +// os.Error -> error +// os.NewError -> errors.New +// os.EOF -> io.EOF +// +// However, if can apply any of those rewrites, it assumes that the +// file predates the error type and tries to update the code to use +// the new definition for error - an Error method, not a String method. +// This more heuristic procedure may not be 100% accurate, so it is +// only run when the file needs updating anyway. The heuristic can +// be forced to run using -force=error. +// +// First, we must identify the implementations of os.Error. +// These include the type of any value returned as or assigned to an os.Error. +// To that set we add any type whose name contains "Error" or "error". +// The heuristic helps for implementations that are not used as os.Error +// in the file in which they are defined. +// +// In any implementation of os.Error, we rename an existing struct field +// or method named Error to Err and rename the String method to Error. +// +// Second, we must identify the values of type os.Error. +// These include any value that obviously has type os.Error. +// To that set we add any variable whose name is e or err or error +// possibly followed by _ or a numeric or capitalized suffix. +// The heuristic helps for variables that are initialized using calls +// to functions in other packages. The type checker does not have +// information about those packages available, and in general cannot +// (because the packages may themselves not compile). +// +// For any value of type os.Error, we replace a call to String with a call to Error. +// We also replace type assertion err.(*os.Waitmsg) with err.(*exec.ExitError). + +// Variables matching this regexp are assumed to have type os.Error. +var errVar = regexp.MustCompile(`^(e|err|error)_?([A-Z0-9].*)?$`) + +// Types matching this regexp are assumed to be implementations of os.Error. +var errType = regexp.MustCompile(`^\*?([Ee]rror|.*Error)$`) + +// Type-checking configuration: tell the type-checker this basic +// information about types, functions, and variables in external packages. +var errorTypeConfig = &TypeConfig{ + Type: map[string]*Type{ + "os.Error": {}, + }, + Func: map[string]string{ + "fmt.Errorf": "os.Error", + "os.NewError": "os.Error", + }, + Var: map[string]string{ + "os.EPERM": "os.Error", + "os.ENOENT": "os.Error", + "os.ESRCH": "os.Error", + "os.EINTR": "os.Error", + "os.EIO": "os.Error", + "os.ENXIO": "os.Error", + "os.E2BIG": "os.Error", + "os.ENOEXEC": "os.Error", + "os.EBADF": "os.Error", + "os.ECHILD": "os.Error", + "os.EDEADLK": "os.Error", + "os.ENOMEM": "os.Error", + "os.EACCES": "os.Error", + "os.EFAULT": "os.Error", + "os.EBUSY": "os.Error", + "os.EEXIST": "os.Error", + "os.EXDEV": "os.Error", + "os.ENODEV": "os.Error", + "os.ENOTDIR": "os.Error", + "os.EISDIR": "os.Error", + "os.EINVAL": "os.Error", + "os.ENFILE": "os.Error", + "os.EMFILE": "os.Error", + "os.ENOTTY": "os.Error", + "os.EFBIG": "os.Error", + "os.ENOSPC": "os.Error", + "os.ESPIPE": "os.Error", + "os.EROFS": "os.Error", + "os.EMLINK": "os.Error", + "os.EPIPE": "os.Error", + "os.EAGAIN": "os.Error", + "os.EDOM": "os.Error", + "os.ERANGE": "os.Error", + "os.EADDRINUSE": "os.Error", + "os.ECONNREFUSED": "os.Error", + "os.ENAMETOOLONG": "os.Error", + "os.EAFNOSUPPORT": "os.Error", + "os.ETIMEDOUT": "os.Error", + "os.ENOTCONN": "os.Error", + }, +} + +func errorFn(f *ast.File) bool { + if !imports(f, "os") && !force["error"] { + return false + } + + // Fix gets called once to run the heuristics described above + // when we notice that this file definitely needs fixing + // (it mentions os.Error or something similar). + var fixed bool + var didHeuristic bool + heuristic := func() { + if didHeuristic { + return + } + didHeuristic = true + + // We have identified a necessary fix (like os.Error -> error) + // but have not applied it or any others yet. Prepare the file + // for fixing and apply heuristic fixes. + + // Rename error to error_ to make room for error. + fixed = renameTop(f, "error", "error_") || fixed + + // Use type checker to build list of error implementations. + typeof, assign := typecheck(errorTypeConfig, f) + + isError := map[string]bool{} + for _, val := range assign["os.Error"] { + t := typeof[val] + if strings.HasPrefix(t, "*") { + t = t[1:] + } + if t != "" && !strings.HasPrefix(t, "func(") { + isError[t] = true + } + } + + // We use both the type check results and the "Error" name heuristic + // to identify implementations of os.Error. + isErrorImpl := func(typ string) bool { + return isError[typ] || errType.MatchString(typ) + } + + isErrorVar := func(x ast.Expr) bool { + if typ := typeof[x]; typ != "" { + return isErrorImpl(typ) || typ == "os.Error" + } + if sel, ok := x.(*ast.SelectorExpr); ok { + return sel.Sel.Name == "Error" || sel.Sel.Name == "Err" + } + if id, ok := x.(*ast.Ident); ok { + return errVar.MatchString(id.Name) + } + return false + } + + walk(f, func(n interface{}) { + // In method declaration on error implementation type, + // rename String() to Error() and Error() to Err(). + fn, ok := n.(*ast.FuncDecl) + if ok && + fn.Recv != nil && + len(fn.Recv.List) == 1 && + isErrorImpl(typeName(fn.Recv.List[0].Type)) { + // Rename. + switch fn.Name.Name { + case "String": + fn.Name.Name = "Error" + fixed = true + case "Error": + fn.Name.Name = "Err" + fixed = true + } + return + } + + // In type definition of an error implementation type, + // rename Error field to Err to make room for method. + // Given type XxxError struct { ... Error T } rename field to Err. + d, ok := n.(*ast.GenDecl) + if ok { + for _, s := range d.Specs { + switch s := s.(type) { + case *ast.TypeSpec: + if isErrorImpl(typeName(s.Name)) { + st, ok := s.Type.(*ast.StructType) + if ok { + for _, f := range st.Fields.List { + for _, n := range f.Names { + if n.Name == "Error" { + n.Name = "Err" + fixed = true + } + } + } + } + } + } + } + } + + // For values that are an error implementation type, + // rename .Error to .Err and .String to .Error + sel, selok := n.(*ast.SelectorExpr) + if selok && isErrorImpl(typeof[sel.X]) { + switch sel.Sel.Name { + case "Error": + sel.Sel.Name = "Err" + fixed = true + case "String": + sel.Sel.Name = "Error" + fixed = true + } + } + + // Assume x.Err is an error value and rename .String to .Error + // Children have been processed so the rewrite from Error to Err + // has already happened there. + if selok { + if subsel, ok := sel.X.(*ast.SelectorExpr); ok && subsel.Sel.Name == "Err" && sel.Sel.Name == "String" { + sel.Sel.Name = "Error" + fixed = true + } + } + + // For values that are an error variable, rename .String to .Error. + if selok && isErrorVar(sel.X) && sel.Sel.Name == "String" { + sel.Sel.Name = "Error" + fixed = true + } + + // Rewrite composite literal of error type to turn Error: into Err:. + lit, ok := n.(*ast.CompositeLit) + if ok && isErrorImpl(typeof[lit]) { + for _, e := range lit.Elts { + if kv, ok := e.(*ast.KeyValueExpr); ok && isName(kv.Key, "Error") { + kv.Key.(*ast.Ident).Name = "Err" + fixed = true + } + } + } + + // Rename os.Waitmsg to exec.ExitError + // when used in a type assertion on an error. + ta, ok := n.(*ast.TypeAssertExpr) + if ok && isErrorVar(ta.X) && isPtrPkgDot(ta.Type, "os", "Waitmsg") { + addImport(f, "exec") + sel := ta.Type.(*ast.StarExpr).X.(*ast.SelectorExpr) + sel.X.(*ast.Ident).Name = "exec" + sel.Sel.Name = "ExitError" + fixed = true + } + + }) + } + + fix := func() { + if fixed { + return + } + fixed = true + heuristic() + } + + if force["error"] { + heuristic() + } + + walk(f, func(n interface{}) { + p, ok := n.(*ast.Expr) + if !ok { + return + } + sel, ok := (*p).(*ast.SelectorExpr) + if !ok { + return + } + switch { + case isPkgDot(sel, "os", "Error"): + fix() + *p = &ast.Ident{NamePos: sel.Pos(), Name: "error"} + case isPkgDot(sel, "os", "NewError"): + fix() + addImport(f, "errors") + sel.X.(*ast.Ident).Name = "errors" + sel.Sel.Name = "New" + case isPkgDot(sel, "os", "EOF"): + fix() + addImport(f, "io") + sel.X.(*ast.Ident).Name = "io" + } + }) + + if fixed && !usesImport(f, "os") { + deleteImport(f, "os") + } + + return fixed +} + +func typeName(typ ast.Expr) string { + if p, ok := typ.(*ast.StarExpr); ok { + typ = p.X + } + id, ok := typ.(*ast.Ident) + if ok { + return id.Name + } + sel, ok := typ.(*ast.SelectorExpr) + if ok { + return typeName(sel.X) + "." + sel.Sel.Name + } + return "" +} diff --git a/src/cmd/gofix/error_test.go b/src/cmd/gofix/error_test.go new file mode 100644 index 000000000..eeab7e2ee --- /dev/null +++ b/src/cmd/gofix/error_test.go @@ -0,0 +1,232 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(errorTests, errorFn) +} + +var errorTests = []testCase{ + { + Name: "error.0", + In: `package main + +func error() {} + +var error int +`, + Out: `package main + +func error() {} + +var error int +`, + }, + { + Name: "error.1", + In: `package main + +import "os" + +func f() os.Error { + return os.EOF +} + +func error() {} + +var error int + +func g() { + error := 1 + _ = error +} +`, + Out: `package main + +import "io" + +func f() error { + return io.EOF +} + +func error_() {} + +var error_ int + +func g() { + error := 1 + _ = error +} +`, + }, + { + Name: "error.2", + In: `package main + +import "os" + +func f() os.Error { + return os.EOF +} + +func g() string { + // these all convert because f is known + if err := f(); err != nil { + return err.String() + } + if err1 := f(); err1 != nil { + return err1.String() + } + if e := f(); e != nil { + return e.String() + } + if x := f(); x != nil { + return x.String() + } + + // only the error names (err, err1, e) convert; u is not known + if err := u(); err != nil { + return err.String() + } + if err1 := u(); err1 != nil { + return err1.String() + } + if e := u(); e != nil { + return e.String() + } + if x := u(); x != nil { + return x.String() + } + return "" +} + +type T int + +func (t T) String() string { return "t" } + +type PT int + +func (p *PT) String() string { return "pt" } + +type MyError int + +func (t MyError) String() string { return "myerror" } + +type PMyError int + +func (p *PMyError) String() string { return "pmyerror" } + +func error() {} + +var error int +`, + Out: `package main + +import "io" + +func f() error { + return io.EOF +} + +func g() string { + // these all convert because f is known + if err := f(); err != nil { + return err.Error() + } + if err1 := f(); err1 != nil { + return err1.Error() + } + if e := f(); e != nil { + return e.Error() + } + if x := f(); x != nil { + return x.Error() + } + + // only the error names (err, err1, e) convert; u is not known + if err := u(); err != nil { + return err.Error() + } + if err1 := u(); err1 != nil { + return err1.Error() + } + if e := u(); e != nil { + return e.Error() + } + if x := u(); x != nil { + return x.String() + } + return "" +} + +type T int + +func (t T) String() string { return "t" } + +type PT int + +func (p *PT) String() string { return "pt" } + +type MyError int + +func (t MyError) Error() string { return "myerror" } + +type PMyError int + +func (p *PMyError) Error() string { return "pmyerror" } + +func error_() {} + +var error_ int +`, + }, + { + Name: "error.3", + In: `package main + +import "os" + +func f() os.Error { + return os.EOF +} + +type PathError struct { + Name string + Error os.Error +} + +func (p *PathError) String() string { + return p.Name + ": " + p.Error.String() +} + +func (p *PathError) Error1() string { + p = &PathError{Error: nil} + return fmt.Sprint(p.Name, ": ", p.Error) +} +`, + Out: `package main + +import "io" + +func f() error { + return io.EOF +} + +type PathError struct { + Name string + Err error +} + +func (p *PathError) Error() string { + return p.Name + ": " + p.Err.Error() +} + +func (p *PathError) Error1() string { + p = &PathError{Err: nil} + return fmt.Sprint(p.Name, ": ", p.Err) +} +`, + }, +} diff --git a/src/cmd/gofix/filepath.go b/src/cmd/gofix/filepath.go index 1d0ad6879..f31226018 100644 --- a/src/cmd/gofix/filepath.go +++ b/src/cmd/gofix/filepath.go @@ -9,14 +9,17 @@ import ( ) func init() { - register(fix{ - "filepath", - filepathFunc, - `Adapt code from filepath.[List]SeparatorString to string(filepath.[List]Separator). + register(filepathFix) +} + +var filepathFix = fix{ + "filepath", + "2011-06-26", + filepathFunc, + `Adapt code from filepath.[List]SeparatorString to string(filepath.[List]Separator). http://codereview.appspot.com/4527090 `, - }) } func filepathFunc(f *ast.File) (fixed bool) { diff --git a/src/cmd/gofix/filepath_test.go b/src/cmd/gofix/filepath_test.go index d170c3ae3..37a2f5d9f 100644 --- a/src/cmd/gofix/filepath_test.go +++ b/src/cmd/gofix/filepath_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(filepathTests) + addTestCases(filepathTests, filepathFunc) } var filepathTests = []testCase{ diff --git a/src/cmd/gofix/fix.go b/src/cmd/gofix/fix.go index cc85ceafa..d1a7bc874 100644 --- a/src/cmd/gofix/fix.go +++ b/src/cmd/gofix/fix.go @@ -4,29 +4,47 @@ package main +/* +receiver named error +function named error +method on error +exiterror +slice of named type (go/scanner) +*/ + import ( "fmt" "go/ast" + "go/parser" "go/token" "os" + "path" "strconv" "strings" ) type fix struct { name string + date string // date that fix was introduced, in YYYY-MM-DD format f func(*ast.File) bool desc string } -// main runs sort.Sort(fixes) after init process is done. -type fixlist []fix +// main runs sort.Sort(byName(fixes)) before printing list of fixes. +type byName []fix + +func (f byName) Len() int { return len(f) } +func (f byName) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (f byName) Less(i, j int) bool { return f[i].name < f[j].name } + +// main runs sort.Sort(byDate(fixes)) before applying fixes. +type byDate []fix -func (f fixlist) Len() int { return len(f) } -func (f fixlist) Swap(i, j int) { f[i], f[j] = f[j], f[i] } -func (f fixlist) Less(i, j int) bool { return f[i].name < f[j].name } +func (f byDate) Len() int { return len(f) } +func (f byDate) Swap(i, j int) { f[i], f[j] = f[j], f[i] } +func (f byDate) Less(i, j int) bool { return f[i].date < f[j].date } -var fixes fixlist +var fixes []fix func register(f fix) { fixes = append(fixes, f) @@ -73,6 +91,8 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) { walkBeforeAfter(*n, before, after) case **ast.Ident: walkBeforeAfter(*n, before, after) + case **ast.BasicLit: + walkBeforeAfter(*n, before, after) // pointers to slices case *[]ast.Decl: @@ -90,7 +110,9 @@ func walkBeforeAfter(x interface{}, before, after func(interface{})) { // These are ordered and grouped to match ../../pkg/go/ast/ast.go case *ast.Field: + walkBeforeAfter(&n.Names, before, after) walkBeforeAfter(&n.Type, before, after) + walkBeforeAfter(&n.Tag, before, after) case *ast.FieldList: for _, field := range n.List { walkBeforeAfter(field, before, after) @@ -292,6 +314,20 @@ func importPath(s *ast.ImportSpec) string { return "" } +// declImports reports whether gen contains an import of path. +func declImports(gen *ast.GenDecl, path string) bool { + if gen.Tok != token.IMPORT { + return false + } + for _, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + if importPath(impspec) == path { + return true + } + } + return false +} + // isPkgDot returns true if t is the expression "pkg.name" // where pkg is an imported identifier. func isPkgDot(t ast.Expr, pkg, name string) bool { @@ -446,66 +482,172 @@ func newPkgDot(pos token.Pos, pkg, name string) ast.Expr { } } +// renameTop renames all references to the top-level name old. +// It returns true if it makes any changes. +func renameTop(f *ast.File, old, new string) bool { + var fixed bool + + // Rename any conflicting imports + // (assuming package name is last element of path). + for _, s := range f.Imports { + if s.Name != nil { + if s.Name.Name == old { + s.Name.Name = new + fixed = true + } + } else { + _, thisName := path.Split(importPath(s)) + if thisName == old { + s.Name = ast.NewIdent(new) + fixed = true + } + } + } + + // Rename any top-level declarations. + for _, d := range f.Decls { + switch d := d.(type) { + case *ast.FuncDecl: + if d.Recv == nil && d.Name.Name == old { + d.Name.Name = new + d.Name.Obj.Name = new + fixed = true + } + case *ast.GenDecl: + for _, s := range d.Specs { + switch s := s.(type) { + case *ast.TypeSpec: + if s.Name.Name == old { + s.Name.Name = new + s.Name.Obj.Name = new + fixed = true + } + case *ast.ValueSpec: + for _, n := range s.Names { + if n.Name == old { + n.Name = new + n.Obj.Name = new + fixed = true + } + } + } + } + } + } + + // Rename top-level old to new, both unresolved names + // (probably defined in another file) and names that resolve + // to a declaration we renamed. + walk(f, func(n interface{}) { + id, ok := n.(*ast.Ident) + if ok && isTopName(id, old) { + id.Name = new + fixed = true + } + if ok && id.Obj != nil && id.Name == old && id.Obj.Name == new { + id.Name = id.Obj.Name + fixed = true + } + }) + + return fixed +} + +// matchLen returns the length of the longest prefix shared by x and y. +func matchLen(x, y string) int { + i := 0 + for i < len(x) && i < len(y) && x[i] == y[i] { + i++ + } + return i +} + // addImport adds the import path to the file f, if absent. -func addImport(f *ast.File, path string) { - if imports(f, path) { - return +func addImport(f *ast.File, ipath string) (added bool) { + if imports(f, ipath) { + return false } + // Determine name of import. + // Assume added imports follow convention of using last element. + _, name := path.Split(ipath) + + // Rename any conflicting top-level references from name to name_. + renameTop(f, name, name+"_") + newImport := &ast.ImportSpec{ Path: &ast.BasicLit{ Kind: token.STRING, - Value: strconv.Quote(path), + Value: strconv.Quote(ipath), }, } - var impdecl *ast.GenDecl - // Find an import decl to add to. - for _, decl := range f.Decls { + var ( + bestMatch = -1 + lastImport = -1 + impDecl *ast.GenDecl + impIndex = -1 + ) + for i, decl := range f.Decls { gen, ok := decl.(*ast.GenDecl) - if ok && gen.Tok == token.IMPORT { - impdecl = gen - break + lastImport = i + // Do not add to import "C", to avoid disrupting the + // association with its doc comment, breaking cgo. + if declImports(gen, "C") { + continue + } + + // Compute longest shared prefix with imports in this block. + for j, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + n := matchLen(importPath(impspec), ipath) + if n > bestMatch { + bestMatch = n + impDecl = gen + impIndex = j + } + } } } - // No import decl found. Add one. - if impdecl == nil { - impdecl = &ast.GenDecl{ + // If no import decl found, add one after the last import. + if impDecl == nil { + impDecl = &ast.GenDecl{ Tok: token.IMPORT, } f.Decls = append(f.Decls, nil) - copy(f.Decls[1:], f.Decls) - f.Decls[0] = impdecl + copy(f.Decls[lastImport+2:], f.Decls[lastImport+1:]) + f.Decls[lastImport+1] = impDecl } // Ensure the import decl has parentheses, if needed. - if len(impdecl.Specs) > 0 && !impdecl.Lparen.IsValid() { - impdecl.Lparen = impdecl.Pos() + if len(impDecl.Specs) > 0 && !impDecl.Lparen.IsValid() { + impDecl.Lparen = impDecl.Pos() } - // Assume the import paths are alphabetically ordered. - // If they are not, the result is ugly, but legal. - insertAt := len(impdecl.Specs) // default to end of specs - for i, spec := range impdecl.Specs { - impspec := spec.(*ast.ImportSpec) - if importPath(impspec) > path { - insertAt = i - break - } + insertAt := impIndex + 1 + if insertAt == 0 { + insertAt = len(impDecl.Specs) + } + impDecl.Specs = append(impDecl.Specs, nil) + copy(impDecl.Specs[insertAt+1:], impDecl.Specs[insertAt:]) + impDecl.Specs[insertAt] = newImport + if insertAt > 0 { + // Assign same position as the previous import, + // so that the sorter sees it as being in the same block. + prev := impDecl.Specs[insertAt-1] + newImport.Path.ValuePos = prev.Pos() + newImport.EndPos = prev.Pos() } - - impdecl.Specs = append(impdecl.Specs, nil) - copy(impdecl.Specs[insertAt+1:], impdecl.Specs[insertAt:]) - impdecl.Specs[insertAt] = newImport f.Imports = append(f.Imports, newImport) + return true } // deleteImport deletes the import path from the file f, if present. -func deleteImport(f *ast.File, path string) { +func deleteImport(f *ast.File, path string) (deleted bool) { oldImport := importSpec(f, path) // Find the import node that imports path, if any. @@ -516,13 +658,13 @@ func deleteImport(f *ast.File, path string) { } for j, spec := range gen.Specs { impspec := spec.(*ast.ImportSpec) - if oldImport != impspec { continue } // We found an import spec that imports path. // Delete it. + deleted = true copy(gen.Specs[j:], gen.Specs[j+1:]) gen.Specs = gen.Specs[:len(gen.Specs)-1] @@ -534,7 +676,13 @@ func deleteImport(f *ast.File, path string) { } else if len(gen.Specs) == 1 { gen.Lparen = token.NoPos // drop parens } - + if j > 0 { + // We deleted an entry but now there will be + // a blank line-sized hole where the import was. + // Close the hole by making the previous + // import appear to "end" where this one did. + gen.Specs[j-1].(*ast.ImportSpec).EndPos = impspec.End() + } break } } @@ -547,6 +695,22 @@ func deleteImport(f *ast.File, path string) { break } } + + return +} + +// rewriteImport rewrites any import of path oldPath to path newPath. +func rewriteImport(f *ast.File, oldPath, newPath string) (rewrote bool) { + for _, imp := range f.Imports { + if importPath(imp) == oldPath { + rewrote = true + // record old End, beacuse the default is to compute + // it using the length of imp.Path.Value. + imp.EndPos = imp.End() + imp.Path.Value = strconv.Quote(newPath) + } + } + return } func usesImport(f *ast.File, path string) (used bool) { @@ -580,3 +744,11 @@ func usesImport(f *ast.File, path string) (used bool) { return } + +func expr(s string) ast.Expr { + x, err := parser.ParseExpr(s) + if err != nil { + panic("parsing " + s + ": " + err.Error()) + } + return x +} diff --git a/src/cmd/gofix/go1pkgrename.go b/src/cmd/gofix/go1pkgrename.go new file mode 100644 index 000000000..7dc952dfa --- /dev/null +++ b/src/cmd/gofix/go1pkgrename.go @@ -0,0 +1,119 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(go1pkgrenameFix) +} + +var go1pkgrenameFix = fix{ + "go1rename", + "2011-11-08", + go1pkgrename, + `Rewrite imports for packages moved during transition to Go 1. + +http://codereview.appspot.com/5316078 +`, +} + +var go1PackageRenames = []struct{ old, new string }{ + {"asn1", "encoding/asn1"}, + {"big", "math/big"}, + {"cmath", "math/cmplx"}, + {"csv", "encoding/csv"}, + {"exec", "os/exec"}, + {"exp/template/html", "html/template"}, + {"gob", "encoding/gob"}, + {"http", "net/http"}, + {"http/cgi", "net/http/cgi"}, + {"http/fcgi", "net/http/fcgi"}, + {"http/httptest", "net/http/httptest"}, + {"http/pprof", "net/http/pprof"}, + {"json", "encoding/json"}, + {"mail", "net/mail"}, + {"rpc", "net/rpc"}, + {"rpc/jsonrpc", "net/rpc/jsonrpc"}, + {"scanner", "text/scanner"}, + {"smtp", "net/smtp"}, + {"syslog", "log/syslog"}, + {"tabwriter", "text/tabwriter"}, + {"template", "text/template"}, + {"template/parse", "text/template/parse"}, + {"rand", "math/rand"}, + {"url", "net/url"}, + {"utf16", "unicode/utf16"}, + {"utf8", "unicode/utf8"}, + {"xml", "encoding/xml"}, + + // go.crypto sub-repository + {"crypto/bcrypt", "code.google.com/p/go.crypto/bcrypt"}, + {"crypto/blowfish", "code.google.com/p/go.crypto/blowfish"}, + {"crypto/cast5", "code.google.com/p/go.crypto/cast5"}, + {"crypto/md4", "code.google.com/p/go.crypto/md4"}, + {"crypto/ocsp", "code.google.com/p/go.crypto/ocsp"}, + {"crypto/openpgp", "code.google.com/p/go.crypto/openpgp"}, + {"crypto/openpgp/armor", "code.google.com/p/go.crypto/openpgp/armor"}, + {"crypto/openpgp/elgamal", "code.google.com/p/go.crypto/openpgp/elgamal"}, + {"crypto/openpgp/errors", "code.google.com/p/go.crypto/openpgp/errors"}, + {"crypto/openpgp/packet", "code.google.com/p/go.crypto/openpgp/packet"}, + {"crypto/openpgp/s2k", "code.google.com/p/go.crypto/openpgp/s2k"}, + {"crypto/ripemd160", "code.google.com/p/go.crypto/ripemd160"}, + {"crypto/twofish", "code.google.com/p/go.crypto/twofish"}, + {"crypto/xtea", "code.google.com/p/go.crypto/xtea"}, + {"exp/ssh", "code.google.com/p/go.crypto/ssh"}, + + // go.net sub-repository + {"net/dict", "code.google.com/p/go.net/dict"}, + {"net/websocket", "code.google.com/p/go.net/websocket"}, + {"exp/spdy", "code.google.com/p/go.net/spdy"}, + + // go.codereview sub-repository + {"encoding/git85", "code.google.com/p/go.codereview/git85"}, + {"patch", "code.google.com/p/go.codereview/patch"}, +} + +var go1PackageNameRenames = []struct{ newPath, old, new string }{ + {"html/template", "html", "template"}, + {"math/cmplx", "cmath", "cmplx"}, +} + +func go1pkgrename(f *ast.File) bool { + fixed := false + + // First update the imports. + for _, rename := range go1PackageRenames { + if !imports(f, rename.old) { + continue + } + if rewriteImport(f, rename.old, rename.new) { + fixed = true + } + } + if !fixed { + return false + } + + // Now update the package names used by importers. + for _, rename := range go1PackageNameRenames { + // These are rare packages, so do the import test before walking. + if imports(f, rename.newPath) { + walk(f, func(n interface{}) { + if sel, ok := n.(*ast.SelectorExpr); ok { + if isTopName(sel.X, rename.old) { + // We know Sel.X is an Ident. + sel.X.(*ast.Ident).Name = rename.new + return + } + } + }) + } + } + + return fixed +} diff --git a/src/cmd/gofix/go1pkgrename_test.go b/src/cmd/gofix/go1pkgrename_test.go new file mode 100644 index 000000000..736e7ed7f --- /dev/null +++ b/src/cmd/gofix/go1pkgrename_test.go @@ -0,0 +1,129 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(go1renameTests, go1pkgrename) +} + +var go1renameTests = []testCase{ + { + Name: "go1rename.0", + In: `package main + +import ( + "asn1" + "big" + "cmath" + "csv" + "exec" + "exp/template/html" + "gob" + "http" + "http/cgi" + "http/fcgi" + "http/httptest" + "http/pprof" + "json" + "mail" + "rand" + "rpc" + "rpc/jsonrpc" + "scanner" + "smtp" + "syslog" + "tabwriter" + "template" + "template/parse" + "url" + "utf16" + "utf8" + "xml" + + "crypto/bcrypt" +) +`, + Out: `package main + +import ( + "encoding/asn1" + "encoding/csv" + "encoding/gob" + "encoding/json" + "encoding/xml" + "html/template" + "log/syslog" + "math/big" + "math/cmplx" + "math/rand" + "net/http" + "net/http/cgi" + "net/http/fcgi" + "net/http/httptest" + "net/http/pprof" + "net/mail" + "net/rpc" + "net/rpc/jsonrpc" + "net/smtp" + "net/url" + "os/exec" + "text/scanner" + "text/tabwriter" + "text/template" + "text/template/parse" + "unicode/utf16" + "unicode/utf8" + + "code.google.com/p/go.crypto/bcrypt" +) +`, + }, + { + Name: "go1rename.1", + In: `package main + +import "cmath" +import poot "exp/template/html" + +var _ = cmath.Sin +var _ = poot.Poot +`, + Out: `package main + +import "math/cmplx" +import poot "html/template" + +var _ = cmplx.Sin +var _ = poot.Poot +`, + }, + { + Name: "go1rename.2", + In: `package foo + +import ( + "fmt" + "http" + "url" + + "google/secret/project/go" +) + +func main() {} +`, + Out: `package foo + +import ( + "fmt" + "net/http" + "net/url" + + "google/secret/project/go" +) + +func main() {} +`, + }, +} diff --git a/src/cmd/gofix/googlecode.go b/src/cmd/gofix/googlecode.go new file mode 100644 index 000000000..143781a74 --- /dev/null +++ b/src/cmd/gofix/googlecode.go @@ -0,0 +1,41 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "regexp" +) + +func init() { + register(googlecodeFix) +} + +var googlecodeFix = fix{ + "googlecode", + "2011-11-21", + googlecode, + `Rewrite Google Code imports from the deprecated form +"foo.googlecode.com/vcs/path" to "code.google.com/p/foo/path". +`, +} + +var googlecodeRe = regexp.MustCompile(`^([a-z0-9\-]+)\.googlecode\.com/(svn|git|hg)(/[a-z0-9A-Z_.\-/]+)?$`) + +func googlecode(f *ast.File) bool { + fixed := false + + for _, s := range f.Imports { + old := importPath(s) + if m := googlecodeRe.FindStringSubmatch(old); m != nil { + new := "code.google.com/p/" + m[1] + m[3] + if rewriteImport(f, old, new) { + fixed = true + } + } + } + + return fixed +} diff --git a/src/cmd/gofix/googlecode_test.go b/src/cmd/gofix/googlecode_test.go new file mode 100644 index 000000000..c62ee4f32 --- /dev/null +++ b/src/cmd/gofix/googlecode_test.go @@ -0,0 +1,31 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(googlecodeTests, googlecode) +} + +var googlecodeTests = []testCase{ + { + Name: "googlecode.0", + In: `package main + +import ( + "foo.googlecode.com/hg/bar" + "go-qux-23.googlecode.com/svn" + "zap.googlecode.com/git/some/path" +) +`, + Out: `package main + +import ( + "code.google.com/p/foo/bar" + "code.google.com/p/go-qux-23" + "code.google.com/p/zap/some/path" +) +`, + }, +} diff --git a/src/cmd/gofix/hashsum.go b/src/cmd/gofix/hashsum.go new file mode 100644 index 000000000..0df6ad749 --- /dev/null +++ b/src/cmd/gofix/hashsum.go @@ -0,0 +1,94 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(hashSumFix) +} + +var hashSumFix = fix{ + "hashsum", + "2011-11-30", + hashSumFn, + `Pass a nil argument to calls to hash.Sum + +This fix rewrites code so that it passes a nil argument to hash.Sum. +The additional argument will allow callers to avoid an +allocation in the future. + +http://codereview.appspot.com/5448065 +`, +} + +// Type-checking configuration: tell the type-checker this basic +// information about types, functions, and variables in external packages. +var hashSumTypeConfig = &TypeConfig{ + Var: map[string]string{ + "crypto.MD4": "crypto.Hash", + "crypto.MD5": "crypto.Hash", + "crypto.SHA1": "crypto.Hash", + "crypto.SHA224": "crypto.Hash", + "crypto.SHA256": "crypto.Hash", + "crypto.SHA384": "crypto.Hash", + "crypto.SHA512": "crypto.Hash", + "crypto.MD5SHA1": "crypto.Hash", + "crypto.RIPEMD160": "crypto.Hash", + }, + + Func: map[string]string{ + "adler32.New": "hash.Hash", + "crc32.New": "hash.Hash", + "crc32.NewIEEE": "hash.Hash", + "crc64.New": "hash.Hash", + "fnv.New32a": "hash.Hash", + "fnv.New32": "hash.Hash", + "fnv.New64a": "hash.Hash", + "fnv.New64": "hash.Hash", + "hmac.New": "hash.Hash", + "hmac.NewMD5": "hash.Hash", + "hmac.NewSHA1": "hash.Hash", + "hmac.NewSHA256": "hash.Hash", + "md4.New": "hash.Hash", + "md5.New": "hash.Hash", + "ripemd160.New": "hash.Hash", + "sha1.New224": "hash.Hash", + "sha1.New": "hash.Hash", + "sha256.New224": "hash.Hash", + "sha256.New": "hash.Hash", + "sha512.New384": "hash.Hash", + "sha512.New": "hash.Hash", + }, + + Type: map[string]*Type{ + "crypto.Hash": { + Method: map[string]string{ + "New": "func() hash.Hash", + }, + }, + }, +} + +func hashSumFn(f *ast.File) bool { + typeof, _ := typecheck(hashSumTypeConfig, f) + + fixed := false + + walk(f, func(n interface{}) { + call, ok := n.(*ast.CallExpr) + if ok && len(call.Args) == 0 { + sel, ok := call.Fun.(*ast.SelectorExpr) + if ok && sel.Sel.Name == "Sum" && typeof[sel.X] == "hash.Hash" { + call.Args = append(call.Args, ast.NewIdent("nil")) + fixed = true + } + } + }) + + return fixed +} diff --git a/src/cmd/gofix/hashsum_test.go b/src/cmd/gofix/hashsum_test.go new file mode 100644 index 000000000..241af2020 --- /dev/null +++ b/src/cmd/gofix/hashsum_test.go @@ -0,0 +1,99 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(hashSumTests, hashSumFn) +} + +var hashSumTests = []testCase{ + { + Name: "hashsum.0", + In: `package main + +import "crypto/sha256" + +func f() []byte { + h := sha256.New() + return h.Sum() +} +`, + Out: `package main + +import "crypto/sha256" + +func f() []byte { + h := sha256.New() + return h.Sum(nil) +} +`, + }, + + { + Name: "hashsum.1", + In: `package main + +func f(h hash.Hash) []byte { + return h.Sum() +} +`, + Out: `package main + +func f(h hash.Hash) []byte { + return h.Sum(nil) +} +`, + }, + + { + Name: "hashsum.0", + In: `package main + +import "crypto/sha256" + +func f() []byte { + h := sha256.New() + h.Write([]byte("foo")) + digest := h.Sum() +} +`, + Out: `package main + +import "crypto/sha256" + +func f() []byte { + h := sha256.New() + h.Write([]byte("foo")) + digest := h.Sum(nil) +} +`, + }, + + { + Name: "hashsum.0", + In: `package main + +import _ "crypto/sha256" +import "crypto" + +func f() []byte { + hashType := crypto.SHA256 + h := hashType.New() + digest := h.Sum() +} +`, + Out: `package main + +import _ "crypto/sha256" +import "crypto" + +func f() []byte { + hashType := crypto.SHA256 + h := hashType.New() + digest := h.Sum(nil) +} +`, + }, +} diff --git a/src/cmd/gofix/hmacnew.go b/src/cmd/gofix/hmacnew.go new file mode 100644 index 000000000..c0c44ef3e --- /dev/null +++ b/src/cmd/gofix/hmacnew.go @@ -0,0 +1,61 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + register(hmacNewFix) +} + +var hmacNewFix = fix{ + "hmacnew", + "2012-01-19", + hmacnew, + `Deprecate hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256. + +This fix rewrites code using hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256 to +use hmac.New: + + hmac.NewMD5(key) -> hmac.New(md5.New, key) + hmac.NewSHA1(key) -> hmac.New(sha1.New, key) + hmac.NewSHA256(key) -> hmac.New(sha256.New, key) + +`, +} + +func hmacnew(f *ast.File) (fixed bool) { + if !imports(f, "crypto/hmac") { + return + } + + walk(f, func(n interface{}) { + ce, ok := n.(*ast.CallExpr) + if !ok { + return + } + + var pkg string + switch { + case isPkgDot(ce.Fun, "hmac", "NewMD5"): + pkg = "md5" + case isPkgDot(ce.Fun, "hmac", "NewSHA1"): + pkg = "sha1" + case isPkgDot(ce.Fun, "hmac", "NewSHA256"): + pkg = "sha256" + default: + return + } + + addImport(f, "crypto/"+pkg) + + ce.Fun = ast.NewIdent("hmac.New") + ce.Args = append([]ast.Expr{ast.NewIdent(pkg + ".New")}, ce.Args...) + + fixed = true + }) + + return +} diff --git a/src/cmd/gofix/hmacnew_test.go b/src/cmd/gofix/hmacnew_test.go new file mode 100644 index 000000000..5aeee8573 --- /dev/null +++ b/src/cmd/gofix/hmacnew_test.go @@ -0,0 +1,107 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(hmacNewTests, hmacnew) +} + +var hmacNewTests = []testCase{ + { + Name: "hmacnew.0", + In: `package main + +import "crypto/hmac" + +var f = hmac.NewSHA1([]byte("some key")) +`, + Out: `package main + +import ( + "crypto/hmac" + "crypto/sha1" +) + +var f = hmac.New(sha1.New, []byte("some key")) +`, + }, + { + Name: "hmacnew.1", + In: `package main + +import "crypto/hmac" + +var key = make([]byte, 8) +var f = hmac.NewSHA1(key) +`, + Out: `package main + +import ( + "crypto/hmac" + "crypto/sha1" +) + +var key = make([]byte, 8) +var f = hmac.New(sha1.New, key) +`, + }, + { + Name: "hmacnew.2", + In: `package main + +import "crypto/hmac" + +var f = hmac.NewMD5([]byte("some key")) +`, + Out: `package main + +import ( + "crypto/hmac" + "crypto/md5" +) + +var f = hmac.New(md5.New, []byte("some key")) +`, + }, + { + Name: "hmacnew.3", + In: `package main + +import "crypto/hmac" + +var f = hmac.NewSHA256([]byte("some key")) +`, + Out: `package main + +import ( + "crypto/hmac" + "crypto/sha256" +) + +var f = hmac.New(sha256.New, []byte("some key")) +`, + }, + { + Name: "hmacnew.4", + In: `package main + +import ( + "crypto/hmac" + "crypto/sha1" +) + +var f = hmac.New(sha1.New, []byte("some key")) +`, + Out: `package main + +import ( + "crypto/hmac" + "crypto/sha1" +) + +var f = hmac.New(sha1.New, []byte("some key")) +`, + }, +} diff --git a/src/cmd/gofix/htmlerr.go b/src/cmd/gofix/htmlerr.go new file mode 100644 index 000000000..b5105c822 --- /dev/null +++ b/src/cmd/gofix/htmlerr.go @@ -0,0 +1,47 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(htmlerrFix) +} + +var htmlerrFix = fix{ + "htmlerr", + "2011-11-04", + htmlerr, + `Rename html's Tokenizer.Error method to Err. + +http://codereview.appspot.com/5327064/ +`, +} + +var htmlerrTypeConfig = &TypeConfig{ + Func: map[string]string{ + "html.NewTokenizer": "html.Tokenizer", + }, +} + +func htmlerr(f *ast.File) bool { + if !imports(f, "html") { + return false + } + + typeof, _ := typecheck(htmlerrTypeConfig, f) + + fixed := false + walk(f, func(n interface{}) { + s, ok := n.(*ast.SelectorExpr) + if ok && typeof[s.X] == "html.Tokenizer" && s.Sel.Name == "Error" { + s.Sel.Name = "Err" + fixed = true + } + }) + return fixed +} diff --git a/src/cmd/gofix/htmlerr_test.go b/src/cmd/gofix/htmlerr_test.go new file mode 100644 index 000000000..043abc42a --- /dev/null +++ b/src/cmd/gofix/htmlerr_test.go @@ -0,0 +1,39 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(htmlerrTests, htmlerr) +} + +var htmlerrTests = []testCase{ + { + Name: "htmlerr.0", + In: `package main + +import ( + "html" +) + +func f() { + e := errors.New("") + t := html.NewTokenizer(r) + _, _ = e.Error(), t.Error() +} +`, + Out: `package main + +import ( + "html" +) + +func f() { + e := errors.New("") + t := html.NewTokenizer(r) + _, _ = e.Error(), t.Err() +} +`, + }, +} diff --git a/src/cmd/gofix/httpfinalurl.go b/src/cmd/gofix/httpfinalurl.go index 9e6cbf6bc..49b9f1c51 100644 --- a/src/cmd/gofix/httpfinalurl.go +++ b/src/cmd/gofix/httpfinalurl.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(httpFinalURLFix) +} + var httpFinalURLFix = fix{ "httpfinalurl", + "2011-05-13", httpfinalurl, `Adapt http Get calls to not have a finalURL result parameter. @@ -17,10 +22,6 @@ http://codereview.appspot.com/4535056/ `, } -func init() { - register(httpFinalURLFix) -} - func httpfinalurl(f *ast.File) bool { if !imports(f, "http") { return false diff --git a/src/cmd/gofix/httpfinalurl_test.go b/src/cmd/gofix/httpfinalurl_test.go index 9e7d6242d..9249f7e18 100644 --- a/src/cmd/gofix/httpfinalurl_test.go +++ b/src/cmd/gofix/httpfinalurl_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(httpfinalurlTests) + addTestCases(httpfinalurlTests, httpfinalurl) } var httpfinalurlTests = []testCase{ diff --git a/src/cmd/gofix/httpfs.go b/src/cmd/gofix/httpfs.go index 7f2765680..625dd0f7d 100644 --- a/src/cmd/gofix/httpfs.go +++ b/src/cmd/gofix/httpfs.go @@ -9,8 +9,13 @@ import ( "go/token" ) +func init() { + register(httpFileSystemFix) +} + var httpFileSystemFix = fix{ "httpfs", + "2011-06-27", httpfs, `Adapt http FileServer to take a FileSystem. @@ -18,10 +23,6 @@ http://codereview.appspot.com/4629047 http FileSystem interface `, } -func init() { - register(httpFileSystemFix) -} - func httpfs(f *ast.File) bool { if !imports(f, "http") { return false diff --git a/src/cmd/gofix/httpfs_test.go b/src/cmd/gofix/httpfs_test.go index d1804e93b..dd8ef2cfd 100644 --- a/src/cmd/gofix/httpfs_test.go +++ b/src/cmd/gofix/httpfs_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(httpFileSystemTests) + addTestCases(httpFileSystemTests, httpfs) } var httpFileSystemTests = []testCase{ diff --git a/src/cmd/gofix/httpheaders.go b/src/cmd/gofix/httpheaders.go index 8a9080e8e..15c21ac86 100644 --- a/src/cmd/gofix/httpheaders.go +++ b/src/cmd/gofix/httpheaders.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(httpHeadersFix) +} + var httpHeadersFix = fix{ "httpheaders", + "2011-06-16", httpheaders, `Rename http Referer, UserAgent, Cookie, SetCookie, which are now methods. @@ -17,10 +22,6 @@ http://codereview.appspot.com/4620049/ `, } -func init() { - register(httpHeadersFix) -} - func httpheaders(f *ast.File) bool { if !imports(f, "http") { return false @@ -35,7 +36,7 @@ func httpheaders(f *ast.File) bool { }) fixed := false - typeof := typecheck(headerTypeConfig, f) + typeof, _ := typecheck(headerTypeConfig, f) walk(f, func(ni interface{}) { switch n := ni.(type) { case *ast.SelectorExpr: @@ -60,7 +61,7 @@ func httpheaders(f *ast.File) bool { var headerTypeConfig = &TypeConfig{ Type: map[string]*Type{ - "*http.Request": &Type{}, - "*http.Response": &Type{}, + "*http.Request": {}, + "*http.Response": {}, }, } diff --git a/src/cmd/gofix/httpheaders_test.go b/src/cmd/gofix/httpheaders_test.go index cc82b5893..37506b82d 100644 --- a/src/cmd/gofix/httpheaders_test.go +++ b/src/cmd/gofix/httpheaders_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(httpHeadersTests) + addTestCases(httpHeadersTests, httpheaders) } var httpHeadersTests = []testCase{ diff --git a/src/cmd/gofix/httpserver.go b/src/cmd/gofix/httpserver.go index 37866e88b..7aa651786 100644 --- a/src/cmd/gofix/httpserver.go +++ b/src/cmd/gofix/httpserver.go @@ -9,8 +9,13 @@ import ( "go/token" ) +func init() { + register(httpserverFix) +} + var httpserverFix = fix{ "httpserver", + "2011-03-15", httpserver, `Adapt http server methods and functions to changes made to the http ResponseWriter interface. @@ -22,10 +27,6 @@ http://codereview.appspot.com/4248075 RemoteAddr, UsingTLS `, } -func init() { - register(httpserverFix) -} - func httpserver(f *ast.File) bool { if !imports(f, "http") { return false diff --git a/src/cmd/gofix/httpserver_test.go b/src/cmd/gofix/httpserver_test.go index 89bb4fa71..b6ddff27e 100644 --- a/src/cmd/gofix/httpserver_test.go +++ b/src/cmd/gofix/httpserver_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(httpserverTests) + addTestCases(httpserverTests, httpserver) } var httpserverTests = []testCase{ diff --git a/src/cmd/gofix/httputil.go b/src/cmd/gofix/httputil.go new file mode 100644 index 000000000..86c42e160 --- /dev/null +++ b/src/cmd/gofix/httputil.go @@ -0,0 +1,63 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + register(httputilFix) +} + +var httputilFix = fix{ + "httputil", + "2011-11-18", + httputil, + `Move some functions in http package into httputil package. + +http://codereview.appspot.com/5336049 +`, +} + +var httputilFuncs = []string{ + "DumpRequest", + "DumpRequestOut", + "DumpResponse", + "NewChunkedReader", + "NewChunkedWriter", + "NewClientConn", + "NewProxyClientConn", + "NewServerConn", + "NewSingleHostReverseProxy", +} + +func httputil(f *ast.File) bool { + if imports(f, "net/http/httputil") { + return false + } + + fixed := false + + walk(f, func(n interface{}) { + // Rename package name. + if expr, ok := n.(ast.Expr); ok { + for _, s := range httputilFuncs { + if isPkgDot(expr, "http", s) { + if !fixed { + addImport(f, "net/http/httputil") + fixed = true + } + expr.(*ast.SelectorExpr).X.(*ast.Ident).Name = "httputil" + } + } + } + }) + + // Remove the net/http import if no longer needed. + if fixed && !usesImport(f, "net/http") { + deleteImport(f, "net/http") + } + + return fixed +} diff --git a/src/cmd/gofix/httputil_test.go b/src/cmd/gofix/httputil_test.go new file mode 100644 index 000000000..83e9f6dfb --- /dev/null +++ b/src/cmd/gofix/httputil_test.go @@ -0,0 +1,122 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(httputilTests, httputil) +} + +var httputilTests = []testCase{ + { + Name: "httputil.0", + In: `package main + +import "net/http" + +func f() { + http.DumpRequest(nil, false) + http.DumpRequestOut(nil, false) + http.DumpResponse(nil, false) + http.NewChunkedReader(nil) + http.NewChunkedWriter(nil) + http.NewClientConn(nil, nil) + http.NewProxyClientConn(nil, nil) + http.NewServerConn(nil, nil) + http.NewSingleHostReverseProxy(nil) +} +`, + Out: `package main + +import "net/http/httputil" + +func f() { + httputil.DumpRequest(nil, false) + httputil.DumpRequestOut(nil, false) + httputil.DumpResponse(nil, false) + httputil.NewChunkedReader(nil) + httputil.NewChunkedWriter(nil) + httputil.NewClientConn(nil, nil) + httputil.NewProxyClientConn(nil, nil) + httputil.NewServerConn(nil, nil) + httputil.NewSingleHostReverseProxy(nil) +} +`, + }, + { + Name: "httputil.1", + In: `package main + +import "net/http" + +func f() { + http.DumpRequest(nil, false) + http.DumpRequestOut(nil, false) + http.DumpResponse(nil, false) + http.NewChunkedReader(nil) + http.NewChunkedWriter(nil) + http.NewClientConn(nil, nil) + http.NewProxyClientConn(nil, nil) + http.NewServerConn(nil, nil) + http.NewSingleHostReverseProxy(nil) +} +`, + Out: `package main + +import "net/http/httputil" + +func f() { + httputil.DumpRequest(nil, false) + httputil.DumpRequestOut(nil, false) + httputil.DumpResponse(nil, false) + httputil.NewChunkedReader(nil) + httputil.NewChunkedWriter(nil) + httputil.NewClientConn(nil, nil) + httputil.NewProxyClientConn(nil, nil) + httputil.NewServerConn(nil, nil) + httputil.NewSingleHostReverseProxy(nil) +} +`, + }, + { + Name: "httputil.2", + In: `package main + +import "net/http" + +func f() { + http.DumpRequest(nil, false) + http.DumpRequestOut(nil, false) + http.DumpResponse(nil, false) + http.NewChunkedReader(nil) + http.NewChunkedWriter(nil) + http.NewClientConn(nil, nil) + http.NewProxyClientConn(nil, nil) + http.NewServerConn(nil, nil) + http.NewSingleHostReverseProxy(nil) + http.Get("") +} +`, + Out: `package main + +import ( + "net/http" + "net/http/httputil" +) + +func f() { + httputil.DumpRequest(nil, false) + httputil.DumpRequestOut(nil, false) + httputil.DumpResponse(nil, false) + httputil.NewChunkedReader(nil) + httputil.NewChunkedWriter(nil) + httputil.NewClientConn(nil, nil) + httputil.NewProxyClientConn(nil, nil) + httputil.NewServerConn(nil, nil) + httputil.NewSingleHostReverseProxy(nil) + http.Get("") +} +`, + }, +} diff --git a/src/cmd/gofix/imagecolor.go b/src/cmd/gofix/imagecolor.go new file mode 100644 index 000000000..1aac40a6f --- /dev/null +++ b/src/cmd/gofix/imagecolor.go @@ -0,0 +1,85 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(imagecolorFix) +} + +var imagecolorFix = fix{ + "imagecolor", + "2011-10-04", + imagecolor, + `Adapt code to types moved from image to color. + +http://codereview.appspot.com/5132048 +`, +} + +var colorRenames = []struct{ in, out string }{ + {"Color", "Color"}, + {"ColorModel", "Model"}, + {"ColorModelFunc", "ModelFunc"}, + {"PalettedColorModel", "Palette"}, + + {"RGBAColor", "RGBA"}, + {"RGBA64Color", "RGBA64"}, + {"NRGBAColor", "NRGBA"}, + {"NRGBA64Color", "NRGBA64"}, + {"AlphaColor", "Alpha"}, + {"Alpha16Color", "Alpha16"}, + {"GrayColor", "Gray"}, + {"Gray16Color", "Gray16"}, + + {"RGBAColorModel", "RGBAModel"}, + {"RGBA64ColorModel", "RGBA64Model"}, + {"NRGBAColorModel", "NRGBAModel"}, + {"NRGBA64ColorModel", "NRGBA64Model"}, + {"AlphaColorModel", "AlphaModel"}, + {"Alpha16ColorModel", "Alpha16Model"}, + {"GrayColorModel", "GrayModel"}, + {"Gray16ColorModel", "Gray16Model"}, +} + +func imagecolor(f *ast.File) (fixed bool) { + if !imports(f, "image") { + return + } + + walk(f, func(n interface{}) { + s, ok := n.(*ast.SelectorExpr) + + if !ok || !isTopName(s.X, "image") { + return + } + + switch sel := s.Sel.String(); { + case sel == "ColorImage": + s.Sel = &ast.Ident{Name: "Uniform"} + fixed = true + case sel == "NewColorImage": + s.Sel = &ast.Ident{Name: "NewUniform"} + fixed = true + default: + for _, rename := range colorRenames { + if sel == rename.in { + addImport(f, "image/color") + s.X.(*ast.Ident).Name = "color" + s.Sel.Name = rename.out + fixed = true + } + } + } + }) + + if fixed && !usesImport(f, "image") { + deleteImport(f, "image") + } + return +} diff --git a/src/cmd/gofix/imagecolor_test.go b/src/cmd/gofix/imagecolor_test.go new file mode 100644 index 000000000..c62365481 --- /dev/null +++ b/src/cmd/gofix/imagecolor_test.go @@ -0,0 +1,126 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(colorTests, imagecolor) +} + +var colorTests = []testCase{ + { + Name: "color.0", + In: `package main + +import ( + "image" +) + +var ( + _ image.Image + _ image.RGBA + _ image.Black + _ image.Color + _ image.ColorModel + _ image.ColorModelFunc + _ image.PalettedColorModel + _ image.RGBAColor + _ image.RGBA64Color + _ image.NRGBAColor + _ image.NRGBA64Color + _ image.AlphaColor + _ image.Alpha16Color + _ image.GrayColor + _ image.Gray16Color +) + +func f() { + _ = image.RGBAColorModel + _ = image.RGBA64ColorModel + _ = image.NRGBAColorModel + _ = image.NRGBA64ColorModel + _ = image.AlphaColorModel + _ = image.Alpha16ColorModel + _ = image.GrayColorModel + _ = image.Gray16ColorModel +} +`, + Out: `package main + +import ( + "image" + "image/color" +) + +var ( + _ image.Image + _ image.RGBA + _ image.Black + _ color.Color + _ color.Model + _ color.ModelFunc + _ color.Palette + _ color.RGBA + _ color.RGBA64 + _ color.NRGBA + _ color.NRGBA64 + _ color.Alpha + _ color.Alpha16 + _ color.Gray + _ color.Gray16 +) + +func f() { + _ = color.RGBAModel + _ = color.RGBA64Model + _ = color.NRGBAModel + _ = color.NRGBA64Model + _ = color.AlphaModel + _ = color.Alpha16Model + _ = color.GrayModel + _ = color.Gray16Model +} +`, + }, + { + Name: "color.1", + In: `package main + +import ( + "fmt" + "image" +) + +func f() { + fmt.Println(image.RGBAColor{1, 2, 3, 4}.RGBA()) +} +`, + Out: `package main + +import ( + "fmt" + "image/color" +) + +func f() { + fmt.Println(color.RGBA{1, 2, 3, 4}.RGBA()) +} +`, + }, + { + Name: "color.2", + In: `package main + +import "image" + +var c *image.ColorImage = image.NewColorImage(nil) +`, + Out: `package main + +import "image" + +var c *image.Uniform = image.NewUniform(nil) +`, + }, +} diff --git a/src/cmd/gofix/imagenew.go b/src/cmd/gofix/imagenew.go index 0b3c0a307..b4e36d4f0 100644 --- a/src/cmd/gofix/imagenew.go +++ b/src/cmd/gofix/imagenew.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(imagenewFix) +} + var imagenewFix = fix{ "imagenew", + "2011-09-14", imagenew, `Adapt image.NewXxx calls to pass an image.Rectangle instead of (w, h int). @@ -17,10 +22,6 @@ http://codereview.appspot.com/4964073 `, } -func init() { - register(imagenewFix) -} - var imagenewFuncs = map[string]bool{ "NewRGBA": true, "NewRGBA64": true, diff --git a/src/cmd/gofix/imagenew_test.go b/src/cmd/gofix/imagenew_test.go index 3d40fea81..30abed23c 100644 --- a/src/cmd/gofix/imagenew_test.go +++ b/src/cmd/gofix/imagenew_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(imagenewTests) + addTestCases(imagenewTests, imagenew) } var imagenewTests = []testCase{ @@ -26,8 +26,7 @@ func f() { image.NewAlpha16(1, 2) image.NewGray(1, 2) image.NewGray16(1, 2) - var m image.PalettedColorModel - image.NewPaletted(1, 2, m) + image.NewPaletted(1, 2, nil) } `, Out: `package main @@ -45,8 +44,7 @@ func f() { image.NewAlpha16(image.Rect(0, 0, 1, 2)) image.NewGray(image.Rect(0, 0, 1, 2)) image.NewGray16(image.Rect(0, 0, 1, 2)) - var m image.PalettedColorModel - image.NewPaletted(image.Rect(0, 0, 1, 2), m) + image.NewPaletted(image.Rect(0, 0, 1, 2), nil) } `, }, diff --git a/src/cmd/gofix/imagetiled.go b/src/cmd/gofix/imagetiled.go new file mode 100644 index 000000000..d8f3f7980 --- /dev/null +++ b/src/cmd/gofix/imagetiled.go @@ -0,0 +1,40 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(imagetiledFix) +} + +var imagetiledFix = fix{ + "imagetiled", + "2012-01-10", + imagetiled, + `Rename image.Tiled to image.Repeated. + +http://codereview.appspot.com/5530062 +`, +} + +func imagetiled(f *ast.File) bool { + if !imports(f, "image") { + return false + } + + fixed := false + walk(f, func(n interface{}) { + s, ok := n.(*ast.SelectorExpr) + if !ok || !isTopName(s.X, "image") || s.Sel.String() != "Tiled" { + return + } + s.Sel = &ast.Ident{Name: "Repeated"} + fixed = true + }) + return fixed +} diff --git a/src/cmd/gofix/imagetiled_test.go b/src/cmd/gofix/imagetiled_test.go new file mode 100644 index 000000000..98a9c0a8d --- /dev/null +++ b/src/cmd/gofix/imagetiled_test.go @@ -0,0 +1,41 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(imagetiledTests, imagetiled) +} + +var imagetiledTests = []testCase{ + { + Name: "imagetiled.0", + In: `package main + +import ( + "foo" + "image" +) + +var ( + _ foo.Tiled + _ image.RGBA + _ image.Tiled +) +`, + Out: `package main + +import ( + "foo" + "image" +) + +var ( + _ foo.Tiled + _ image.RGBA + _ image.Repeated +) +`, + }, +} diff --git a/src/cmd/gofix/imageycbcr.go b/src/cmd/gofix/imageycbcr.go new file mode 100644 index 000000000..41b96d18d --- /dev/null +++ b/src/cmd/gofix/imageycbcr.go @@ -0,0 +1,64 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(imageycbcrFix) +} + +var imageycbcrFix = fix{ + "imageycbcr", + "2011-12-20", + imageycbcr, + `Adapt code to types moved from image/ycbcr to image and image/color. + +http://codereview.appspot.com/5493084 +`, +} + +func imageycbcr(f *ast.File) (fixed bool) { + if !imports(f, "image/ycbcr") { + return + } + + walk(f, func(n interface{}) { + s, ok := n.(*ast.SelectorExpr) + + if !ok || !isTopName(s.X, "ycbcr") { + return + } + + switch s.Sel.String() { + case "RGBToYCbCr", "YCbCrToRGB": + addImport(f, "image/color") + s.X.(*ast.Ident).Name = "color" + case "YCbCrColor": + addImport(f, "image/color") + s.X.(*ast.Ident).Name = "color" + s.Sel.Name = "YCbCr" + case "YCbCrColorModel": + addImport(f, "image/color") + s.X.(*ast.Ident).Name = "color" + s.Sel.Name = "YCbCrModel" + case "SubsampleRatio", "SubsampleRatio444", "SubsampleRatio422", "SubsampleRatio420": + addImport(f, "image") + s.X.(*ast.Ident).Name = "image" + s.Sel.Name = "YCbCr" + s.Sel.Name + case "YCbCr": + addImport(f, "image") + s.X.(*ast.Ident).Name = "image" + default: + return + } + fixed = true + }) + + deleteImport(f, "image/ycbcr") + return +} diff --git a/src/cmd/gofix/imageycbcr_test.go b/src/cmd/gofix/imageycbcr_test.go new file mode 100644 index 000000000..23b599dcd --- /dev/null +++ b/src/cmd/gofix/imageycbcr_test.go @@ -0,0 +1,54 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(ycbcrTests, imageycbcr) +} + +var ycbcrTests = []testCase{ + { + Name: "ycbcr.0", + In: `package main + +import ( + "image/ycbcr" +) + +func f() { + _ = ycbcr.RGBToYCbCr + _ = ycbcr.YCbCrToRGB + _ = ycbcr.YCbCrColorModel + var _ ycbcr.YCbCrColor + var _ ycbcr.YCbCr + var ( + _ ycbcr.SubsampleRatio = ycbcr.SubsampleRatio444 + _ ycbcr.SubsampleRatio = ycbcr.SubsampleRatio422 + _ ycbcr.SubsampleRatio = ycbcr.SubsampleRatio420 + ) +} +`, + Out: `package main + +import ( + "image" + "image/color" +) + +func f() { + _ = color.RGBToYCbCr + _ = color.YCbCrToRGB + _ = color.YCbCrModel + var _ color.YCbCr + var _ image.YCbCr + var ( + _ image.YCbCrSubsampleRatio = image.YCbCrSubsampleRatio444 + _ image.YCbCrSubsampleRatio = image.YCbCrSubsampleRatio422 + _ image.YCbCrSubsampleRatio = image.YCbCrSubsampleRatio420 + ) +} +`, + }, +} diff --git a/src/cmd/gofix/import_test.go b/src/cmd/gofix/import_test.go new file mode 100644 index 000000000..730119205 --- /dev/null +++ b/src/cmd/gofix/import_test.go @@ -0,0 +1,458 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + addTestCases(importTests, nil) +} + +var importTests = []testCase{ + { + Name: "import.0", + Fn: addImportFn("os"), + In: `package main + +import ( + "os" +) +`, + Out: `package main + +import ( + "os" +) +`, + }, + { + Name: "import.1", + Fn: addImportFn("os"), + In: `package main +`, + Out: `package main + +import "os" +`, + }, + { + Name: "import.2", + Fn: addImportFn("os"), + In: `package main + +// Comment +import "C" +`, + Out: `package main + +// Comment +import "C" +import "os" +`, + }, + { + Name: "import.3", + Fn: addImportFn("os"), + In: `package main + +// Comment +import "C" + +import ( + "io" + "utf8" +) +`, + Out: `package main + +// Comment +import "C" + +import ( + "io" + "os" + "utf8" +) +`, + }, + { + Name: "import.4", + Fn: deleteImportFn("os"), + In: `package main + +import ( + "os" +) +`, + Out: `package main +`, + }, + { + Name: "import.5", + Fn: deleteImportFn("os"), + In: `package main + +// Comment +import "C" +import "os" +`, + Out: `package main + +// Comment +import "C" +`, + }, + { + Name: "import.6", + Fn: deleteImportFn("os"), + In: `package main + +// Comment +import "C" + +import ( + "io" + "os" + "utf8" +) +`, + Out: `package main + +// Comment +import "C" + +import ( + "io" + "utf8" +) +`, + }, + { + Name: "import.7", + Fn: deleteImportFn("io"), + In: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + Out: `package main + +import ( + // a + "os" // b + "utf8" // c +) +`, + }, + { + Name: "import.8", + Fn: deleteImportFn("os"), + In: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + Out: `package main + +import ( + "io" // a + // b + "utf8" // c +) +`, + }, + { + Name: "import.9", + Fn: deleteImportFn("utf8"), + In: `package main + +import ( + "io" // a + "os" // b + "utf8" // c +) +`, + Out: `package main + +import ( + "io" // a + "os" // b + // c +) +`, + }, + { + Name: "import.10", + Fn: deleteImportFn("io"), + In: `package main + +import ( + "io" + "os" + "utf8" +) +`, + Out: `package main + +import ( + "os" + "utf8" +) +`, + }, + { + Name: "import.11", + Fn: deleteImportFn("os"), + In: `package main + +import ( + "io" + "os" + "utf8" +) +`, + Out: `package main + +import ( + "io" + "utf8" +) +`, + }, + { + Name: "import.12", + Fn: deleteImportFn("utf8"), + In: `package main + +import ( + "io" + "os" + "utf8" +) +`, + Out: `package main + +import ( + "io" + "os" +) +`, + }, + { + Name: "import.13", + Fn: rewriteImportFn("utf8", "encoding/utf8"), + In: `package main + +import ( + "io" + "os" + "utf8" // thanks ken +) +`, + Out: `package main + +import ( + "encoding/utf8" // thanks ken + "io" + "os" +) +`, + }, + { + Name: "import.14", + Fn: rewriteImportFn("asn1", "encoding/asn1"), + In: `package main + +import ( + "asn1" + "crypto" + "crypto/rsa" + _ "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "time" +) + +var x = 1 +`, + Out: `package main + +import ( + "crypto" + "crypto/rsa" + _ "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "time" +) + +var x = 1 +`, + }, + { + Name: "import.15", + Fn: rewriteImportFn("url", "net/url"), + In: `package main + +import ( + "bufio" + "net" + "path" + "url" +) + +var x = 1 // comment on x, not on url +`, + Out: `package main + +import ( + "bufio" + "net" + "net/url" + "path" +) + +var x = 1 // comment on x, not on url +`, + }, + { + Name: "import.16", + Fn: rewriteImportFn("http", "net/http", "template", "text/template"), + In: `package main + +import ( + "flag" + "http" + "log" + "template" +) + +var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 +`, + Out: `package main + +import ( + "flag" + "log" + "net/http" + "text/template" +) + +var addr = flag.String("addr", ":1718", "http service address") // Q=17, R=18 +`, + }, + { + Name: "import.17", + Fn: addImportFn("x/y/z", "x/a/c"), + In: `package main + +// Comment +import "C" + +import ( + "a" + "b" + + "x/w" + + "d/f" +) +`, + Out: `package main + +// Comment +import "C" + +import ( + "a" + "b" + + "x/a/c" + "x/w" + "x/y/z" + + "d/f" +) +`, + }, + { + Name: "import.18", + Fn: addDelImportFn("e", "o"), + In: `package main + +import ( + "f" + "o" + "z" +) +`, + Out: `package main + +import ( + "e" + "f" + "z" +) +`, + }, +} + +func addImportFn(path ...string) func(*ast.File) bool { + return func(f *ast.File) bool { + fixed := false + for _, p := range path { + if !imports(f, p) { + addImport(f, p) + fixed = true + } + } + return fixed + } +} + +func deleteImportFn(path string) func(*ast.File) bool { + return func(f *ast.File) bool { + if imports(f, path) { + deleteImport(f, path) + return true + } + return false + } +} + +func addDelImportFn(p1 string, p2 string) func(*ast.File) bool { + return func(f *ast.File) bool { + fixed := false + if !imports(f, p1) { + addImport(f, p1) + fixed = true + } + if imports(f, p2) { + deleteImport(f, p2) + fixed = true + } + return fixed + } +} + +func rewriteImportFn(oldnew ...string) func(*ast.File) bool { + return func(f *ast.File) bool { + fixed := false + for i := 0; i < len(oldnew); i += 2 { + if imports(f, oldnew[i]) { + rewriteImport(f, oldnew[i], oldnew[i+1]) + fixed = true + } + } + return fixed + } +} diff --git a/src/cmd/gofix/iocopyn.go b/src/cmd/gofix/iocopyn.go new file mode 100644 index 000000000..720f3c689 --- /dev/null +++ b/src/cmd/gofix/iocopyn.go @@ -0,0 +1,41 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(ioCopyNFix) +} + +var ioCopyNFix = fix{ + "iocopyn", + "2011-09-30", + ioCopyN, + `Rename io.Copyn to io.CopyN. + +http://codereview.appspot.com/5157045 +`, +} + +func ioCopyN(f *ast.File) bool { + if !imports(f, "io") { + return false + } + + fixed := false + walk(f, func(n interface{}) { + if expr, ok := n.(ast.Expr); ok { + if isPkgDot(expr, "io", "Copyn") { + expr.(*ast.SelectorExpr).Sel.Name = "CopyN" + fixed = true + return + } + } + }) + return fixed +} diff --git a/src/cmd/gofix/iocopyn_test.go b/src/cmd/gofix/iocopyn_test.go new file mode 100644 index 000000000..f86fad763 --- /dev/null +++ b/src/cmd/gofix/iocopyn_test.go @@ -0,0 +1,37 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(ioCopyNTests, ioCopyN) +} + +var ioCopyNTests = []testCase{ + { + Name: "io.CopyN.0", + In: `package main + +import ( + "io" +) + +func f() { + io.Copyn(dst, src) + foo.Copyn(dst, src) +} +`, + Out: `package main + +import ( + "io" +) + +func f() { + io.CopyN(dst, src) + foo.Copyn(dst, src) +} +`, + }, +} diff --git a/src/cmd/gofix/main.go b/src/cmd/gofix/main.go index e0709fc8b..ca7e1a0f3 100644 --- a/src/cmd/gofix/main.go +++ b/src/cmd/gofix/main.go @@ -6,15 +6,16 @@ package main import ( "bytes" - "exec" "flag" "fmt" + "go/ast" "go/parser" "go/printer" "go/scanner" "go/token" "io/ioutil" "os" + "os/exec" "path/filepath" "sort" "strings" @@ -28,14 +29,21 @@ var ( var allowedRewrites = flag.String("r", "", "restrict the rewrites to this comma-separated list") -var allowed map[string]bool +var forceRewrites = flag.String("force", "", + "force these fixes to run even if the code looks updated") + +var allowed, force map[string]bool var doDiff = flag.Bool("diff", false, "display diffs instead of rewriting files") +// enable for debugging gofix failures +const debug = false // display incorrectly reformatted source and exit + func usage() { - fmt.Fprintf(os.Stderr, "usage: gofix [-diff] [-r fixname,...] [path ...]\n") + fmt.Fprintf(os.Stderr, "usage: gofix [-diff] [-r fixname,...] [-force fixname,...] [path ...]\n") flag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nAvailable rewrites are:\n") + sort.Sort(byName(fixes)) for _, f := range fixes { fmt.Fprintf(os.Stderr, "\n%s\n", f.name) desc := strings.TrimSpace(f.desc) @@ -46,11 +54,11 @@ func usage() { } func main() { - sort.Sort(fixes) - flag.Usage = usage flag.Parse() + sort.Sort(byDate(fixes)) + if *allowedRewrites != "" { allowed = make(map[string]bool) for _, f := range strings.Split(*allowedRewrites, ",") { @@ -58,6 +66,13 @@ func main() { } } + if *forceRewrites != "" { + force = make(map[string]bool) + for _, f := range strings.Split(*forceRewrites, ",") { + force[f] = true + } + } + if flag.NArg() == 0 { if err := processFile("standard input", true); err != nil { report(err) @@ -70,12 +85,12 @@ func main() { switch dir, err := os.Stat(path); { case err != nil: report(err) - case dir.IsRegular(): + case dir.IsDir(): + walkDir(path) + default: if err := processFile(path, false); err != nil { report(err) } - case dir.IsDirectory(): - walkDir(path) } } @@ -93,11 +108,21 @@ var printConfig = &printer.Config{ tabWidth, } -func processFile(filename string, useStdin bool) os.Error { +func gofmtFile(f *ast.File) ([]byte, error) { + var buf bytes.Buffer + + ast.SortImports(fset, f) + err := printConfig.Fprint(&buf, fset, f) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func processFile(filename string, useStdin bool) error { var f *os.File - var err os.Error + var err error var fixlog bytes.Buffer - var buf bytes.Buffer if useStdin { f = os.Stdin @@ -133,14 +158,17 @@ func processFile(filename string, useStdin bool) os.Error { // AST changed. // Print and parse, to update any missing scoping // or position information for subsequent fixers. - buf.Reset() - _, err = printConfig.Fprint(&buf, fset, newFile) + newSrc, err := gofmtFile(newFile) if err != nil { return err } - newSrc := buf.Bytes() newFile, err = parser.ParseFile(fset, filename, newSrc, parserMode) if err != nil { + if debug { + fmt.Printf("%s", newSrc) + report(err) + os.Exit(exitCode) + } return err } } @@ -156,12 +184,10 @@ func processFile(filename string, useStdin bool) os.Error { // output of the printer run on a standard AST generated by the parser, // but the source we generated inside the loop above is the // output of the printer run on a mangled AST generated by a fixer. - buf.Reset() - _, err = printConfig.Fprint(&buf, fset, newFile) + newSrc, err := gofmtFile(newFile) if err != nil { return err } - newSrc := buf.Bytes() if *doDiff { data, err := diff(src, newSrc) @@ -185,14 +211,14 @@ var gofmtBuf bytes.Buffer func gofmt(n interface{}) string { gofmtBuf.Reset() - _, err := printConfig.Fprint(&gofmtBuf, fset, n) + err := printConfig.Fprint(&gofmtBuf, fset, n) if err != nil { - return "<" + err.String() + ">" + return "<" + err.Error() + ">" } return gofmtBuf.String() } -func report(err os.Error) { +func report(err error) { scanner.PrintError(os.Stderr, err) exitCode = 2 } @@ -201,7 +227,7 @@ func walkDir(path string) { filepath.Walk(path, visitFile) } -func visitFile(path string, f *os.FileInfo, err os.Error) os.Error { +func visitFile(path string, f os.FileInfo, err error) error { if err == nil && isGoFile(f) { err = processFile(path, false) } @@ -211,12 +237,13 @@ func visitFile(path string, f *os.FileInfo, err os.Error) os.Error { return nil } -func isGoFile(f *os.FileInfo) bool { +func isGoFile(f os.FileInfo) bool { // ignore non-Go files - return f.IsRegular() && !strings.HasPrefix(f.Name, ".") && strings.HasSuffix(f.Name, ".go") + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") } -func diff(b1, b2 []byte) (data []byte, err os.Error) { +func diff(b1, b2 []byte) (data []byte, err error) { f1, err := ioutil.TempFile("", "gofix") if err != nil { return nil, err diff --git a/src/cmd/gofix/main_test.go b/src/cmd/gofix/main_test.go index 275778e5b..2151bf29e 100644 --- a/src/cmd/gofix/main_test.go +++ b/src/cmd/gofix/main_test.go @@ -5,10 +5,8 @@ package main import ( - "bytes" "go/ast" "go/parser" - "go/printer" "strings" "testing" ) @@ -22,27 +20,33 @@ type testCase struct { var testCases []testCase -func addTestCases(t []testCase) { +func addTestCases(t []testCase, fn func(*ast.File) bool) { + // Fill in fn to avoid repetition in definitions. + if fn != nil { + for i := range t { + if t[i].Fn == nil { + t[i].Fn = fn + } + } + } testCases = append(testCases, t...) } func fnop(*ast.File) bool { return false } -func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string) (out string, fixed, ok bool) { +func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string, mustBeGofmt bool) (out string, fixed, ok bool) { file, err := parser.ParseFile(fset, desc, in, parserMode) if err != nil { t.Errorf("%s: parsing: %v", desc, err) return } - var buf bytes.Buffer - buf.Reset() - _, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file) + outb, err := gofmtFile(file) if err != nil { t.Errorf("%s: printing: %v", desc, err) return } - if s := buf.String(); in != s && fn != fnop { + if s := string(outb); in != s && mustBeGofmt { t.Errorf("%s: not gofmt-formatted.\n--- %s\n%s\n--- %s | gofmt\n%s", desc, desc, in, desc, s) tdiff(t, in, s) @@ -59,26 +63,25 @@ func parseFixPrint(t *testing.T, fn func(*ast.File) bool, desc, in string) (out fixed = fn(file) } - buf.Reset() - _, err = (&printer.Config{printerMode, tabWidth}).Fprint(&buf, fset, file) + outb, err = gofmtFile(file) if err != nil { t.Errorf("%s: printing: %v", desc, err) return } - return buf.String(), fixed, true + return string(outb), fixed, true } func TestRewrite(t *testing.T) { for _, tt := range testCases { // Apply fix: should get tt.Out. - out, fixed, ok := parseFixPrint(t, tt.Fn, tt.Name, tt.In) + out, fixed, ok := parseFixPrint(t, tt.Fn, tt.Name, tt.In, true) if !ok { continue } // reformat to get printing right - out, _, ok = parseFixPrint(t, fnop, tt.Name, out) + out, _, ok = parseFixPrint(t, fnop, tt.Name, out, false) if !ok { continue } @@ -98,7 +101,7 @@ func TestRewrite(t *testing.T) { } // Should not change if run again. - out2, fixed2, ok := parseFixPrint(t, tt.Fn, tt.Name+" output", out) + out2, fixed2, ok := parseFixPrint(t, tt.Fn, tt.Name+" output", out, true) if !ok { continue } diff --git a/src/cmd/gofix/mapdelete.go b/src/cmd/gofix/mapdelete.go new file mode 100644 index 000000000..db89c7bf4 --- /dev/null +++ b/src/cmd/gofix/mapdelete.go @@ -0,0 +1,89 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + register(mapdeleteFix) +} + +var mapdeleteFix = fix{ + "mapdelete", + "2011-10-18", + mapdelete, + `Use delete(m, k) instead of m[k] = 0, false. + +http://codereview.appspot.com/5272045 +`, +} + +func mapdelete(f *ast.File) bool { + fixed := false + walk(f, func(n interface{}) { + stmt, ok := n.(*ast.Stmt) + if !ok { + return + } + as, ok := (*stmt).(*ast.AssignStmt) + if !ok || len(as.Lhs) != 1 || len(as.Rhs) != 2 { + return + } + ix, ok := as.Lhs[0].(*ast.IndexExpr) + if !ok { + return + } + if !isTopName(as.Rhs[1], "false") { + warn(as.Pos(), "two-element map assignment with non-false second value") + return + } + if !canDrop(as.Rhs[0]) { + warn(as.Pos(), "two-element map assignment with non-trivial first value") + return + } + *stmt = &ast.ExprStmt{ + X: &ast.CallExpr{ + Fun: &ast.Ident{ + NamePos: as.Pos(), + Name: "delete", + }, + Args: []ast.Expr{ix.X, ix.Index}, + }, + } + fixed = true + }) + return fixed +} + +// canDrop reports whether it is safe to drop the +// evaluation of n from the program. +// It is very conservative. +func canDrop(n ast.Expr) bool { + switch n := n.(type) { + case *ast.Ident, *ast.BasicLit: + return true + case *ast.ParenExpr: + return canDrop(n.X) + case *ast.SelectorExpr: + return canDrop(n.X) + case *ast.CompositeLit: + if !canDrop(n.Type) { + return false + } + for _, e := range n.Elts { + if !canDrop(e) { + return false + } + } + return true + case *ast.StarExpr: + // Dropping *x is questionable, + // but we have to be able to drop (*T)(nil). + return canDrop(n.X) + case *ast.ArrayType, *ast.ChanType, *ast.FuncType, *ast.InterfaceType, *ast.MapType, *ast.StructType: + return true + } + return false +} diff --git a/src/cmd/gofix/mapdelete_test.go b/src/cmd/gofix/mapdelete_test.go new file mode 100644 index 000000000..8ed50328e --- /dev/null +++ b/src/cmd/gofix/mapdelete_test.go @@ -0,0 +1,43 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(mapdeleteTests, mapdelete) +} + +var mapdeleteTests = []testCase{ + { + Name: "mapdelete.0", + In: `package main + +func f() { + m[x] = 0, false + m[x] = g(), false + m[x] = 1 + delete(m, x) + m[x] = 0, b +} + +func g(false bool) { + m[x] = 0, false +} +`, + Out: `package main + +func f() { + delete(m, x) + m[x] = g(), false + m[x] = 1 + delete(m, x) + m[x] = 0, b +} + +func g(false bool) { + m[x] = 0, false +} +`, + }, +} diff --git a/src/cmd/gofix/math.go b/src/cmd/gofix/math.go new file mode 100644 index 000000000..2ec837eb0 --- /dev/null +++ b/src/cmd/gofix/math.go @@ -0,0 +1,51 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + register(mathFix) +} + +var mathFix = fix{ + "math", + "2011-09-29", + math, + `Remove the leading F from math functions such as Fabs. + +http://codereview.appspot.com/5158043 +`, +} + +var mathRenames = []struct{ in, out string }{ + {"Fabs", "Abs"}, + {"Fdim", "Dim"}, + {"Fmax", "Max"}, + {"Fmin", "Min"}, + {"Fmod", "Mod"}, +} + +func math(f *ast.File) bool { + if !imports(f, "math") { + return false + } + + fixed := false + + walk(f, func(n interface{}) { + // Rename functions. + if expr, ok := n.(ast.Expr); ok { + for _, s := range mathRenames { + if isPkgDot(expr, "math", s.in) { + expr.(*ast.SelectorExpr).Sel.Name = s.out + fixed = true + return + } + } + } + }) + return fixed +} diff --git a/src/cmd/gofix/math_test.go b/src/cmd/gofix/math_test.go new file mode 100644 index 000000000..b8d69d2f2 --- /dev/null +++ b/src/cmd/gofix/math_test.go @@ -0,0 +1,47 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(mathTests, math) +} + +var mathTests = []testCase{ + { + Name: "math.0", + In: `package main + +import ( + "math" +) + +func f() { + math.Fabs(1) + math.Fdim(1) + math.Fmax(1) + math.Fmin(1) + math.Fmod(1) + math.Abs(1) + foo.Fabs(1) +} +`, + Out: `package main + +import ( + "math" +) + +func f() { + math.Abs(1) + math.Dim(1) + math.Max(1) + math.Min(1) + math.Mod(1) + math.Abs(1) + foo.Fabs(1) +} +`, + }, +} diff --git a/src/cmd/gofix/netdial.go b/src/cmd/gofix/netdial.go index afa98953b..2de994cff 100644 --- a/src/cmd/gofix/netdial.go +++ b/src/cmd/gofix/netdial.go @@ -8,8 +8,15 @@ import ( "go/ast" ) +func init() { + register(netdialFix) + register(tlsdialFix) + register(netlookupFix) +} + var netdialFix = fix{ "netdial", + "2011-03-28", netdial, `Adapt 3-argument calls of net.Dial to use 2-argument form. @@ -19,6 +26,7 @@ http://codereview.appspot.com/4244055 var tlsdialFix = fix{ "tlsdial", + "2011-03-28", tlsdial, `Adapt 4-argument calls of tls.Dial to use 3-argument form. @@ -28,6 +36,7 @@ http://codereview.appspot.com/4244055 var netlookupFix = fix{ "netlookup", + "2011-03-28", netlookup, `Adapt 3-result calls to net.LookupHost to use 2-result form. @@ -35,12 +44,6 @@ http://codereview.appspot.com/4244055 `, } -func init() { - register(netdialFix) - register(tlsdialFix) - register(netlookupFix) -} - func netdial(f *ast.File) bool { if !imports(f, "net") { return false diff --git a/src/cmd/gofix/netdial_test.go b/src/cmd/gofix/netdial_test.go index 272aa526a..fff00b4ad 100644 --- a/src/cmd/gofix/netdial_test.go +++ b/src/cmd/gofix/netdial_test.go @@ -1,12 +1,17 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + package main func init() { - addTestCases(netdialTests) + addTestCases(netdialTests, nil) } var netdialTests = []testCase{ { Name: "netdial.0", + Fn: netdial, In: `package main import "net" @@ -29,6 +34,7 @@ func f() { { Name: "netlookup.0", + Fn: netlookup, In: `package main import "net" diff --git a/src/cmd/gofix/netudpgroup.go b/src/cmd/gofix/netudpgroup.go index 347452d43..b54beb0de 100644 --- a/src/cmd/gofix/netudpgroup.go +++ b/src/cmd/gofix/netudpgroup.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(netudpgroupFix) +} + var netudpgroupFix = fix{ "netudpgroup", + "2011-08-18", netudpgroup, `Adapt 1-argument calls of net.(*UDPConn).JoinGroup, LeaveGroup to use 2-argument form. @@ -17,10 +22,6 @@ http://codereview.appspot.com/4815074 `, } -func init() { - register(netudpgroupFix) -} - func netudpgroup(f *ast.File) bool { if !imports(f, "net") { return false @@ -29,7 +30,7 @@ func netudpgroup(f *ast.File) bool { fixed := false for _, d := range f.Decls { fd, ok := d.(*ast.FuncDecl) - if !ok { + if !ok || fd.Body == nil { continue } walk(fd.Body, func(n interface{}) { diff --git a/src/cmd/gofix/netudpgroup_test.go b/src/cmd/gofix/netudpgroup_test.go index b3b5816da..88c0e093f 100644 --- a/src/cmd/gofix/netudpgroup_test.go +++ b/src/cmd/gofix/netudpgroup_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(netudpgroupTests) + addTestCases(netudpgroupTests, netudpgroup) } var netudpgroupTests = []testCase{ @@ -30,4 +30,24 @@ func f() { } `, }, + // Innocent function with no body. + { + Name: "netudpgroup.1", + In: `package main + +import "net" + +func f() + +var _ net.IP +`, + Out: `package main + +import "net" + +func f() + +var _ net.IP +`, + }, } diff --git a/src/cmd/gofix/oserrorstring.go b/src/cmd/gofix/oserrorstring.go index db39ee9dc..a75a2c12d 100644 --- a/src/cmd/gofix/oserrorstring.go +++ b/src/cmd/gofix/oserrorstring.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(oserrorstringFix) +} + var oserrorstringFix = fix{ "oserrorstring", + "2011-06-22", oserrorstring, `Replace os.ErrorString() conversions with calls to os.NewError(). @@ -17,10 +22,6 @@ http://codereview.appspot.com/4607052 `, } -func init() { - register(oserrorstringFix) -} - func oserrorstring(f *ast.File) bool { if !imports(f, "os") { return false diff --git a/src/cmd/gofix/oserrorstring_test.go b/src/cmd/gofix/oserrorstring_test.go index 070d9222b..75551480c 100644 --- a/src/cmd/gofix/oserrorstring_test.go +++ b/src/cmd/gofix/oserrorstring_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(oserrorstringTests) + addTestCases(oserrorstringTests, oserrorstring) } var oserrorstringTests = []testCase{ diff --git a/src/cmd/gofix/osopen.go b/src/cmd/gofix/osopen.go index 19c19b5b6..af2796ac2 100644 --- a/src/cmd/gofix/osopen.go +++ b/src/cmd/gofix/osopen.go @@ -8,8 +8,13 @@ import ( "go/ast" ) +func init() { + register(osopenFix) +} + var osopenFix = fix{ "osopen", + "2011-04-04", osopen, `Adapt os.Open calls to new, easier API and rename O_CREAT O_CREATE. @@ -17,10 +22,6 @@ http://codereview.appspot.com/4357052 `, } -func init() { - register(osopenFix) -} - func osopen(f *ast.File) bool { if !imports(f, "os") { return false diff --git a/src/cmd/gofix/osopen_test.go b/src/cmd/gofix/osopen_test.go index a33bcd4fb..5797adb7b 100644 --- a/src/cmd/gofix/osopen_test.go +++ b/src/cmd/gofix/osopen_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(osopenTests) + addTestCases(osopenTests, osopen) } var osopenTests = []testCase{ diff --git a/src/cmd/gofix/procattr.go b/src/cmd/gofix/procattr.go index 0e2190b1f..ea375ec9d 100644 --- a/src/cmd/gofix/procattr.go +++ b/src/cmd/gofix/procattr.go @@ -9,8 +9,13 @@ import ( "go/token" ) +func init() { + register(procattrFix) +} + var procattrFix = fix{ "procattr", + "2011-03-15", procattr, `Adapt calls to os.StartProcess to use new ProcAttr type. @@ -18,10 +23,6 @@ http://codereview.appspot.com/4253052 `, } -func init() { - register(procattrFix) -} - func procattr(f *ast.File) bool { if !imports(f, "os") && !imports(f, "syscall") { return false diff --git a/src/cmd/gofix/procattr_test.go b/src/cmd/gofix/procattr_test.go index b973b9684..9e2b86e74 100644 --- a/src/cmd/gofix/procattr_test.go +++ b/src/cmd/gofix/procattr_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(procattrTests) + addTestCases(procattrTests, procattr) } var procattrTests = []testCase{ diff --git a/src/cmd/gofix/reflect.go b/src/cmd/gofix/reflect.go index 3c8becaef..4665d1527 100644 --- a/src/cmd/gofix/reflect.go +++ b/src/cmd/gofix/reflect.go @@ -15,8 +15,13 @@ import ( "strings" ) +func init() { + register(reflectFix) +} + var reflectFix = fix{ "reflect", + "2011-04-08", reflectFn, `Adapt code to new reflect API. @@ -25,10 +30,6 @@ http://codereview.appspot.com/4433066 `, } -func init() { - register(reflectFix) -} - // The reflect API change dropped the concrete types *reflect.ArrayType etc. // Any type assertions prior to method calls can be deleted: // x.(*reflect.ArrayType).Len() -> x.Len() @@ -99,7 +100,7 @@ func reflectFn(f *ast.File) bool { // Rewrite names in method calls. // Needs basic type information (see above). - typeof := typecheck(reflectTypeConfig, f) + typeof, _ := typecheck(reflectTypeConfig, f) walk(f, func(n interface{}) { switch n := n.(type) { case *ast.SelectorExpr: @@ -616,75 +617,75 @@ func reflectFixAssert(n interface{}) bool { // which implements Type.) var reflectTypeConfig = &TypeConfig{ Type: map[string]*Type{ - "reflect.ArrayOrSliceType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.ArrayOrSliceValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.ArrayType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.ArrayValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.BoolType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.BoolValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.ChanType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.ChanValue": &Type{ + "reflect.ArrayOrSliceType": {Embed: []string{"reflect.Type"}}, + "reflect.ArrayOrSliceValue": {Embed: []string{"reflect.Value"}}, + "reflect.ArrayType": {Embed: []string{"reflect.Type"}}, + "reflect.ArrayValue": {Embed: []string{"reflect.Value"}}, + "reflect.BoolType": {Embed: []string{"reflect.Type"}}, + "reflect.BoolValue": {Embed: []string{"reflect.Value"}}, + "reflect.ChanType": {Embed: []string{"reflect.Type"}}, + "reflect.ChanValue": { Method: map[string]string{ "Recv": "func() (reflect.Value, bool)", "TryRecv": "func() (reflect.Value, bool)", }, Embed: []string{"reflect.Value"}, }, - "reflect.ComplexType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.ComplexValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.FloatType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.FloatValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.FuncType": &Type{ + "reflect.ComplexType": {Embed: []string{"reflect.Type"}}, + "reflect.ComplexValue": {Embed: []string{"reflect.Value"}}, + "reflect.FloatType": {Embed: []string{"reflect.Type"}}, + "reflect.FloatValue": {Embed: []string{"reflect.Value"}}, + "reflect.FuncType": { Method: map[string]string{ "In": "func(int) reflect.Type", "Out": "func(int) reflect.Type", }, Embed: []string{"reflect.Type"}, }, - "reflect.FuncValue": &Type{ + "reflect.FuncValue": { Method: map[string]string{ "Call": "func([]reflect.Value) []reflect.Value", }, }, - "reflect.IntType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.IntValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.InterfaceType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.InterfaceValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.MapType": &Type{ + "reflect.IntType": {Embed: []string{"reflect.Type"}}, + "reflect.IntValue": {Embed: []string{"reflect.Value"}}, + "reflect.InterfaceType": {Embed: []string{"reflect.Type"}}, + "reflect.InterfaceValue": {Embed: []string{"reflect.Value"}}, + "reflect.MapType": { Method: map[string]string{ "Key": "func() reflect.Type", }, Embed: []string{"reflect.Type"}, }, - "reflect.MapValue": &Type{ + "reflect.MapValue": { Method: map[string]string{ "Keys": "func() []reflect.Value", }, Embed: []string{"reflect.Value"}, }, - "reflect.Method": &Type{ + "reflect.Method": { Field: map[string]string{ "Type": "*reflect.FuncType", "Func": "*reflect.FuncValue", }, }, - "reflect.PtrType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.PtrValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.SliceType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.SliceValue": &Type{ + "reflect.PtrType": {Embed: []string{"reflect.Type"}}, + "reflect.PtrValue": {Embed: []string{"reflect.Value"}}, + "reflect.SliceType": {Embed: []string{"reflect.Type"}}, + "reflect.SliceValue": { Method: map[string]string{ "Slice": "func(int, int) *reflect.SliceValue", }, Embed: []string{"reflect.Value"}, }, - "reflect.StringType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.StringValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.StructField": &Type{ + "reflect.StringType": {Embed: []string{"reflect.Type"}}, + "reflect.StringValue": {Embed: []string{"reflect.Value"}}, + "reflect.StructField": { Field: map[string]string{ "Type": "reflect.Type", }, }, - "reflect.StructType": &Type{ + "reflect.StructType": { Method: map[string]string{ "Field": "func() reflect.StructField", "FieldByIndex": "func() reflect.StructField", @@ -693,7 +694,7 @@ var reflectTypeConfig = &TypeConfig{ }, Embed: []string{"reflect.Type"}, }, - "reflect.StructValue": &Type{ + "reflect.StructValue": { Method: map[string]string{ "Field": "func() reflect.Value", "FieldByIndex": "func() reflect.Value", @@ -702,17 +703,17 @@ var reflectTypeConfig = &TypeConfig{ }, Embed: []string{"reflect.Value"}, }, - "reflect.Type": &Type{ + "reflect.Type": { Method: map[string]string{ "Elem": "func() reflect.Type", "Method": "func() reflect.Method", }, }, - "reflect.UintType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.UintValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.UnsafePointerType": &Type{Embed: []string{"reflect.Type"}}, - "reflect.UnsafePointerValue": &Type{Embed: []string{"reflect.Value"}}, - "reflect.Value": &Type{ + "reflect.UintType": {Embed: []string{"reflect.Type"}}, + "reflect.UintValue": {Embed: []string{"reflect.Value"}}, + "reflect.UnsafePointerType": {Embed: []string{"reflect.Type"}}, + "reflect.UnsafePointerValue": {Embed: []string{"reflect.Value"}}, + "reflect.Value": { Method: map[string]string{ "Addr": "func() *reflect.PtrValue", "Elem": "func() reflect.Value", diff --git a/src/cmd/gofix/reflect_test.go b/src/cmd/gofix/reflect_test.go index 00edf30e9..032cbc745 100644 --- a/src/cmd/gofix/reflect_test.go +++ b/src/cmd/gofix/reflect_test.go @@ -1,3 +1,7 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file + package main import ( @@ -7,7 +11,7 @@ import ( ) func init() { - addTestCases(reflectTests()) + addTestCases(reflectTests(), reflectFn) } func reflectTests() []testCase { diff --git a/src/cmd/gofix/signal.go b/src/cmd/gofix/signal.go index 53c338851..5a583d41e 100644 --- a/src/cmd/gofix/signal.go +++ b/src/cmd/gofix/signal.go @@ -10,14 +10,17 @@ import ( ) func init() { - register(fix{ - "signal", - signal, - `Adapt code to types moved from os/signal to signal. + register(signalFix) +} + +var signalFix = fix{ + "signal", + "2011-06-29", + signal, + `Adapt code to types moved from os/signal to signal. http://codereview.appspot.com/4437091 `, - }) } func signal(f *ast.File) (fixed bool) { @@ -34,16 +37,14 @@ func signal(f *ast.File) (fixed bool) { sel := s.Sel.String() if sel == "Signal" || sel == "UnixSignal" || strings.HasPrefix(sel, "SIG") { + addImport(f, "os") s.X = &ast.Ident{Name: "os"} fixed = true } }) - if fixed { - addImport(f, "os") - if !usesImport(f, "os/signal") { - deleteImport(f, "os/signal") - } + if fixed && !usesImport(f, "os/signal") { + deleteImport(f, "os/signal") } return } diff --git a/src/cmd/gofix/signal_test.go b/src/cmd/gofix/signal_test.go index 4abba3534..7bca7d5c4 100644 --- a/src/cmd/gofix/signal_test.go +++ b/src/cmd/gofix/signal_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(signalTests) + addTestCases(signalTests, signal) } var signalTests = []testCase{ diff --git a/src/cmd/gofix/sorthelpers.go b/src/cmd/gofix/sorthelpers.go index 4e89fa88f..fa549313e 100644 --- a/src/cmd/gofix/sorthelpers.go +++ b/src/cmd/gofix/sorthelpers.go @@ -9,12 +9,15 @@ import ( ) func init() { - register(fix{ - "sorthelpers", - sorthelpers, - `Adapt code from sort.Sort[Ints|Float64s|Strings] to sort.[Ints|Float64s|Strings]. + register(sorthelpersFix) +} + +var sorthelpersFix = fix{ + "sorthelpers", + "2011-07-08", + sorthelpers, + `Adapt code from sort.Sort[Ints|Float64s|Strings] to sort.[Ints|Float64s|Strings]. `, - }) } func sorthelpers(f *ast.File) (fixed bool) { diff --git a/src/cmd/gofix/sorthelpers_test.go b/src/cmd/gofix/sorthelpers_test.go index 6c37858fd..dd6b58e03 100644 --- a/src/cmd/gofix/sorthelpers_test.go +++ b/src/cmd/gofix/sorthelpers_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(sorthelpersTests) + addTestCases(sorthelpersTests, sorthelpers) } var sorthelpersTests = []testCase{ diff --git a/src/cmd/gofix/sortslice.go b/src/cmd/gofix/sortslice.go index 7cfa1696b..89267b847 100644 --- a/src/cmd/gofix/sortslice.go +++ b/src/cmd/gofix/sortslice.go @@ -9,15 +9,18 @@ import ( ) func init() { - register(fix{ - "sortslice", - sortslice, - `Adapt code from sort.[Float64|Int|String]Array to sort.[Float64|Int|String]Slice. + register(sortsliceFix) +} + +var sortsliceFix = fix{ + "sortslice", + "2011-06-26", + sortslice, + `Adapt code from sort.[Float64|Int|String]Array to sort.[Float64|Int|String]Slice. http://codereview.appspot.com/4602054 http://codereview.appspot.com/4639041 `, - }) } func sortslice(f *ast.File) (fixed bool) { diff --git a/src/cmd/gofix/sortslice_test.go b/src/cmd/gofix/sortslice_test.go index 404feb26f..7b745a232 100644 --- a/src/cmd/gofix/sortslice_test.go +++ b/src/cmd/gofix/sortslice_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(sortsliceTests) + addTestCases(sortsliceTests, sortslice) } var sortsliceTests = []testCase{ diff --git a/src/cmd/gofix/strconv.go b/src/cmd/gofix/strconv.go new file mode 100644 index 000000000..6cd69020b --- /dev/null +++ b/src/cmd/gofix/strconv.go @@ -0,0 +1,127 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import "go/ast" + +func init() { + register(strconvFix) +} + +var strconvFix = fix{ + "strconv", + "2011-12-01", + strconvFn, + `Convert to new strconv API. + +http://codereview.appspot.com/5434095 +http://codereview.appspot.com/5434069 +`, +} + +func strconvFn(f *ast.File) bool { + if !imports(f, "strconv") { + return false + } + + fixed := false + + walk(f, func(n interface{}) { + // Rename functions. + call, ok := n.(*ast.CallExpr) + if !ok || len(call.Args) < 1 { + return + } + sel, ok := call.Fun.(*ast.SelectorExpr) + if !ok || !isTopName(sel.X, "strconv") { + return + } + change := func(name string) { + fixed = true + sel.Sel.Name = name + } + add := func(s string) { + call.Args = append(call.Args, expr(s)) + } + switch sel.Sel.Name { + case "Atob": + change("ParseBool") + case "Atof32": + change("ParseFloat") + add("32") // bitSize + warn(call.Pos(), "rewrote strconv.Atof32(_) to strconv.ParseFloat(_, 32) but return value must be converted to float32") + case "Atof64": + change("ParseFloat") + add("64") // bitSize + case "AtofN": + change("ParseFloat") + case "Atoi": + // Atoi stayed as a convenience wrapper. + case "Atoi64": + change("ParseInt") + add("10") // base + add("64") // bitSize + case "Atoui": + change("ParseUint") + add("10") // base + add("0") // bitSize + warn(call.Pos(), "rewrote strconv.Atoui(_) to strconv.ParseUint(_, 10, 0) but return value must be converted to uint") + case "Atoui64": + change("ParseUint") + add("10") // base + add("64") // bitSize + case "Btoa": + change("FormatBool") + case "Btoi64": + change("ParseInt") + add("64") // bitSize + case "Btoui64": + change("ParseUint") + add("64") // bitSize + case "Ftoa32": + change("FormatFloat") + call.Args[0] = strconvRewrite("float32", "float64", call.Args[0]) + add("32") // bitSize + case "Ftoa64": + change("FormatFloat") + add("64") // bitSize + case "FtoaN": + change("FormatFloat") + case "Itoa": + // Itoa stayed as a convenience wrapper. + case "Itoa64": + change("FormatInt") + add("10") // base + case "Itob": + change("FormatInt") + call.Args[0] = strconvRewrite("int", "int64", call.Args[0]) + case "Itob64": + change("FormatInt") + case "Uitoa": + change("FormatUint") + call.Args[0] = strconvRewrite("uint", "uint64", call.Args[0]) + add("10") // base + case "Uitoa64": + change("FormatUint") + add("10") // base + case "Uitob": + change("FormatUint") + call.Args[0] = strconvRewrite("uint", "uint64", call.Args[0]) + case "Uitob64": + change("FormatUint") + } + }) + return fixed +} + +// rewrite from type t1 to type t2 +// If the expression x is of the form t1(_), use t2(_). Otherwise use t2(x). +func strconvRewrite(t1, t2 string, x ast.Expr) ast.Expr { + if call, ok := x.(*ast.CallExpr); ok && isTopName(call.Fun, t1) { + call.Fun.(*ast.Ident).Name = t2 + return x + } + return &ast.CallExpr{Fun: ast.NewIdent(t2), Args: []ast.Expr{x}} +} diff --git a/src/cmd/gofix/strconv_test.go b/src/cmd/gofix/strconv_test.go new file mode 100644 index 000000000..7fbd4e42e --- /dev/null +++ b/src/cmd/gofix/strconv_test.go @@ -0,0 +1,93 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(strconvTests, strconvFn) +} + +var strconvTests = []testCase{ + { + Name: "strconv.0", + In: `package main + +import "strconv" + +func f() { + foo.Atob("abc") + + strconv.Atob("true") + strconv.Btoa(false) + + strconv.Atof32("1.2") + strconv.Atof64("1.2") + strconv.AtofN("1.2", 64) + strconv.Ftoa32(1.2, 'g', 17) + strconv.Ftoa64(1.2, 'g', 17) + strconv.FtoaN(1.2, 'g', 17, 64) + + strconv.Atoi("3") + strconv.Atoi64("3") + strconv.Btoi64("1234", 5) + + strconv.Atoui("3") + strconv.Atoui64("3") + strconv.Btoui64("1234", 5) + + strconv.Itoa(123) + strconv.Itoa64(1234) + strconv.Itob(123, 5) + strconv.Itob64(1234, 5) + + strconv.Uitoa(123) + strconv.Uitoa64(1234) + strconv.Uitob(123, 5) + strconv.Uitob64(1234, 5) + + strconv.Uitoa(uint(x)) + strconv.Uitoa(f(x)) +} +`, + Out: `package main + +import "strconv" + +func f() { + foo.Atob("abc") + + strconv.ParseBool("true") + strconv.FormatBool(false) + + strconv.ParseFloat("1.2", 32) + strconv.ParseFloat("1.2", 64) + strconv.ParseFloat("1.2", 64) + strconv.FormatFloat(float64(1.2), 'g', 17, 32) + strconv.FormatFloat(1.2, 'g', 17, 64) + strconv.FormatFloat(1.2, 'g', 17, 64) + + strconv.Atoi("3") + strconv.ParseInt("3", 10, 64) + strconv.ParseInt("1234", 5, 64) + + strconv.ParseUint("3", 10, 0) + strconv.ParseUint("3", 10, 64) + strconv.ParseUint("1234", 5, 64) + + strconv.Itoa(123) + strconv.FormatInt(1234, 10) + strconv.FormatInt(int64(123), 5) + strconv.FormatInt(1234, 5) + + strconv.FormatUint(uint64(123), 10) + strconv.FormatUint(1234, 10) + strconv.FormatUint(uint64(123), 5) + strconv.FormatUint(1234, 5) + + strconv.FormatUint(uint64(x), 10) + strconv.FormatUint(uint64(f(x)), 10) +} +`, + }, +} diff --git a/src/cmd/gofix/stringssplit.go b/src/cmd/gofix/stringssplit.go index 4a1fe93d3..d89ecf039 100644 --- a/src/cmd/gofix/stringssplit.go +++ b/src/cmd/gofix/stringssplit.go @@ -9,8 +9,13 @@ import ( "go/token" ) +func init() { + register(stringssplitFix) +} + var stringssplitFix = fix{ "stringssplit", + "2011-06-28", stringssplit, `Restore strings.Split to its original meaning and add strings.SplitN. Bytes too. @@ -18,10 +23,6 @@ http://codereview.appspot.com/4661051 `, } -func init() { - register(stringssplitFix) -} - func stringssplit(f *ast.File) bool { if !imports(f, "bytes") && !imports(f, "strings") { return false diff --git a/src/cmd/gofix/stringssplit_test.go b/src/cmd/gofix/stringssplit_test.go index b925722af..fa42b1bea 100644 --- a/src/cmd/gofix/stringssplit_test.go +++ b/src/cmd/gofix/stringssplit_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(stringssplitTests) + addTestCases(stringssplitTests, stringssplit) } var stringssplitTests = []testCase{ diff --git a/src/cmd/gofix/template.go b/src/cmd/gofix/template.go new file mode 100644 index 000000000..a3dd1440b --- /dev/null +++ b/src/cmd/gofix/template.go @@ -0,0 +1,111 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(templateFix) +} + +var templateFix = fix{ + "template", + "2011-11-22", + template, + `Rewrite calls to template.ParseFile to template.ParseFiles + +http://codereview.appspot.com/5433048 +`, +} + +var templateSetGlobals = []string{ + "ParseSetFiles", + "ParseSetGlob", + "ParseTemplateFiles", + "ParseTemplateGlob", + "Set", + "SetMust", +} + +var templateSetMethods = []string{ + "ParseSetFiles", + "ParseSetGlob", + "ParseTemplateFiles", + "ParseTemplateGlob", +} + +var templateTypeConfig = &TypeConfig{ + Type: map[string]*Type{ + "template.Template": { + Method: map[string]string{ + "Funcs": "func() *template.Template", + "Delims": "func() *template.Template", + "Parse": "func() (*template.Template, error)", + "ParseFile": "func() (*template.Template, error)", + "ParseInSet": "func() (*template.Template, error)", + }, + }, + "template.Set": { + Method: map[string]string{ + "ParseSetFiles": "func() (*template.Set, error)", + "ParseSetGlob": "func() (*template.Set, error)", + "ParseTemplateFiles": "func() (*template.Set, error)", + "ParseTemplateGlob": "func() (*template.Set, error)", + }, + }, + }, + + Func: map[string]string{ + "template.New": "*template.Template", + "template.Must": "(*template.Template, error)", + "template.SetMust": "(*template.Set, error)", + }, +} + +func template(f *ast.File) bool { + if !imports(f, "text/template") && !imports(f, "html/template") { + return false + } + + fixed := false + + typeof, _ := typecheck(templateTypeConfig, f) + + // Now update the names used by importers. + walk(f, func(n interface{}) { + if sel, ok := n.(*ast.SelectorExpr); ok { + // Reference to top-level function ParseFile. + if isPkgDot(sel, "template", "ParseFile") { + sel.Sel.Name = "ParseFiles" + fixed = true + return + } + // Reference to ParseFiles method. + if typeof[sel.X] == "*template.Template" && sel.Sel.Name == "ParseFile" { + sel.Sel.Name = "ParseFiles" + fixed = true + return + } + // The Set type and its functions are now gone. + for _, name := range templateSetGlobals { + if isPkgDot(sel, "template", name) { + warn(sel.Pos(), "reference to template.%s must be fixed manually", name) + return + } + } + // The methods of Set are now gone. + for _, name := range templateSetMethods { + if typeof[sel.X] == "*template.Set" && sel.Sel.Name == name { + warn(sel.Pos(), "reference to template.*Set.%s must be fixed manually", name) + return + } + } + } + }) + + return fixed +} diff --git a/src/cmd/gofix/template_test.go b/src/cmd/gofix/template_test.go new file mode 100644 index 000000000..f713a2901 --- /dev/null +++ b/src/cmd/gofix/template_test.go @@ -0,0 +1,55 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(templateTests, template) +} + +var templateTests = []testCase{ + { + Name: "template.0", + In: `package main + +import ( + "text/template" +) + +func f() { + template.ParseFile(a) + var t template.Template + x, y := template.ParseFile() + template.New("x").Funcs(m).ParseFile(a) // chained method + // Output should complain about these as functions or methods. + var s *template.Set + s.ParseSetFiles(a) + template.ParseSetGlob(a) + s.ParseTemplateFiles(a) + template.ParseTemplateGlob(a) + x := template.SetMust(a()) +} +`, + Out: `package main + +import ( + "text/template" +) + +func f() { + template.ParseFiles(a) + var t template.Template + x, y := template.ParseFiles() + template.New("x").Funcs(m).ParseFiles(a) // chained method + // Output should complain about these as functions or methods. + var s *template.Set + s.ParseSetFiles(a) + template.ParseSetGlob(a) + s.ParseTemplateFiles(a) + template.ParseTemplateGlob(a) + x := template.SetMust(a()) +} +`, + }, +} diff --git a/src/cmd/gofix/testdata/reflect.decoder.go.in b/src/cmd/gofix/testdata/reflect.decoder.go.in index 34364161a..0ce9b06fd 100644 --- a/src/cmd/gofix/testdata/reflect.decoder.go.in +++ b/src/cmd/gofix/testdata/reflect.decoder.go.in @@ -44,7 +44,7 @@ func NewDecoder(r io.Reader) *Decoder { func (dec *Decoder) recvType(id typeId) { // Have we already seen this type? That's an error if id < firstUserId || dec.wireType[id] != nil { - dec.err = os.ErrorString("gob: duplicate type received") + dec.err = os.NewError("gob: duplicate type received") return } @@ -143,7 +143,7 @@ func (dec *Decoder) decodeTypeSequence(isInterface bool) typeId { // will be absorbed by recvMessage.) if dec.buf.Len() > 0 { if !isInterface { - dec.err = os.ErrorString("extra data in buffer") + dec.err = os.NewError("extra data in buffer") break } dec.nextUint() @@ -165,7 +165,7 @@ func (dec *Decoder) Decode(e interface{}) os.Error { // If e represents a value as opposed to a pointer, the answer won't // get back to the caller. Make sure it's a pointer. if value.Type().Kind() != reflect.Ptr { - dec.err = os.ErrorString("gob: attempt to decode into a non-pointer") + dec.err = os.NewError("gob: attempt to decode into a non-pointer") return dec.err } return dec.DecodeValue(value) diff --git a/src/cmd/gofix/testdata/reflect.encoder.go.in b/src/cmd/gofix/testdata/reflect.encoder.go.in index e52a4de29..0202d79ac 100644 --- a/src/cmd/gofix/testdata/reflect.encoder.go.in +++ b/src/cmd/gofix/testdata/reflect.encoder.go.in @@ -50,7 +50,7 @@ func (enc *Encoder) popWriter() { } func (enc *Encoder) badType(rt reflect.Type) { - enc.setError(os.ErrorString("gob: can't encode type " + rt.String())) + enc.setError(os.NewError("gob: can't encode type " + rt.String())) } func (enc *Encoder) setError(err os.Error) { diff --git a/src/cmd/gofix/testdata/reflect.export.go.in b/src/cmd/gofix/testdata/reflect.export.go.in index e91e777e3..ce7940b29 100644 --- a/src/cmd/gofix/testdata/reflect.export.go.in +++ b/src/cmd/gofix/testdata/reflect.export.go.in @@ -22,8 +22,8 @@ package netchan // BUG: can't use range clause to receive when using ImportNValues to limit the count. import ( - "log" "io" + "log" "net" "os" "reflect" @@ -343,20 +343,20 @@ func (exp *Exporter) Sync(timeout int64) os.Error { func checkChan(chT interface{}, dir Dir) (*reflect.ChanValue, os.Error) { chanType, ok := reflect.Typeof(chT).(*reflect.ChanType) if !ok { - return nil, os.ErrorString("not a channel") + return nil, os.NewError("not a channel") } if dir != Send && dir != Recv { - return nil, os.ErrorString("unknown channel direction") + return nil, os.NewError("unknown channel direction") } switch chanType.Dir() { case reflect.BothDir: case reflect.SendDir: if dir != Recv { - return nil, os.ErrorString("to import/export with Send, must provide <-chan") + return nil, os.NewError("to import/export with Send, must provide <-chan") } case reflect.RecvDir: if dir != Send { - return nil, os.ErrorString("to import/export with Recv, must provide chan<-") + return nil, os.NewError("to import/export with Recv, must provide chan<-") } } return reflect.NewValue(chT).(*reflect.ChanValue), nil @@ -376,7 +376,7 @@ func (exp *Exporter) Export(name string, chT interface{}, dir Dir) os.Error { defer exp.mu.Unlock() _, present := exp.names[name] if present { - return os.ErrorString("channel name already being exported:" + name) + return os.NewError("channel name already being exported:" + name) } exp.names[name] = &chanDir{ch, dir} return nil @@ -393,7 +393,7 @@ func (exp *Exporter) Hangup(name string) os.Error { // TODO drop all instances of channel from client sets exp.mu.Unlock() if !ok { - return os.ErrorString("netchan export: hangup: no such channel: " + name) + return os.NewError("netchan export: hangup: no such channel: " + name) } chDir.ch.Close() return nil diff --git a/src/cmd/gofix/testdata/reflect.export.go.out b/src/cmd/gofix/testdata/reflect.export.go.out index 460edb40b..7bd73c5e7 100644 --- a/src/cmd/gofix/testdata/reflect.export.go.out +++ b/src/cmd/gofix/testdata/reflect.export.go.out @@ -22,8 +22,8 @@ package netchan // BUG: can't use range clause to receive when using ImportNValues to limit the count. import ( - "log" "io" + "log" "net" "os" "reflect" diff --git a/src/cmd/gofix/testdata/reflect.print.go.in b/src/cmd/gofix/testdata/reflect.print.go.in index cba1df296..6c9b8e4f9 100644 --- a/src/cmd/gofix/testdata/reflect.print.go.in +++ b/src/cmd/gofix/testdata/reflect.print.go.in @@ -185,7 +185,7 @@ func Sprintf(format string, a ...interface{}) string { // Errorf formats according to a format specifier and returns the string // converted to an os.ErrorString, which satisfies the os.Error interface. func Errorf(format string, a ...interface{}) os.Error { - return os.ErrorString(Sprintf(format, a...)) + return os.NewError(Sprintf(format, a...)) } // These routines do not take a format string diff --git a/src/cmd/gofix/testdata/reflect.read.go.in b/src/cmd/gofix/testdata/reflect.read.go.in index 9ae3bb8ee..487994ac6 100644 --- a/src/cmd/gofix/testdata/reflect.read.go.in +++ b/src/cmd/gofix/testdata/reflect.read.go.in @@ -244,7 +244,7 @@ func (p *Parser) unmarshal(val reflect.Value, start *StartElement) os.Error { switch v := val.(type) { default: - return os.ErrorString("unknown type " + v.Type().String()) + return os.NewError("unknown type " + v.Type().String()) case *reflect.SliceValue: typ := v.Type().(*reflect.SliceType) @@ -483,7 +483,7 @@ Loop: case nil: // Probably a comment, handled below default: - return os.ErrorString("cannot happen: unknown type " + t.Type().String()) + return os.NewError("cannot happen: unknown type " + t.Type().String()) case *reflect.IntValue: if !getInt64() { return err diff --git a/src/cmd/gofix/testdata/reflect.scan.go.in b/src/cmd/gofix/testdata/reflect.scan.go.in index 83650e605..51898181f 100644 --- a/src/cmd/gofix/testdata/reflect.scan.go.in +++ b/src/cmd/gofix/testdata/reflect.scan.go.in @@ -167,7 +167,7 @@ type ssave struct { // satisfies io.Reader. It will never be called when used as // intended, so there is no need to make it actually work. func (s *ss) Read(buf []byte) (n int, err os.Error) { - return 0, os.ErrorString("ScanState's Read should not be called. Use ReadRune") + return 0, os.NewError("ScanState's Read should not be called. Use ReadRune") } func (s *ss) ReadRune() (rune int, size int, err os.Error) { @@ -240,7 +240,7 @@ func (s *ss) error(err os.Error) { } func (s *ss) errorString(err string) { - panic(scanError{os.ErrorString(err)}) + panic(scanError{os.NewError(err)}) } func (s *ss) Token(skipSpace bool, f func(int) bool) (tok []byte, err os.Error) { @@ -424,8 +424,8 @@ func (s *ss) typeError(field interface{}, expected string) { s.errorString("expected field of type pointer to " + expected + "; found " + reflect.Typeof(field).String()) } -var complexError = os.ErrorString("syntax error scanning complex number") -var boolError = os.ErrorString("syntax error scanning boolean") +var complexError = os.NewError("syntax error scanning complex number") +var boolError = os.NewError("syntax error scanning boolean") // consume reads the next rune in the input and reports whether it is in the ok string. // If accept is true, it puts the character into the input token. diff --git a/src/cmd/gofix/testdata/reflect.type.go.in b/src/cmd/gofix/testdata/reflect.type.go.in index 305d41980..34963bef9 100644 --- a/src/cmd/gofix/testdata/reflect.type.go.in +++ b/src/cmd/gofix/testdata/reflect.type.go.in @@ -67,7 +67,7 @@ func validUserType(rt reflect.Type) (ut *userTypeInfo, err os.Error) { ut.base = pt.Elem() if ut.base == slowpoke { // ut.base lapped slowpoke // recursive pointer type. - return nil, os.ErrorString("can't represent recursive pointer type " + ut.base.String()) + return nil, os.NewError("can't represent recursive pointer type " + ut.base.String()) } if ut.indir%2 == 0 { slowpoke = slowpoke.(*reflect.PtrType).Elem() @@ -150,6 +150,7 @@ func userType(rt reflect.Type) *userTypeInfo { } return ut } + // A typeId represents a gob Type as an integer that can be passed on the wire. // Internally, typeIds are used as keys to a map to recover the underlying type info. type typeId int32 @@ -524,7 +525,7 @@ func newTypeObject(name string, ut *userTypeInfo, rt reflect.Type) (gobType, os. return st, nil default: - return nil, os.ErrorString("gob NewTypeObject can't handle type: " + rt.String()) + return nil, os.NewError("gob NewTypeObject can't handle type: " + rt.String()) } return nil, nil } diff --git a/src/cmd/gofix/testdata/reflect.type.go.out b/src/cmd/gofix/testdata/reflect.type.go.out index 9cd78296d..d729ea471 100644 --- a/src/cmd/gofix/testdata/reflect.type.go.out +++ b/src/cmd/gofix/testdata/reflect.type.go.out @@ -150,6 +150,7 @@ func userType(rt reflect.Type) *userTypeInfo { } return ut } + // A typeId represents a gob Type as an integer that can be passed on the wire. // Internally, typeIds are used as keys to a map to recover the underlying type info. type typeId int32 diff --git a/src/cmd/gofix/timefileinfo.go b/src/cmd/gofix/timefileinfo.go new file mode 100644 index 000000000..b2ea23d8f --- /dev/null +++ b/src/cmd/gofix/timefileinfo.go @@ -0,0 +1,298 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" + "go/token" + "strings" +) + +func init() { + register(timefileinfoFix) +} + +var timefileinfoFix = fix{ + "time+fileinfo", + "2011-11-29", + timefileinfo, + `Rewrite for new time and os.FileInfo APIs. + +This fix applies some of the more mechanical changes, +but most code will still need manual cleanup. + +http://codereview.appspot.com/5392041 +http://codereview.appspot.com/5416060 +`, +} + +var timefileinfoTypeConfig = &TypeConfig{ + Type: map[string]*Type{ + "os.File": { + Method: map[string]string{ + "Readdir": "func() []*os.FileInfo", + "Stat": "func() (*os.FileInfo, error)", + }, + }, + "time.Time": { + Method: map[string]string{ + "Seconds": "time.raw", + "Nanoseconds": "time.raw", + }, + }, + }, + Func: map[string]string{ + "ioutil.ReadDir": "([]*os.FileInfo, error)", + "os.Stat": "(*os.FileInfo, error)", + "os.Lstat": "(*os.FileInfo, error)", + "time.LocalTime": "*time.Time", + "time.UTC": "*time.Time", + "time.SecondsToLocalTime": "*time.Time", + "time.SecondsToUTC": "*time.Time", + "time.NanosecondsToLocalTime": "*time.Time", + "time.NanosecondsToUTC": "*time.Time", + "time.Parse": "(*time.Time, error)", + "time.Nanoseconds": "time.raw", + "time.Seconds": "time.raw", + }, +} + +// timefileinfoIsOld reports whether f has evidence of being +// "old code", from before the API changes. Evidence means: +// +// a mention of *os.FileInfo (the pointer) +// a mention of *time.Time (the pointer) +// a mention of old functions from package time +// an attempt to call time.UTC +// +func timefileinfoIsOld(f *ast.File, typeof map[interface{}]string) bool { + old := false + + // called records the expressions that appear as + // the function part of a function call, so that + // we can distinguish a ref to the possibly new time.UTC + // from the definitely old time.UTC() function call. + called := make(map[interface{}]bool) + + before := func(n interface{}) { + if old { + return + } + if star, ok := n.(*ast.StarExpr); ok { + if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") { + old = true + return + } + } + if sel, ok := n.(*ast.SelectorExpr); ok { + if isTopName(sel.X, "time") { + if timefileinfoOldTimeFunc[sel.Sel.Name] { + old = true + return + } + } + if typeof[sel.X] == "os.FileInfo" || typeof[sel.X] == "*os.FileInfo" { + switch sel.Sel.Name { + case "Mtime_ns", "IsDirectory", "IsRegular": + old = true + return + case "Name", "Mode", "Size": + if !called[sel] { + old = true + return + } + } + } + } + call, ok := n.(*ast.CallExpr) + if ok && isPkgDot(call.Fun, "time", "UTC") { + old = true + return + } + if ok { + called[call.Fun] = true + } + } + walkBeforeAfter(f, before, nop) + return old +} + +var timefileinfoOldTimeFunc = map[string]bool{ + "LocalTime": true, + "SecondsToLocalTime": true, + "SecondsToUTC": true, + "NanosecondsToLocalTime": true, + "NanosecondsToUTC": true, + "Seconds": true, + "Nanoseconds": true, +} + +var isTimeNow = map[string]bool{ + "LocalTime": true, + "UTC": true, + "Seconds": true, + "Nanoseconds": true, +} + +func timefileinfo(f *ast.File) bool { + if !imports(f, "os") && !imports(f, "time") && !imports(f, "io/ioutil") { + return false + } + + typeof, _ := typecheck(timefileinfoTypeConfig, f) + + if !timefileinfoIsOld(f, typeof) { + return false + } + + fixed := false + walk(f, func(n interface{}) { + p, ok := n.(*ast.Expr) + if !ok { + return + } + nn := *p + + // Rewrite *os.FileInfo and *time.Time to drop the pointer. + if star, ok := nn.(*ast.StarExpr); ok { + if isPkgDot(star.X, "os", "FileInfo") || isPkgDot(star.X, "time", "Time") { + fixed = true + *p = star.X + return + } + } + + // Rewrite old time API calls to new calls. + // The code will still not compile after this edit, + // but the compiler will catch that, and the replacement + // code will be the correct functions to use in the new API. + if sel, ok := nn.(*ast.SelectorExpr); ok && isTopName(sel.X, "time") { + fn := sel.Sel.Name + if fn == "LocalTime" || fn == "Seconds" || fn == "Nanoseconds" { + fixed = true + sel.Sel.Name = "Now" + return + } + } + + if call, ok := nn.(*ast.CallExpr); ok { + if sel, ok := call.Fun.(*ast.SelectorExpr); ok { + // Rewrite time.UTC but only when called (there's a new time.UTC var now). + if isPkgDot(sel, "time", "UTC") { + fixed = true + sel.Sel.Name = "Now" + // rewrite time.Now() into time.Now().UTC() + *p = &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: call, + Sel: ast.NewIdent("UTC"), + }, + } + return + } + + // Rewrite conversions. + if ok && isTopName(sel.X, "time") && len(call.Args) == 1 { + fn := sel.Sel.Name + switch fn { + case "SecondsToLocalTime", "SecondsToUTC", + "NanosecondsToLocalTime", "NanosecondsToUTC": + fixed = true + sel.Sel.Name = "Unix" + call.Args = append(call.Args, nil) + if strings.HasPrefix(fn, "Seconds") { + // Unix(sec, 0) + call.Args[1] = ast.NewIdent("0") + } else { + // Unix(0, nsec) + call.Args[1] = call.Args[0] + call.Args[0] = ast.NewIdent("0") + } + if strings.HasSuffix(fn, "ToUTC") { + // rewrite call into call.UTC() + *p = &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: call, + Sel: ast.NewIdent("UTC"), + }, + } + } + return + } + } + + // Rewrite method calls. + switch typeof[sel.X] { + case "*time.Time", "time.Time": + switch sel.Sel.Name { + case "Seconds": + fixed = true + sel.Sel.Name = "Unix" + return + case "Nanoseconds": + fixed = true + sel.Sel.Name = "UnixNano" + return + } + + case "*os.FileInfo", "os.FileInfo": + switch sel.Sel.Name { + case "IsDirectory": + fixed = true + sel.Sel.Name = "IsDir" + return + case "IsRegular": + fixed = true + sel.Sel.Name = "IsDir" + *p = &ast.UnaryExpr{ + Op: token.NOT, + X: call, + } + return + } + } + } + } + + // Rewrite subtraction of two times. + // Cannot handle +=/-=. + if bin, ok := nn.(*ast.BinaryExpr); ok && + bin.Op == token.SUB && + (typeof[bin.X] == "time.raw" || typeof[bin.Y] == "time.raw") { + fixed = true + *p = &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: bin.X, + Sel: ast.NewIdent("Sub"), + }, + Args: []ast.Expr{bin.Y}, + } + } + + // Rewrite field references for os.FileInfo. + if sel, ok := nn.(*ast.SelectorExpr); ok { + if typ := typeof[sel.X]; typ == "*os.FileInfo" || typ == "os.FileInfo" { + addCall := false + switch sel.Sel.Name { + case "Name", "Size", "Mode": + fixed = true + addCall = true + case "Mtime_ns": + fixed = true + sel.Sel.Name = "ModTime" + addCall = true + } + if addCall { + *p = &ast.CallExpr{ + Fun: sel, + } + return + } + } + } + }) + + return true +} diff --git a/src/cmd/gofix/timefileinfo_test.go b/src/cmd/gofix/timefileinfo_test.go new file mode 100644 index 000000000..76d5c1f7f --- /dev/null +++ b/src/cmd/gofix/timefileinfo_test.go @@ -0,0 +1,161 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(timefileinfoTests, timefileinfo) +} + +var timefileinfoTests = []testCase{ + { + Name: "timefileinfo.0", + In: `package main + +import "os" + +func main() { + st, _ := os.Stat("/etc/passwd") + _ = st.Name +} +`, + Out: `package main + +import "os" + +func main() { + st, _ := os.Stat("/etc/passwd") + _ = st.Name() +} +`, + }, + { + Name: "timefileinfo.1", + In: `package main + +import "os" + +func main() { + st, _ := os.Stat("/etc/passwd") + _ = st.Size + _ = st.Mode + _ = st.Mtime_ns + _ = st.IsDirectory() + _ = st.IsRegular() +} +`, + Out: `package main + +import "os" + +func main() { + st, _ := os.Stat("/etc/passwd") + _ = st.Size() + _ = st.Mode() + _ = st.ModTime() + _ = st.IsDir() + _ = !st.IsDir() +} +`, + }, + { + Name: "timefileinfo.2", + In: `package main + +import "os" + +func f(st *os.FileInfo) { + _ = st.Name + _ = st.Size + _ = st.Mode + _ = st.Mtime_ns + _ = st.IsDirectory() + _ = st.IsRegular() +} +`, + Out: `package main + +import "os" + +func f(st os.FileInfo) { + _ = st.Name() + _ = st.Size() + _ = st.Mode() + _ = st.ModTime() + _ = st.IsDir() + _ = !st.IsDir() +} +`, + }, + { + Name: "timefileinfo.3", + In: `package main + +import "time" + +func main() { + _ = time.Seconds() + _ = time.Nanoseconds() + _ = time.LocalTime() + _ = time.UTC() + _ = time.SecondsToLocalTime(sec) + _ = time.SecondsToUTC(sec) + _ = time.NanosecondsToLocalTime(nsec) + _ = time.NanosecondsToUTC(nsec) +} +`, + Out: `package main + +import "time" + +func main() { + _ = time.Now() + _ = time.Now() + _ = time.Now() + _ = time.Now().UTC() + _ = time.Unix(sec, 0) + _ = time.Unix(sec, 0).UTC() + _ = time.Unix(0, nsec) + _ = time.Unix(0, nsec).UTC() +} +`, + }, + { + Name: "timefileinfo.4", + In: `package main + +import "time" + +func f(*time.Time) + +func main() { + t := time.LocalTime() + _ = t.Seconds() + _ = t.Nanoseconds() + + t1 := time.Nanoseconds() + f(nil) + t2 := time.Nanoseconds() + dt := t2 - t1 +} +`, + Out: `package main + +import "time" + +func f(time.Time) + +func main() { + t := time.Now() + _ = t.Unix() + _ = t.UnixNano() + + t1 := time.Now() + f(nil) + t2 := time.Now() + dt := t2.Sub(t1) +} +`, + }, +} diff --git a/src/cmd/gofix/typecheck.go b/src/cmd/gofix/typecheck.go index 2d81b9710..8e54314d1 100644 --- a/src/cmd/gofix/typecheck.go +++ b/src/cmd/gofix/typecheck.go @@ -97,8 +97,9 @@ func (cfg *TypeConfig) typeof(name string) string { // looked for in the Embed list. type Type struct { Field map[string]string // map field name to type - Method map[string]string // map method name to comma-separated return types + Method map[string]string // map method name to comma-separated return types (should start with "func ") Embed []string // list of types this type embeds (for extra methods) + Def string // definition of named type } // dot returns the type of "typ.name", making its decision @@ -128,9 +129,16 @@ func (typ *Type) dot(cfg *TypeConfig, name string) string { } // typecheck type checks the AST f assuming the information in cfg. -// It returns a map from AST nodes to type information in gofmt string form. -func typecheck(cfg *TypeConfig, f *ast.File) map[interface{}]string { - typeof := make(map[interface{}]string) +// It returns two maps with type information: +// typeof maps AST nodes to type information in gofmt string form. +// assign maps type strings to lists of expressions that were assigned +// to values of another type that were assigned to that type. +func typecheck(cfg *TypeConfig, f *ast.File) (typeof map[interface{}]string, assign map[string][]interface{}) { + typeof = make(map[interface{}]string) + assign = make(map[string][]interface{}) + cfg1 := &TypeConfig{} + *cfg1 = *cfg // make copy so we can add locally + copied := false // gather function declarations for _, decl := range f.Decls { @@ -138,7 +146,7 @@ func typecheck(cfg *TypeConfig, f *ast.File) map[interface{}]string { if !ok { continue } - typecheck1(cfg, fn.Type, typeof) + typecheck1(cfg, fn.Type, typeof, assign) t := typeof[fn.Type] if fn.Recv != nil { // The receiver must be a type. @@ -168,8 +176,43 @@ func typecheck(cfg *TypeConfig, f *ast.File) map[interface{}]string { } } - typecheck1(cfg, f, typeof) - return typeof + // gather struct declarations + for _, decl := range f.Decls { + d, ok := decl.(*ast.GenDecl) + if ok { + for _, s := range d.Specs { + switch s := s.(type) { + case *ast.TypeSpec: + if cfg1.Type[s.Name.Name] != nil { + break + } + if !copied { + copied = true + // Copy map lazily: it's time. + cfg1.Type = make(map[string]*Type) + for k, v := range cfg.Type { + cfg1.Type[k] = v + } + } + t := &Type{Field: map[string]string{}} + cfg1.Type[s.Name.Name] = t + switch st := s.Type.(type) { + case *ast.StructType: + for _, f := range st.Fields.List { + for _, n := range f.Names { + t.Field[n.Name] = gofmt(f.Type) + } + } + case *ast.ArrayType, *ast.StarExpr, *ast.MapType: + t.Def = gofmt(st) + } + } + } + } + } + + typecheck1(cfg1, f, typeof, assign) + return typeof, assign } func makeExprList(a []*ast.Ident) []ast.Expr { @@ -183,11 +226,14 @@ func makeExprList(a []*ast.Ident) []ast.Expr { // Typecheck1 is the recursive form of typecheck. // It is like typecheck but adds to the information in typeof // instead of allocating a new map. -func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { +func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string, assign map[string][]interface{}) { // set sets the type of n to typ. // If isDecl is true, n is being declared. set := func(n ast.Expr, typ string, isDecl bool) { if typeof[n] != "" || typ == "" { + if typeof[n] != typ { + assign[typ] = append(assign[typ], n) + } return } typeof[n] = typ @@ -236,6 +282,14 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { } } + expand := func(s string) string { + typ := cfg.Type[s] + if typ != nil && typ.Def != "" { + return typ.Def + } + return s + } + // The main type check is a recursive algorithm implemented // by walkBeforeAfter(n, before, after). // Most of it is bottom-up, but in a few places we need @@ -263,7 +317,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { defer func() { if t := typeof[n]; t != "" { pos := fset.Position(n.(ast.Node).Pos()) - fmt.Fprintf(os.Stderr, "%s: typeof[%s] = %s\n", pos.String(), gofmt(n), t) + fmt.Fprintf(os.Stderr, "%s: typeof[%s] = %s\n", pos, gofmt(n), t) } }() } @@ -405,6 +459,8 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { // x.(T) has type T. if t := typeof[n.Type]; isType(t) { typeof[n] = getType(t) + } else { + typeof[n] = gofmt(n.Type) } case *ast.SliceExpr: @@ -413,7 +469,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { case *ast.IndexExpr: // x[i] has key type of x's type. - t := typeof[n.X] + t := expand(typeof[n.X]) if strings.HasPrefix(t, "[") || strings.HasPrefix(t, "map[") { // Lazy: assume there are no nested [] in the array // length or map key type. @@ -426,7 +482,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { // *x for x of type *T has type T when x is an expr. // We don't use the result when *x is a type, but // compute it anyway. - t := typeof[n.X] + t := expand(typeof[n.X]) if isType(t) { typeof[n] = "type *" + getType(t) } else if strings.HasPrefix(t, "*") { @@ -437,7 +493,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { // &x for x of type T has type *T. t := typeof[n.X] if t != "" && n.Op == token.AND { - typeof[n] = "&" + t + typeof[n] = "*" + t } case *ast.CompositeLit: @@ -448,6 +504,39 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { // (x) has type of x. typeof[n] = typeof[n.X] + case *ast.RangeStmt: + t := expand(typeof[n.X]) + if t == "" { + return + } + var key, value string + if t == "string" { + key, value = "int", "rune" + } else if strings.HasPrefix(t, "[") { + key = "int" + if i := strings.Index(t, "]"); i >= 0 { + value = t[i+1:] + } + } else if strings.HasPrefix(t, "map[") { + if i := strings.Index(t, "]"); i >= 0 { + key, value = t[4:i], t[i+1:] + } + } + changed := false + if n.Key != nil && key != "" { + changed = true + set(n.Key, key, n.Tok == token.DEFINE) + } + if n.Value != nil && value != "" { + changed = true + set(n.Value, value, n.Tok == token.DEFINE) + } + // Ugly failure of vision: already type-checked body. + // Do it again now that we have that type info. + if changed { + typecheck1(cfg, n.Body, typeof, assign) + } + case *ast.TypeSwitchStmt: // Type of variable changes for each case in type switch, // but go/parser generates just one variable. @@ -471,7 +560,7 @@ func typecheck1(cfg *TypeConfig, f interface{}, typeof map[interface{}]string) { tt = getType(tt) typeof[varx] = tt typeof[varx.Obj] = tt - typecheck1(cfg, cas.Body, typeof) + typecheck1(cfg, cas.Body, typeof, assign) } } } diff --git a/src/cmd/gofix/url.go b/src/cmd/gofix/url.go index 7135d8edf..49aac739b 100644 --- a/src/cmd/gofix/url.go +++ b/src/cmd/gofix/url.go @@ -4,17 +4,15 @@ package main -import ( - "fmt" - "os" - "go/ast" -) +import "go/ast" -var _ fmt.Stringer -var _ os.Error +func init() { + register(urlFix) +} var urlFix = fix{ "url", + "2011-08-17", url, `Move the URL pieces of package http into a new package, url. @@ -22,10 +20,6 @@ http://codereview.appspot.com/4893043 `, } -func init() { - register(urlFix) -} - var urlRenames = []struct{ in, out string }{ {"URL", "URL"}, {"ParseURL", "Parse"}, @@ -46,12 +40,7 @@ func url(f *ast.File) bool { fixed := false // Update URL code. - var skip interface{} urlWalk := func(n interface{}) { - if n == skip { - skip = nil - return - } // Is it an identifier? if ident, ok := n.(*ast.Ident); ok && ident.Name == "url" { ident.Name = "url_" @@ -62,12 +51,6 @@ func url(f *ast.File) bool { fixed = urlDoFields(fn.Params) || fixed fixed = urlDoFields(fn.Results) || fixed } - // U{url: ...} is likely a struct field. - if kv, ok := n.(*ast.KeyValueExpr); ok { - if ident, ok := kv.Key.(*ast.Ident); ok && ident.Name == "url" { - skip = ident - } - } } // Fix up URL code and add import, at most once. @@ -75,8 +58,8 @@ func url(f *ast.File) bool { if fixed { return } - walkBeforeAfter(f, urlWalk, nop) addImport(f, "url") + walkBeforeAfter(f, urlWalk, nop) fixed = true } diff --git a/src/cmd/gofix/url_test.go b/src/cmd/gofix/url_test.go index 8d9542cbc..39827f780 100644 --- a/src/cmd/gofix/url_test.go +++ b/src/cmd/gofix/url_test.go @@ -5,7 +5,7 @@ package main func init() { - addTestCases(urlTests) + addTestCases(urlTests, url) } var urlTests = []testCase{ @@ -103,14 +103,14 @@ func h() (url string) { import "url" -type U struct{ url int } +type U struct{ url_ int } type M map[int]int func f() { url.Parse(a) var url_ = 23 url_, x := 45, y - _ = U{url: url_} + _ = U{url_: url_} _ = M{url_ + 1: url_} } diff --git a/src/cmd/gofix/xmlapi.go b/src/cmd/gofix/xmlapi.go new file mode 100644 index 000000000..e74425914 --- /dev/null +++ b/src/cmd/gofix/xmlapi.go @@ -0,0 +1,111 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "go/ast" +) + +func init() { + register(xmlapiFix) +} + +var xmlapiFix = fix{ + "xmlapi", + "2012-01-23", + xmlapi, + ` + Make encoding/xml's API look more like the rest of the encoding packages. + +http://codereview.appspot.com/5574053 +`, +} + +var xmlapiTypeConfig = &TypeConfig{ + Func: map[string]string{ + "xml.NewParser": "*xml.Parser", + "os.Open": "*os.File", + "os.OpenFile": "*os.File", + "bytes.NewBuffer": "*bytes.Buffer", + "bytes.NewBufferString": "*bytes.Buffer", + "bufio.NewReader": "*bufio.Reader", + "bufio.NewReadWriter": "*bufio.ReadWriter", + }, +} + +var isReader = map[string]bool{ + "*os.File": true, + "*bytes.Buffer": true, + "*bufio.Reader": true, + "*bufio.ReadWriter": true, + "io.Reader": true, +} + +func xmlapi(f *ast.File) bool { + if !imports(f, "encoding/xml") { + return false + } + + typeof, _ := typecheck(xmlapiTypeConfig, f) + + fixed := false + walk(f, func(n interface{}) { + s, ok := n.(*ast.SelectorExpr) + if ok && typeof[s.X] == "*xml.Parser" && s.Sel.Name == "Unmarshal" { + s.Sel.Name = "DecodeElement" + fixed = true + return + } + if ok && isPkgDot(s, "xml", "Parser") { + s.Sel.Name = "Decoder" + fixed = true + return + } + + call, ok := n.(*ast.CallExpr) + if !ok { + return + } + switch { + case len(call.Args) == 2 && isPkgDot(call.Fun, "xml", "Marshal"): + *call = xmlMarshal(call.Args) + fixed = true + case len(call.Args) == 2 && isPkgDot(call.Fun, "xml", "Unmarshal"): + if isReader[typeof[call.Args[0]]] { + *call = xmlUnmarshal(call.Args) + fixed = true + } + case len(call.Args) == 1 && isPkgDot(call.Fun, "xml", "NewParser"): + sel := call.Fun.(*ast.SelectorExpr).Sel + sel.Name = "NewDecoder" + fixed = true + } + }) + return fixed +} + +func xmlMarshal(args []ast.Expr) ast.CallExpr { + return xmlCallChain("NewEncoder", "Encode", args) +} + +func xmlUnmarshal(args []ast.Expr) ast.CallExpr { + return xmlCallChain("NewDecoder", "Decode", args) +} + +func xmlCallChain(first, second string, args []ast.Expr) ast.CallExpr { + return ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: &ast.CallExpr{ + Fun: &ast.SelectorExpr{ + X: ast.NewIdent("xml"), + Sel: ast.NewIdent(first), + }, + Args: args[:1], + }, + Sel: ast.NewIdent(second), + }, + Args: args[1:2], + } +} diff --git a/src/cmd/gofix/xmlapi_test.go b/src/cmd/gofix/xmlapi_test.go new file mode 100644 index 000000000..6486c8124 --- /dev/null +++ b/src/cmd/gofix/xmlapi_test.go @@ -0,0 +1,85 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +func init() { + addTestCases(xmlapiTests, xmlapi) +} + +var xmlapiTests = []testCase{ + { + Name: "xmlapi.0", + In: `package main + +import "encoding/xml" + +func f() { + xml.Marshal(a, b) + xml.Unmarshal(a, b) + + var buf1 bytes.Buffer + buf2 := &bytes.Buffer{} + buf3 := bytes.NewBuffer(data) + buf4 := bytes.NewBufferString(data) + buf5 := bufio.NewReader(r) + xml.Unmarshal(&buf1, v) + xml.Unmarshal(buf2, v) + xml.Unmarshal(buf3, v) + xml.Unmarshal(buf4, v) + xml.Unmarshal(buf5, v) + + f := os.Open("foo.xml") + xml.Unmarshal(f, v) + + p1 := xml.NewParser(stream) + p1.Unmarshal(v, start) + + var p2 *xml.Parser + p2.Unmarshal(v, start) +} + +func g(r io.Reader, f *os.File, b []byte) { + xml.Unmarshal(r, v) + xml.Unmarshal(f, v) + xml.Unmarshal(b, v) +} +`, + Out: `package main + +import "encoding/xml" + +func f() { + xml.NewEncoder(a).Encode(b) + xml.Unmarshal(a, b) + + var buf1 bytes.Buffer + buf2 := &bytes.Buffer{} + buf3 := bytes.NewBuffer(data) + buf4 := bytes.NewBufferString(data) + buf5 := bufio.NewReader(r) + xml.NewDecoder(&buf1).Decode(v) + xml.NewDecoder(buf2).Decode(v) + xml.NewDecoder(buf3).Decode(v) + xml.NewDecoder(buf4).Decode(v) + xml.NewDecoder(buf5).Decode(v) + + f := os.Open("foo.xml") + xml.NewDecoder(f).Decode(v) + + p1 := xml.NewDecoder(stream) + p1.DecodeElement(v, start) + + var p2 *xml.Decoder + p2.DecodeElement(v, start) +} + +func g(r io.Reader, f *os.File, b []byte) { + xml.NewDecoder(r).Decode(v) + xml.NewDecoder(f).Decode(v) + xml.Unmarshal(b, v) +} +`, + }, +} diff --git a/src/cmd/gofmt/doc.go b/src/cmd/gofmt/doc.go index 3a20c21e0..65842a3b1 100644 --- a/src/cmd/gofmt/doc.go +++ b/src/cmd/gofmt/doc.go @@ -36,10 +36,8 @@ The flags are: Formatting control flags: -comments=true Print comments; if false, all comments are elided from the output. - -spaces - Align with spaces instead of tabs. - -tabindent - Indent with tabs independent of -spaces. + -tabs=true + Indent with tabs; if false, spaces are used instead. -tabwidth=8 Tab width in spaces. diff --git a/src/cmd/gofmt/gofmt.go b/src/cmd/gofmt/gofmt.go index 1c0efb6db..6d610adc0 100644 --- a/src/cmd/gofmt/gofmt.go +++ b/src/cmd/gofmt/gofmt.go @@ -6,7 +6,6 @@ package main import ( "bytes" - "exec" "flag" "fmt" "go/ast" @@ -17,6 +16,7 @@ import ( "io" "io/ioutil" "os" + "os/exec" "path/filepath" "runtime/pprof" "strings" @@ -34,8 +34,7 @@ var ( // layout control comments = flag.Bool("comments", true, "print comments") tabWidth = flag.Int("tabwidth", 8, "tab width") - tabIndent = flag.Bool("tabindent", true, "indent with tabs independent of -spaces") - useSpaces = flag.Bool("spaces", true, "align with spaces instead of tabs") + tabIndent = flag.Bool("tabs", true, "indent with tabs") // debugging cpuprofile = flag.String("cpuprofile", "", "write cpu profile to this file") @@ -45,11 +44,11 @@ var ( fset = token.NewFileSet() exitCode = 0 rewrite func(*ast.File) *ast.File - parserMode uint + parserMode parser.Mode printerMode uint ) -func report(err os.Error) { +func report(err error) { scanner.PrintError(os.Stderr, err) exitCode = 2 } @@ -61,7 +60,7 @@ func usage() { } func initParserMode() { - parserMode = uint(0) + parserMode = parser.Mode(0) if *comments { parserMode |= parser.ParseComments } @@ -71,22 +70,20 @@ func initParserMode() { } func initPrinterMode() { - printerMode = uint(0) + printerMode = printer.UseSpaces if *tabIndent { printerMode |= printer.TabIndent } - if *useSpaces { - printerMode |= printer.UseSpaces - } } -func isGoFile(f *os.FileInfo) bool { +func isGoFile(f os.FileInfo) bool { // ignore non-Go files - return f.IsRegular() && !strings.HasPrefix(f.Name, ".") && strings.HasSuffix(f.Name, ".go") + name := f.Name() + return !f.IsDir() && !strings.HasPrefix(name, ".") && strings.HasSuffix(name, ".go") } // If in == nil, the source is the contents of the file with the given filename. -func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Error { +func processFile(filename string, in io.Reader, out io.Writer, stdin bool) error { if in == nil { f, err := os.Open(filename) if err != nil { @@ -107,19 +104,28 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Er } if rewrite != nil { - file = rewrite(file) + if adjust == nil { + file = rewrite(file) + } else { + fmt.Fprintf(os.Stderr, "warning: rewrite ignored for incomplete programs\n") + } } + ast.SortImports(fset, file) + if *simplifyAST { simplify(file) } var buf bytes.Buffer - _, err = (&printer.Config{printerMode, *tabWidth}).Fprint(&buf, fset, file) + err = (&printer.Config{printerMode, *tabWidth}).Fprint(&buf, fset, file) if err != nil { return err } - res := adjust(src, buf.Bytes()) + res := buf.Bytes() + if adjust != nil { + res = adjust(src, res) + } if !bytes.Equal(src, res) { // formatting has changed @@ -149,7 +155,7 @@ func processFile(filename string, in io.Reader, out io.Writer, stdin bool) os.Er return err } -func visitFile(path string, f *os.FileInfo, err os.Error) os.Error { +func visitFile(path string, f os.FileInfo, err error) error { if err == nil && isGoFile(f) { err = processFile(path, nil, os.Stdout, false) } @@ -208,17 +214,17 @@ func gofmtMain() { switch dir, err := os.Stat(path); { case err != nil: report(err) - case dir.IsRegular(): + case dir.IsDir(): + walkDir(path) + default: if err := processFile(path, nil, os.Stdout, false); err != nil { report(err) } - case dir.IsDirectory(): - walkDir(path) } } } -func diff(b1, b2 []byte) (data []byte, err os.Error) { +func diff(b1, b2 []byte) (data []byte, err error) { f1, err := ioutil.TempFile("", "gofmt") if err != nil { return @@ -248,17 +254,16 @@ func diff(b1, b2 []byte) (data []byte, err os.Error) { // parse parses src, which was read from filename, // as a Go source file or statement list. -func parse(filename string, src []byte, stdin bool) (*ast.File, func(orig, src []byte) []byte, os.Error) { +func parse(filename string, src []byte, stdin bool) (*ast.File, func(orig, src []byte) []byte, error) { // Try as whole source file. file, err := parser.ParseFile(fset, filename, src, parserMode) if err == nil { - adjust := func(orig, src []byte) []byte { return src } - return file, adjust, nil + return file, nil, nil } // If the error is that the source file didn't begin with a // package line and this is standard input, fall through to // try as a source fragment. Stop and return on any other error. - if !stdin || !strings.Contains(err.String(), "expected 'package'") { + if !stdin || !strings.Contains(err.Error(), "expected 'package'") { return nil, nil, err } @@ -280,7 +285,7 @@ func parse(filename string, src []byte, stdin bool) (*ast.File, func(orig, src [ // If the error is that the source file didn't begin with a // declaration, fall through to try as a statement list. // Stop and return on any other error. - if !strings.Contains(err.String(), "expected declaration") { + if !strings.Contains(err.Error(), "expected declaration") { return nil, nil, err } @@ -318,7 +323,10 @@ func cutSpace(b []byte) (before, middle, after []byte) { for j > 0 && (b[j-1] == ' ' || b[j-1] == '\t' || b[j-1] == '\n') { j-- } - return b[:i], b[i:j], b[j:] + if i <= j { + return b[:i], b[i:j], b[j:] + } + return nil, nil, b[j:] } // matchSpace reformats src to use the same space context as orig. diff --git a/src/cmd/gofmt/gofmt_test.go b/src/cmd/gofmt/gofmt_test.go index 87b02dad7..303c4f1e1 100644 --- a/src/cmd/gofmt/gofmt_test.go +++ b/src/cmd/gofmt/gofmt_test.go @@ -76,7 +76,10 @@ var tests = []struct { {"testdata/old.input", ""}, {"testdata/rewrite1.input", "-r=Foo->Bar"}, {"testdata/rewrite2.input", "-r=int->bool"}, + {"testdata/rewrite3.input", "-r=x->x"}, {"testdata/stdin*.input", "-stdin"}, + {"testdata/comments.input", ""}, + {"testdata/import.input", ""}, } func TestRewrite(t *testing.T) { diff --git a/src/cmd/gofmt/rewrite.go b/src/cmd/gofmt/rewrite.go index 3d74dea0f..3c7861f0d 100644 --- a/src/cmd/gofmt/rewrite.go +++ b/src/cmd/gofmt/rewrite.go @@ -13,7 +13,7 @@ import ( "reflect" "strings" "unicode" - "utf8" + "unicode/utf8" ) func initRewrite() { @@ -36,7 +36,7 @@ func initRewrite() { // but there are problems with preserving formatting and also // with what a wildcard for a statement looks like. func parseExpr(s string, what string) ast.Expr { - x, err := parser.ParseExpr(fset, "input", s) + x, err := parser.ParseExpr(s) if err != nil { fmt.Fprintf(os.Stderr, "parsing %s %s: %s\n", what, s, err) os.Exit(2) @@ -65,7 +65,7 @@ func rewriteFile(pattern, replace ast.Expr, p *ast.File) *ast.File { return reflect.Value{} } for k := range m { - m[k] = reflect.Value{}, false + delete(m, k) } val = apply(f, val) if match(m, pat, val) { @@ -85,7 +85,8 @@ func setValue(x, y reflect.Value) { } defer func() { if x := recover(); x != nil { - if s, ok := x.(string); ok && strings.HasPrefix(s, "type mismatch") { + if s, ok := x.(string); ok && + (strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) { // x cannot be set to y - ignore this rewrite return } @@ -158,8 +159,8 @@ func match(m map[string]reflect.Value, pattern, val reflect.Value) bool { if m != nil && pattern.IsValid() && pattern.Type() == identType { name := pattern.Interface().(*ast.Ident).Name if isWildcard(name) && val.IsValid() { - // wildcards only match expressions - if _, ok := val.Interface().(ast.Expr); ok { + // wildcards only match valid (non-nil) expressions. + if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() { if old, ok := m[name]; ok { return match(nil, old, val) } diff --git a/src/cmd/gofmt/simplify.go b/src/cmd/gofmt/simplify.go index d9afc0e7b..470c00625 100644 --- a/src/cmd/gofmt/simplify.go +++ b/src/cmd/gofmt/simplify.go @@ -6,6 +6,7 @@ package main import ( "go/ast" + "go/token" "reflect" ) @@ -26,10 +27,12 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor { if eltType != nil { typ := reflect.ValueOf(eltType) - for _, x := range outer.Elts { + for i, x := range outer.Elts { + px := &outer.Elts[i] // look at value of indexed/named elements if t, ok := x.(*ast.KeyValueExpr); ok { x = t.Value + px = &t.Value } simplify(x) // if the element is a composite literal and its literal type @@ -40,6 +43,19 @@ func (s *simplifier) Visit(node ast.Node) ast.Visitor { inner.Type = nil } } + // if the outer literal's element type is a pointer type *T + // and the element is & of a composite literal of type T, + // the inner &T may be omitted. + if ptr, ok := eltType.(*ast.StarExpr); ok { + if addr, ok := x.(*ast.UnaryExpr); ok && addr.Op == token.AND { + if inner, ok := addr.X.(*ast.CompositeLit); ok { + if match(nil, reflect.ValueOf(ptr.X), reflect.ValueOf(inner.Type)) { + inner.Type = nil // drop T + *px = inner // drop & + } + } + } + } } // node was simplified - stop walk (there are no subnodes to simplify) diff --git a/src/cmd/gofmt/test.sh b/src/cmd/gofmt/test.sh index 063a0727f..c18987f4d 100755 --- a/src/cmd/gofmt/test.sh +++ b/src/cmd/gofmt/test.sh @@ -14,6 +14,7 @@ TMP1=test_tmp1.go TMP2=test_tmp2.go TMP3=test_tmp3.go COUNT=0 +rm -f _failed count() { #echo $1 @@ -27,10 +28,9 @@ count() { error() { echo $1 - exit 1 + touch _failed } - # apply to one file apply1() { # the following files are skipped because they are test cases @@ -43,7 +43,8 @@ apply1() { bug226.go | bug228.go | bug248.go | bug274.go | bug280.go | \ bug282.go | bug287.go | bug298.go | bug299.go | bug300.go | \ bug302.go | bug306.go | bug322.go | bug324.go | bug335.go | \ - bug340.go | bug349.go | bug351.go | bug358.go ) return ;; + bug340.go | bug349.go | bug351.go | bug358.go | bug367.go | \ + bug388.go | bug394.go ) return ;; esac # the following directories are skipped because they contain test # cases for syntax errors and thus won't parse in the first place: @@ -157,6 +158,11 @@ runtests() { runtests "$@" cleanup +if [ -f _failed ]; then + rm _failed + exit 1 +fi + # done echo echo "PASSED ($COUNT tests)" diff --git a/src/cmd/gofmt/testdata/comments.golden b/src/cmd/gofmt/testdata/comments.golden new file mode 100644 index 000000000..ad6bcafaf --- /dev/null +++ b/src/cmd/gofmt/testdata/comments.golden @@ -0,0 +1,9 @@ +package main + +func main() {} + +// comment here + +func f() {} + +//line foo.go:1 diff --git a/src/cmd/gofmt/testdata/comments.input b/src/cmd/gofmt/testdata/comments.input new file mode 100644 index 000000000..ad6bcafaf --- /dev/null +++ b/src/cmd/gofmt/testdata/comments.input @@ -0,0 +1,9 @@ +package main + +func main() {} + +// comment here + +func f() {} + +//line foo.go:1 diff --git a/src/cmd/gofmt/testdata/composites.golden b/src/cmd/gofmt/testdata/composites.golden index 1fd5847c1..b2825e732 100644 --- a/src/cmd/gofmt/testdata/composites.golden +++ b/src/cmd/gofmt/testdata/composites.golden @@ -102,3 +102,101 @@ var pieces4 = []Piece{ {2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, {3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, } + +var _ = [42]*T{ + {}, + {1, 2}, + {3, 4}, +} + +var _ = [...]*T{ + {}, + {1, 2}, + {3, 4}, +} + +var _ = []*T{ + {}, + {1, 2}, + {3, 4}, +} + +var _ = []*T{ + {}, + 10: {1, 2}, + 20: {3, 4}, +} + +var _ = []*struct { + x, y int +}{ + {}, + 10: {1, 2}, + 20: {3, 4}, +} + +var _ = []interface{}{ + &T{}, + 10: &T{1, 2}, + 20: &T{3, 4}, +} + +var _ = []*[]int{ + {}, + {1, 2}, + {3, 4}, +} + +var _ = []*[]int{ + (&[]int{}), + (&[]int{1, 2}), + {3, 4}, +} + +var _ = []*[]*[]int{ + {}, + { + {}, + {0, 1, 2, 3}, + {4, 5}, + }, +} + +var _ = map[string]*T{ + "foo": {}, + "bar": {1, 2}, + "bal": {3, 4}, +} + +var _ = map[string]*struct { + x, y int +}{ + "foo": {}, + "bar": {1, 2}, + "bal": {3, 4}, +} + +var _ = map[string]interface{}{ + "foo": &T{}, + "bar": &T{1, 2}, + "bal": &T{3, 4}, +} + +var _ = map[string]*[]int{ + "foo": {}, + "bar": {1, 2}, + "bal": {3, 4}, +} + +var _ = map[string]*[]int{ + "foo": (&[]int{}), + "bar": (&[]int{1, 2}), + "bal": {3, 4}, +} + +var pieces4 = []*Piece{ + {0, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, + {1, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, + {2, 0, Point{4, 1}, []Point{{0, 0}, {1, 0}, {1, 0}, {1, 0}}, nil, nil}, + {3, 0, Point{1, 4}, []Point{{0, 0}, {0, 1}, {0, 1}, {0, 1}}, nil, nil}, +} diff --git a/src/cmd/gofmt/testdata/composites.input b/src/cmd/gofmt/testdata/composites.input index 15afd9e5c..7210dafc9 100644 --- a/src/cmd/gofmt/testdata/composites.input +++ b/src/cmd/gofmt/testdata/composites.input @@ -102,3 +102,101 @@ var pieces4 = []Piece{ Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, } + +var _ = [42]*T{ + &T{}, + &T{1, 2}, + &T{3, 4}, +} + +var _ = [...]*T{ + &T{}, + &T{1, 2}, + &T{3, 4}, +} + +var _ = []*T{ + &T{}, + &T{1, 2}, + &T{3, 4}, +} + +var _ = []*T{ + &T{}, + 10: &T{1, 2}, + 20: &T{3, 4}, +} + +var _ = []*struct { + x, y int +}{ + &struct{ x, y int }{}, + 10: &struct{ x, y int }{1, 2}, + 20: &struct{ x, y int }{3, 4}, +} + +var _ = []interface{}{ + &T{}, + 10: &T{1, 2}, + 20: &T{3, 4}, +} + +var _ = []*[]int{ + &[]int{}, + &[]int{1, 2}, + &[]int{3, 4}, +} + +var _ = []*[]int{ + (&[]int{}), + (&[]int{1, 2}), + &[]int{3, 4}, +} + +var _ = []*[]*[]int{ + &[]*[]int{}, + &[]*[]int{ + &[]int{}, + &[]int{0, 1, 2, 3}, + &[]int{4, 5}, + }, +} + +var _ = map[string]*T{ + "foo": &T{}, + "bar": &T{1, 2}, + "bal": &T{3, 4}, +} + +var _ = map[string]*struct { + x, y int +}{ + "foo": &struct{ x, y int }{}, + "bar": &struct{ x, y int }{1, 2}, + "bal": &struct{ x, y int }{3, 4}, +} + +var _ = map[string]interface{}{ + "foo": &T{}, + "bar": &T{1, 2}, + "bal": &T{3, 4}, +} + +var _ = map[string]*[]int{ + "foo": &[]int{}, + "bar": &[]int{1, 2}, + "bal": &[]int{3, 4}, +} + +var _ = map[string]*[]int{ + "foo": (&[]int{}), + "bar": (&[]int{1, 2}), + "bal": &[]int{3, 4}, +} + +var pieces4 = []*Piece{ + &Piece{0, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, + &Piece{1, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, + &Piece{2, 0, Point{4, 1}, []Point{Point{0, 0}, Point{1, 0}, Point{1, 0}, Point{1, 0}}, nil, nil}, + &Piece{3, 0, Point{1, 4}, []Point{Point{0, 0}, Point{0, 1}, Point{0, 1}, Point{0, 1}}, nil, nil}, +} diff --git a/src/cmd/gofmt/testdata/import.golden b/src/cmd/gofmt/testdata/import.golden new file mode 100644 index 000000000..e8ee44988 --- /dev/null +++ b/src/cmd/gofmt/testdata/import.golden @@ -0,0 +1,108 @@ +package main + +import ( + "errors" + "fmt" + "io" + "log" + "math" +) + +import ( + "fmt" + + "math" + + "log" + + "errors" + + "io" +) + +import ( + "errors" + "fmt" + "io" + "log" + "math" + + "fmt" + + "math" + + "log" + + "errors" + + "io" +) + +import ( + // a block with comments + "errors" + "fmt" // for Printf + "io" // for Reader + "log" // for Fatal + "math" +) + +import ( + "fmt" // for Printf + + "math" + + "log" // for Fatal + + "errors" + + "io" // for Reader +) + +import ( + // for Printf + "fmt" + + "math" + + // for Fatal + "log" + + "errors" + + // for Reader + "io" +) + +import ( + "errors" + "fmt" // for Printf + "io" // for Reader + "log" // for Fatal + "math" + + "fmt" // for Printf + + "math" + + "log" // for Fatal + + "errors" + + "io" // for Reader +) + +import ( + "fmt" // for Printf + + "errors" + "io" // for Reader + "log" // for Fatal + "math" + + "errors" + "fmt" // for Printf + "io" // for Reader + "log" // for Fatal + "math" +) diff --git a/src/cmd/gofmt/testdata/import.input b/src/cmd/gofmt/testdata/import.input new file mode 100644 index 000000000..cc36c3e01 --- /dev/null +++ b/src/cmd/gofmt/testdata/import.input @@ -0,0 +1,108 @@ +package main + +import ( + "fmt" + "math" + "log" + "errors" + "io" +) + +import ( + "fmt" + + "math" + + "log" + + "errors" + + "io" +) + +import ( + "fmt" + "math" + "log" + "errors" + "io" + + "fmt" + + "math" + + "log" + + "errors" + + "io" +) + +import ( + // a block with comments + "fmt" // for Printf + "math" + "log" // for Fatal + "errors" + "io" // for Reader +) + +import ( + "fmt" // for Printf + + "math" + + "log" // for Fatal + + "errors" + + "io" // for Reader +) + +import ( + // for Printf + "fmt" + + "math" + + // for Fatal + "log" + + "errors" + + // for Reader + "io" +) + +import ( + "fmt" // for Printf + "math" + "log" // for Fatal + "errors" + "io" // for Reader + + "fmt" // for Printf + + "math" + + "log" // for Fatal + + "errors" + + "io" // for Reader +) + +import ( + "fmt" // for Printf + + "math" + "log" // for Fatal + "errors" + "io" // for Reader + + "fmt" // for Printf + "math" + "log" // for Fatal + "errors" + "io" // for Reader +) diff --git a/src/cmd/gofmt/testdata/rewrite3.golden b/src/cmd/gofmt/testdata/rewrite3.golden new file mode 100644 index 000000000..0d16d1601 --- /dev/null +++ b/src/cmd/gofmt/testdata/rewrite3.golden @@ -0,0 +1,12 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Field tags are *ast.BasicLit nodes that are nil when the tag is +// absent. These nil nodes must not be mistaken for expressions, +// the rewriter should not try to dereference them. Was issue 2410. +type Foo struct { + Field int +} diff --git a/src/cmd/gofmt/testdata/rewrite3.input b/src/cmd/gofmt/testdata/rewrite3.input new file mode 100644 index 000000000..0d16d1601 --- /dev/null +++ b/src/cmd/gofmt/testdata/rewrite3.input @@ -0,0 +1,12 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +// Field tags are *ast.BasicLit nodes that are nil when the tag is +// absent. These nil nodes must not be mistaken for expressions, +// the rewriter should not try to dereference them. Was issue 2410. +type Foo struct { + Field int +} diff --git a/src/cmd/goinstall/doc.go b/src/cmd/goinstall/doc.go index 47c615364..368e1707b 100644 --- a/src/cmd/goinstall/doc.go +++ b/src/cmd/goinstall/doc.go @@ -58,7 +58,7 @@ download the code if necessary. Goinstall recognizes packages from a few common code hosting sites: - BitBucket (Mercurial) + BitBucket (Git, Mercurial) import "bitbucket.org/user/project" import "bitbucket.org/user/project/sub/directory" @@ -79,6 +79,10 @@ Goinstall recognizes packages from a few common code hosting sites: import "project.googlecode.com/svn/trunk" import "project.googlecode.com/svn/trunk/sub/directory" + Google Code Project Hosting sub-repositories: + + import "code.google.com/p/project.subrepo/sub/directory + Launchpad (Bazaar) import "launchpad.net/project" diff --git a/src/cmd/goinstall/download.go b/src/cmd/goinstall/download.go index cc873150a..8e6cb4b37 100644 --- a/src/cmd/goinstall/download.go +++ b/src/cmd/goinstall/download.go @@ -8,10 +8,13 @@ package main import ( "bytes" - "exec" + "encoding/json" + "errors" "fmt" - "http" + "io/ioutil" + "net/http" "os" + "os/exec" "path/filepath" "regexp" "runtime" @@ -53,207 +56,422 @@ type vcs struct { check string protocols []string suffix string - defaultHosts []host } -type host struct { - pattern *regexp.Regexp - protocol string - suffix string +func (v *vcs) String() string { + return v.name } -var hg = vcs{ - name: "Mercurial", - cmd: "hg", - metadir: ".hg", - checkout: "checkout", - clone: "clone", - update: "update", - pull: "pull", - tagList: "tags", - tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"), - check: "identify", - protocols: []string{"https", "http"}, - suffix: ".hg", - defaultHosts: []host{ - {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/hg)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""}, - {regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ""}, +var vcsMap = map[string]*vcs{ + "hg": { + name: "Mercurial", + cmd: "hg", + metadir: ".hg", + checkout: "checkout", + clone: "clone", + update: "update", + pull: "pull", + tagList: "tags", + tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"), + check: "identify", + protocols: []string{"https", "http"}, + suffix: ".hg", }, -} -var git = vcs{ - name: "Git", - cmd: "git", - metadir: ".git", - checkout: "checkout", - clone: "clone", - update: "pull", - pull: "fetch", - tagList: "tag", - tagListRe: regexp.MustCompile("([^\n]+)\n"), - check: "ls-remote", - protocols: []string{"git", "https", "http"}, - suffix: ".git", - defaultHosts: []host{ - {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/git)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""}, - {regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]*)?$`), "http", ".git"}, + "git": { + name: "Git", + cmd: "git", + metadir: ".git", + checkout: "checkout", + clone: "clone", + update: "pull", + pull: "fetch", + tagList: "tag", + tagListRe: regexp.MustCompile("([^\n]+)\n"), + check: "ls-remote", + protocols: []string{"git", "https", "http"}, + suffix: ".git", + }, + + "svn": { + name: "Subversion", + cmd: "svn", + metadir: ".svn", + checkout: "checkout", + clone: "checkout", + update: "update", + check: "info", + protocols: []string{"https", "http", "svn"}, + suffix: ".svn", }, -} -var svn = vcs{ - name: "Subversion", - cmd: "svn", - metadir: ".svn", - checkout: "checkout", - clone: "checkout", - update: "update", - check: "info", - protocols: []string{"https", "http", "svn"}, - suffix: ".svn", - defaultHosts: []host{ - {regexp.MustCompile(`^([a-z0-9\-]+\.googlecode\.com/svn)(/[a-z0-9A-Z_.\-/]*)?$`), "https", ""}, + "bzr": { + name: "Bazaar", + cmd: "bzr", + metadir: ".bzr", + checkout: "update", + clone: "branch", + update: "update", + updateRevFlag: "-r", + pull: "pull", + pullForceFlag: "--overwrite", + tagList: "tags", + tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"), + check: "info", + protocols: []string{"https", "http", "bzr"}, + suffix: ".bzr", }, } -var bzr = vcs{ - name: "Bazaar", - cmd: "bzr", - metadir: ".bzr", - checkout: "update", - clone: "branch", - update: "update", - updateRevFlag: "-r", - pull: "pull", - pullForceFlag: "--overwrite", - tagList: "tags", - tagListRe: regexp.MustCompile("([^ ]+)[^\n]+\n"), - check: "info", - protocols: []string{"https", "http", "bzr"}, - suffix: ".bzr", - defaultHosts: []host{ - {regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`), "https", ""}, +type RemoteRepo interface { + // IsCheckedOut returns whether this repository is checked + // out inside the given srcDir (eg, $GOPATH/src). + IsCheckedOut(srcDir string) bool + + // Repo returns the information about this repository: its url, + // the part of the import path that forms the repository root, + // and the version control system it uses. It may discover this + // information by using the supplied client to make HTTP requests. + Repo(*http.Client) (url, root string, vcs *vcs, err error) +} + +type host struct { + pattern *regexp.Regexp + repo func(repo string) (RemoteRepo, error) +} + +var knownHosts = []host{ + { + regexp.MustCompile(`^code\.google\.com/p/([a-z0-9\-]+(\.[a-z0-9\-]+)?)(/[a-z0-9A-Z_.\-/]+)?$`), + matchGoogleRepo, + }, + { + regexp.MustCompile(`^(github\.com/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`), + matchGithubRepo, + }, + { + regexp.MustCompile(`^(bitbucket\.org/[a-z0-9A-Z_.\-]+/[a-z0-9A-Z_.\-]+)(/[a-z0-9A-Z_.\-/]+)?$`), + matchBitbucketRepo, + }, + { + regexp.MustCompile(`^(launchpad\.net/([a-z0-9A-Z_.\-]+(/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+))(/[a-z0-9A-Z_.\-/]+)?$`), + matchLaunchpadRepo, }, } -var vcsList = []*vcs{&git, &hg, &bzr, &svn} +// baseRepo is the base implementation of RemoteRepo. +type baseRepo struct { + url, root string + vcs *vcs +} -type vcsMatch struct { - *vcs - prefix, repo string +func (r *baseRepo) Repo(*http.Client) (url, root string, vcs *vcs, err error) { + return r.url, r.root, r.vcs, nil } -// findPublicRepo checks whether pkg is located at one of -// the supported code hosting sites and, if so, returns a match. -func findPublicRepo(pkg string) (*vcsMatch, os.Error) { - for _, v := range vcsList { - for _, host := range v.defaultHosts { - if hm := host.pattern.FindStringSubmatch(pkg); hm != nil { - if host.suffix != "" && strings.HasSuffix(hm[1], host.suffix) { - return nil, os.NewError("repository " + pkg + " should not have " + v.suffix + " suffix") - } - repo := host.protocol + "://" + hm[1] + host.suffix - return &vcsMatch{v, hm[1], repo}, nil +// IsCheckedOut reports whether the repo root inside srcDir contains a +// repository metadir. It updates the baseRepo's vcs field if necessary. +func (r *baseRepo) IsCheckedOut(srcDir string) bool { + pkgPath := filepath.Join(srcDir, r.root) + if r.vcs == nil { + for _, vcs := range vcsMap { + if isDir(filepath.Join(pkgPath, vcs.metadir)) { + r.vcs = vcs + return true } } + return false + } + return isDir(filepath.Join(pkgPath, r.vcs.metadir)) +} + +// matchGithubRepo handles matches for github.com repositories. +func matchGithubRepo(root string) (RemoteRepo, error) { + if strings.HasSuffix(root, ".git") { + return nil, errors.New("path must not include .git suffix") + } + return &baseRepo{"http://" + root + ".git", root, vcsMap["git"]}, nil +} + +// matchLaunchpadRepo handles matches for launchpad.net repositories. +func matchLaunchpadRepo(root string) (RemoteRepo, error) { + return &baseRepo{"https://" + root, root, vcsMap["bzr"]}, nil +} + +// matchGoogleRepo matches repos like "code.google.com/p/repo.subrepo/path". +func matchGoogleRepo(id string) (RemoteRepo, error) { + root := "code.google.com/p/" + id + return &googleRepo{baseRepo{"https://" + root, root, nil}}, nil +} + +// googleRepo implements a RemoteRepo that discovers a Google Code +// repository's VCS type by scraping the code.google.com source checkout page. +type googleRepo struct{ baseRepo } + +var googleRepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`) + +func (r *googleRepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) { + if r.vcs != nil { + return r.url, r.root, r.vcs, nil + } + + // Use the code.google.com source checkout page to find the VCS type. + const prefix = "code.google.com/p/" + p := strings.SplitN(r.root[len(prefix):], ".", 2) + u := fmt.Sprintf("https://%s%s/source/checkout", prefix, p[0]) + if len(p) == 2 { + u += fmt.Sprintf("?repo=%s", p[1]) + } + resp, err := client.Get(u) + if err != nil { + return "", "", nil, err + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return "", "", nil, fmt.Errorf("fetching %s: %v", u, resp.Status) + } + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", "", nil, fmt.Errorf("fetching %s: %v", u, err) + } + + // Scrape result for vcs details. + if m := googleRepoRe.FindSubmatch(b); len(m) == 2 { + s := string(m[1]) + if v := vcsMap[s]; v != nil { + if s == "svn" { + // Subversion still uses the old-style URL. + r.url = fmt.Sprintf("http://%s.googlecode.com/svn", p[0]) + } + r.vcs = v + return r.url, r.root, r.vcs, nil + } + } + + return "", "", nil, errors.New("could not detect googlecode vcs") +} + +// matchBitbucketRepo handles matches for all bitbucket.org repositories. +func matchBitbucketRepo(root string) (RemoteRepo, error) { + if strings.HasSuffix(root, ".git") { + return nil, errors.New("path must not include .git suffix") + } + return &bitbucketRepo{baseRepo{root: root}}, nil +} + +// bitbucketRepo implements a RemoteRepo that uses the BitBucket API to +// discover the repository's VCS type. +type bitbucketRepo struct{ baseRepo } + +func (r *bitbucketRepo) Repo(client *http.Client) (url, root string, vcs *vcs, err error) { + if r.vcs != nil && r.url != "" { + return r.url, r.root, r.vcs, nil + } + + // Use the BitBucket API to find which kind of repository this is. + const apiUrl = "https://api.bitbucket.org/1.0/repositories/" + resp, err := client.Get(apiUrl + strings.SplitN(r.root, "/", 2)[1]) + if err != nil { + return "", "", nil, fmt.Errorf("BitBucket API: %v", err) + } + defer resp.Body.Close() + if resp.StatusCode != 200 { + return "", "", nil, fmt.Errorf("BitBucket API: %v", resp.Status) + } + var response struct { + Vcs string `json:"scm"` + } + err = json.NewDecoder(resp.Body).Decode(&response) + if err != nil { + return "", "", nil, fmt.Errorf("BitBucket API: %v", err) + } + switch response.Vcs { + case "git": + r.url = "http://" + r.root + ".git" + case "hg": + r.url = "http://" + r.root + default: + return "", "", nil, errors.New("unsupported bitbucket vcs: " + response.Vcs) + } + if r.vcs = vcsMap[response.Vcs]; r.vcs == nil { + panic("vcs is nil when it should not be") + } + return r.url, r.root, r.vcs, nil +} + +// findPublicRepo checks whether importPath is a well-formed path for one of +// the supported code hosting sites and, if so, returns a RemoteRepo. +func findPublicRepo(importPath string) (RemoteRepo, error) { + for _, host := range knownHosts { + if hm := host.pattern.FindStringSubmatch(importPath); hm != nil { + return host.repo(hm[1]) + } } return nil, nil } -// findAnyRepo looks for a vcs suffix in pkg (.git, etc) and returns a match. -func findAnyRepo(pkg string) (*vcsMatch, os.Error) { - for _, v := range vcsList { - i := strings.Index(pkg+"/", v.suffix+"/") +// findAnyRepo matches import paths with a repo suffix (.git, etc). +func findAnyRepo(importPath string) RemoteRepo { + for _, v := range vcsMap { + i := strings.Index(importPath+"/", v.suffix+"/") if i < 0 { continue } - if !strings.Contains(pkg[:i], "/") { + if !strings.Contains(importPath[:i], "/") { continue // don't match vcs suffix in the host name } - if m := v.find(pkg[:i]); m != nil { - return m, nil + return &anyRepo{ + baseRepo{ + root: importPath[:i] + v.suffix, + vcs: v, + }, + importPath[:i], } - return nil, fmt.Errorf("couldn't find %s repository", v.name) } - return nil, nil + return nil +} + +// anyRepo implements an discoverable remote repo with a suffix (.git, etc). +type anyRepo struct { + baseRepo + rootWithoutSuffix string +} + +func (r *anyRepo) Repo(*http.Client) (url, root string, vcs *vcs, err error) { + if r.url != "" { + return r.url, r.root, r.vcs, nil + } + url, err = r.vcs.findURL(r.rootWithoutSuffix) + if url == "" && err == nil { + err = fmt.Errorf("couldn't find %s repository", r.vcs.name) + } + if err != nil { + return "", "", nil, err + } + r.url = url + return r.url, r.root, r.vcs, nil } -func (v *vcs) find(pkg string) *vcsMatch { +// findURL finds the URL for a given repo root by trying each combination of +// protocol and suffix in series. +func (v *vcs) findURL(root string) (string, error) { for _, proto := range v.protocols { for _, suffix := range []string{"", v.suffix} { - repo := proto + "://" + pkg + suffix - out, err := exec.Command(v.cmd, v.check, repo).CombinedOutput() + url := proto + "://" + root + suffix + out, err := exec.Command(v.cmd, v.check, url).CombinedOutput() if err == nil { - printf("find %s: found %s\n", pkg, repo) - return &vcsMatch{v, pkg + v.suffix, repo} + printf("find %s: found %s\n", root, url) + return url, nil } - printf("find %s: %s %s %s: %v\n%s\n", pkg, v.cmd, v.check, repo, err, out) + printf("findURL(%s): %s %s %s: %v\n%s\n", root, v.cmd, v.check, url, err, out) } } - return nil + return "", nil } -// isRemote returns true if the first part of the package name looks like a -// hostname - i.e. contains at least one '.' and the last part is at least 2 -// characters. -func isRemote(pkg string) bool { - parts := strings.SplitN(pkg, "/", 2) - if len(parts) != 2 { - return false - } - parts = strings.Split(parts[0], ".") - if len(parts) < 2 || len(parts[len(parts)-1]) < 2 { - return false - } - return true +var oldGoogleRepo = regexp.MustCompile(`^([a-z0-9\-]+)\.googlecode\.com/(svn|git|hg)(/[a-z0-9A-Z_.\-/]+)?$`) + +type errOldGoogleRepo struct { + fixedPath string +} + +func (e *errOldGoogleRepo) Error() string { + return fmt.Sprintf("unsupported import path; should be %q", e.fixedPath) } -// download checks out or updates pkg from the remote server. -func download(pkg, srcDir string) (public bool, err os.Error) { - if strings.Contains(pkg, "..") { - err = os.NewError("invalid path (contains ..)") +// download checks out or updates the specified package from the remote server. +func download(importPath, srcDir string) (public bool, err error) { + if strings.Contains(importPath, "..") { + err = errors.New("invalid path (contains ..)") return } - m, err := findPublicRepo(pkg) - if err != nil { + + if m := oldGoogleRepo.FindStringSubmatch(importPath); m != nil { + fixedPath := "code.google.com/p/" + m[1] + m[3] + err = &errOldGoogleRepo{fixedPath} return } - if m != nil { + + repo, err := findPublicRepo(importPath) + if err != nil { + return false, err + } + if repo != nil { public = true } else { - m, err = findAnyRepo(pkg) - if err != nil { - return - } + repo = findAnyRepo(importPath) } - if m == nil { - err = os.NewError("cannot download: " + pkg) + if repo == nil { + err = errors.New("cannot download: " + importPath) return } - err = m.checkoutRepo(srcDir, m.prefix, m.repo) + err = checkoutRepo(srcDir, repo) return } +// checkoutRepo checks out repo into srcDir (if it's not checked out already) +// and, if the -u flag is set, updates the repository. +func checkoutRepo(srcDir string, repo RemoteRepo) error { + if !repo.IsCheckedOut(srcDir) { + // do checkout + url, root, vcs, err := repo.Repo(http.DefaultClient) + if err != nil { + return err + } + repoPath := filepath.Join(srcDir, root) + parent, _ := filepath.Split(repoPath) + if err = os.MkdirAll(parent, 0777); err != nil { + return err + } + if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, url, repoPath); err != nil { + return err + } + return vcs.updateRepo(repoPath) + } + if *update { + // do update + _, root, vcs, err := repo.Repo(http.DefaultClient) + if err != nil { + return err + } + repoPath := filepath.Join(srcDir, root) + // Retrieve new revisions from the remote branch, if the VCS + // supports this operation independently (e.g. svn doesn't) + if vcs.pull != "" { + if vcs.pullForceFlag != "" { + if err = run(repoPath, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil { + return err + } + } else if err = run(repoPath, nil, vcs.cmd, vcs.pull); err != nil { + return err + } + } + // Update to release or latest revision + return vcs.updateRepo(repoPath) + } + return nil +} + // updateRepo gets a list of tags in the repository and // checks out the tag closest to the current runtime.Version. // If no matching tag is found, it just updates to tip. -func (v *vcs) updateRepo(dst string) os.Error { +func (v *vcs) updateRepo(repoPath string) error { if v.tagList == "" || v.tagListRe == nil { // TODO(adg): fix for svn - return run(dst, nil, v.cmd, v.update) + return run(repoPath, nil, v.cmd, v.update) } // Get tag list. stderr := new(bytes.Buffer) cmd := exec.Command(v.cmd, v.tagList) - cmd.Dir = dst + cmd.Dir = repoPath cmd.Stderr = stderr - b, err := cmd.Output() + out, err := cmd.Output() if err != nil { - errorf("%s %s: %s\n", v.cmd, v.tagList, stderr) - return err + return &RunError{strings.Join(cmd.Args, " "), repoPath, out, err} } var tags []string - for _, m := range v.tagListRe.FindAllStringSubmatch(string(b), -1) { + for _, m := range v.tagListRe.FindAllStringSubmatch(string(out), -1) { tags = append(tags, m[1]) } @@ -263,12 +481,12 @@ func (v *vcs) updateRepo(dst string) os.Error { // Select tag. if tag := selectTag(ver, tags); tag != "" { printf("selecting revision %q\n", tag) - return run(dst, nil, v.cmd, v.checkout, v.updateRevFlag+tag) + return run(repoPath, nil, v.cmd, v.checkout, v.updateRevFlag+tag) } // No matching tag found, make default selection. printf("selecting tip\n") - return run(dst, nil, v.cmd, v.update) + return run(repoPath, nil, v.cmd, v.update) } // selectTag returns the closest matching tag for a given version. @@ -279,7 +497,7 @@ func selectTag(goVersion string, tags []string) (match string) { const rPrefix = "release.r" if strings.HasPrefix(goVersion, rPrefix) { p := "go.r" - v, err := strconv.Atof64(goVersion[len(rPrefix):]) + v, err := strconv.ParseFloat(goVersion[len(rPrefix):], 64) if err != nil { return "" } @@ -288,7 +506,7 @@ func selectTag(goVersion string, tags []string) (match string) { if !strings.HasPrefix(t, p) { continue } - tf, err := strconv.Atof64(t[len(p):]) + tf, err := strconv.ParseFloat(t[len(p):], 64) if err != nil { continue } @@ -313,41 +531,7 @@ func selectTag(goVersion string, tags []string) (match string) { return match } -// checkoutRepo checks out repo into dst using vcs. -// It tries to check out (or update, if the dst already -// exists and -u was specified on the command line) -// the repository at tag/branch "release". If there is no -// such tag or branch, it falls back to the repository tip. -func (vcs *vcs) checkoutRepo(srcDir, pkgprefix, repo string) os.Error { - dst := filepath.Join(srcDir, filepath.FromSlash(pkgprefix)) - dir, err := os.Stat(filepath.Join(dst, vcs.metadir)) - if err == nil && !dir.IsDirectory() { - return os.NewError("not a directory: " + dst) - } - if err != nil { - parent, _ := filepath.Split(dst) - if err = os.MkdirAll(parent, 0777); err != nil { - return err - } - if err = run(string(filepath.Separator), nil, vcs.cmd, vcs.clone, repo, dst); err != nil { - return err - } - return vcs.updateRepo(dst) - } - if *update { - // Retrieve new revisions from the remote branch, if the VCS - // supports this operation independently (e.g. svn doesn't) - if vcs.pull != "" { - if vcs.pullForceFlag != "" { - if err = run(dst, nil, vcs.cmd, vcs.pull, vcs.pullForceFlag); err != nil { - return err - } - } else if err = run(dst, nil, vcs.cmd, vcs.pull); err != nil { - return err - } - } - // Update to release or latest revision - return vcs.updateRepo(dst) - } - return nil +func isDir(dir string) bool { + fi, err := os.Stat(dir) + return err == nil && fi.IsDir() } diff --git a/src/cmd/goinstall/download_test.go b/src/cmd/goinstall/download_test.go new file mode 100644 index 000000000..2aa6f6184 --- /dev/null +++ b/src/cmd/goinstall/download_test.go @@ -0,0 +1,149 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "errors" + "io/ioutil" + "net/http" + "testing" +) + +var FindPublicRepoTests = []struct { + pkg string + vcs, root, url string + transport *testTransport +}{ + { + "code.google.com/p/repo/path/foo", + "hg", + "code.google.com/p/repo", + "https://code.google.com/p/repo", + &testTransport{ + "https://code.google.com/p/repo/source/checkout", + `<tt id="checkoutcmd">hg clone https://...`, + }, + }, + { + "code.google.com/p/repo/path/foo", + "svn", + "code.google.com/p/repo", + "http://repo.googlecode.com/svn", + &testTransport{ + "https://code.google.com/p/repo/source/checkout", + `<tt id="checkoutcmd">svn checkout https://...`, + }, + }, + { + "code.google.com/p/repo/path/foo", + "git", + "code.google.com/p/repo", + "https://code.google.com/p/repo", + &testTransport{ + "https://code.google.com/p/repo/source/checkout", + `<tt id="checkoutcmd">git clone https://...`, + }, + }, + { + "code.google.com/p/repo.sub/path", + "hg", + "code.google.com/p/repo.sub", + "https://code.google.com/p/repo.sub", + &testTransport{ + "https://code.google.com/p/repo/source/checkout?repo=sub", + `<tt id="checkoutcmd">hg clone https://...`, + }, + }, + { + "bitbucket.org/user/repo/path/foo", + "hg", + "bitbucket.org/user/repo", + "http://bitbucket.org/user/repo", + &testTransport{ + "https://api.bitbucket.org/1.0/repositories/user/repo", + `{"scm": "hg"}`, + }, + }, + { + "bitbucket.org/user/repo/path/foo", + "git", + "bitbucket.org/user/repo", + "http://bitbucket.org/user/repo.git", + &testTransport{ + "https://api.bitbucket.org/1.0/repositories/user/repo", + `{"scm": "git"}`, + }, + }, + { + "github.com/user/repo/path/foo", + "git", + "github.com/user/repo", + "http://github.com/user/repo.git", + nil, + }, + { + "launchpad.net/project/series/path", + "bzr", + "launchpad.net/project/series", + "https://launchpad.net/project/series", + nil, + }, + { + "launchpad.net/~user/project/branch/path", + "bzr", + "launchpad.net/~user/project/branch", + "https://launchpad.net/~user/project/branch", + nil, + }, +} + +func TestFindPublicRepo(t *testing.T) { + for _, test := range FindPublicRepoTests { + client := http.DefaultClient + if test.transport != nil { + client = &http.Client{Transport: test.transport} + } + repo, err := findPublicRepo(test.pkg) + if err != nil { + t.Errorf("findPublicRepo(%s): error: %v", test.pkg, err) + continue + } + if repo == nil { + t.Errorf("%s: got nil match", test.pkg) + continue + } + url, root, vcs, err := repo.Repo(client) + if err != nil { + t.Errorf("%s: repo.Repo error: %v", test.pkg, err) + continue + } + if v := vcsMap[test.vcs]; vcs != v { + t.Errorf("%s: got vcs=%v, want %v", test.pkg, vcs, v) + } + if root != test.root { + t.Errorf("%s: got root=%v, want %v", test.pkg, root, test.root) + } + if url != test.url { + t.Errorf("%s: got url=%v, want %v", test.pkg, url, test.url) + } + } +} + +type testTransport struct { + expectURL string + responseBody string +} + +func (t *testTransport) RoundTrip(req *http.Request) (*http.Response, error) { + if g, e := req.URL.String(), t.expectURL; g != e { + return nil, errors.New("want " + e) + } + body := ioutil.NopCloser(bytes.NewBufferString(t.responseBody)) + return &http.Response{ + StatusCode: http.StatusOK, + Body: body, + }, nil +} diff --git a/src/cmd/goinstall/main.go b/src/cmd/goinstall/main.go index 478266357..bbc4b6b76 100644 --- a/src/cmd/goinstall/main.go +++ b/src/cmd/goinstall/main.go @@ -6,13 +6,14 @@ package main import ( "bytes" - "exec" + "errors" "flag" "fmt" "go/build" "go/token" "io/ioutil" "os" + "os/exec" "path/filepath" // use for file system paths "regexp" "runtime" @@ -31,7 +32,6 @@ const logfile = "goinstall.log" var ( fset = token.NewFileSet() argv0 = os.Args[0] - errors = false parents = make(map[string]string) visit = make(map[string]status) installedPkgs = make(map[string]map[string]bool) @@ -40,10 +40,11 @@ var ( allpkg = flag.Bool("a", false, "install all previously installed packages") reportToDashboard = flag.Bool("dashboard", true, "report public packages at "+dashboardURL) update = flag.Bool("u", false, "update already-downloaded packages") + doGofix = flag.Bool("fix", false, "gofix each package before building it") doInstall = flag.Bool("install", true, "build and install") clean = flag.Bool("clean", false, "clean the package directory before installing") nuke = flag.Bool("nuke", false, "clean the package directory and target before installing") - useMake = flag.Bool("make", true, "use make to build and install") + useMake = flag.Bool("make", true, "use make to build and install (obsolete, always true)") verbose = flag.Bool("v", false, "verbose") ) @@ -54,6 +55,51 @@ const ( done ) +type PackageError struct { + pkg string + err error +} + +func (e *PackageError) Error() string { + return fmt.Sprintf("%s: %v", e.pkg, e.err) +} + +type DownloadError struct { + pkg string + goroot bool + err error +} + +func (e *DownloadError) Error() string { + s := fmt.Sprintf("%s: download failed: %v", e.pkg, e.err) + if e.goroot && os.Getenv("GOPATH") == "" { + s += " ($GOPATH is not set)" + } + return s +} + +type DependencyError PackageError + +func (e *DependencyError) Error() string { + return fmt.Sprintf("%s: depends on failing packages:\n\t%v", e.pkg, e.err) +} + +type BuildError PackageError + +func (e *BuildError) Error() string { + return fmt.Sprintf("%s: build failed: %v", e.pkg, e.err) +} + +type RunError struct { + cmd, dir string + out []byte + err error +} + +func (e *RunError) Error() string { + return fmt.Sprintf("%v\ncd %q && %q\n%s", e.err, e.dir, e.cmd, e.out) +} + func logf(format string, args ...interface{}) { format = "%s: " + format args = append([]interface{}{argv0}, args...) @@ -66,18 +112,6 @@ func printf(format string, args ...interface{}) { } } -func errorf(format string, args ...interface{}) { - errors = true - logf(format, args...) -} - -func terrorf(tree *build.Tree, format string, args ...interface{}) { - if tree != nil && tree.Goroot && os.Getenv("GOPATH") == "" { - format = strings.TrimRight(format, "\n") + " ($GOPATH not set)\n" - } - errorf(format, args...) -} - func main() { flag.Usage = usage flag.Parse() @@ -111,15 +145,14 @@ func main() { if len(args) == 0 { usage() } + errs := false for _, path := range args { - if s := schemeRe.FindString(path); s != "" { - errorf("%q used in import path, try %q\n", s, path[len(s):]) - continue + if err := install(path, ""); err != nil { + errs = true + fmt.Fprintln(os.Stderr, err) } - - install(path, "") } - if errors { + if errs { os.Exit(1) } } @@ -136,7 +169,7 @@ func printDeps(pkg string) { } // readPackageList reads the list of installed packages from the -// goinstall.log files in GOROOT and the GOPATHs and initalizes +// goinstall.log files in GOROOT and the GOPATHs and initializes // the installedPkgs variable. func readPackageList() { for _, t := range build.Path { @@ -163,7 +196,7 @@ func logPackage(pkg string, tree *build.Tree) (logged bool) { name := filepath.Join(tree.Path, logfile) fout, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) if err != nil { - terrorf(tree, "package log: %s\n", err) + printf("package log: %s\n", err) return false } fmt.Fprintf(fout, "%s\n", pkg) @@ -172,11 +205,19 @@ func logPackage(pkg string, tree *build.Tree) (logged bool) { } // install installs the package named by path, which is needed by parent. -func install(pkg, parent string) { +func install(pkg, parent string) error { + // Basic validation of import path string. + if s := schemeRe.FindString(pkg); s != "" { + return fmt.Errorf("%q used in import path, try %q\n", s, pkg[len(s):]) + } + if strings.HasSuffix(pkg, "/") { + return fmt.Errorf("%q should not have trailing '/'\n", pkg) + } + // Make sure we're not already trying to install pkg. switch visit[pkg] { case done: - return + return nil case visiting: fmt.Fprintf(os.Stderr, "%s: package dependency cycle\n", argv0) printDeps(parent) @@ -189,24 +230,17 @@ func install(pkg, parent string) { visit[pkg] = done }() - // Don't allow trailing '/' - if strings.HasSuffix(pkg, "/") { - errorf("%s should not have trailing '/'\n", pkg) - return - } - // Check whether package is local or remote. // If remote, download or update it. tree, pkg, err := build.FindTree(pkg) // Don't build the standard library. if err == nil && tree.Goroot && isStandardPath(pkg) { if parent == "" { - errorf("%s: can not goinstall the standard library\n", pkg) - } else { - printf("%s: skipping standard library\n", pkg) + return &PackageError{pkg, errors.New("cannot goinstall the standard library")} } - return + return nil } + // Download remote packages if not found or forced with -u flag. remote, public := isRemote(pkg), false if remote { @@ -214,85 +248,118 @@ func install(pkg, parent string) { // Download remote package. printf("%s: download\n", pkg) public, err = download(pkg, tree.SrcDir()) + if err != nil { + // only suggest -fix if the bad import was not on the command line + if e, ok := err.(*errOldGoogleRepo); ok && parent != "" { + err = fmt.Errorf("%v\nRun goinstall with -fix to gofix the code.", e) + } + return &DownloadError{pkg, tree.Goroot, err} + } } else { // Test if this is a public repository // (for reporting to dashboard). - m, _ := findPublicRepo(pkg) - public = m != nil + repo, e := findPublicRepo(pkg) + public = repo != nil + err = e } } if err != nil { - terrorf(tree, "%s: %v\n", pkg, err) - return + return &PackageError{pkg, err} } - dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) - // Install prerequisites. + // Install the package and its dependencies. + if err := installPackage(pkg, parent, tree, false); err != nil { + return err + } + + if remote { + // mark package as installed in goinstall.log + logged := logPackage(pkg, tree) + + // report installation to the dashboard if this is the first + // install from a public repository. + if logged && public { + maybeReportToDashboard(pkg) + } + } + + return nil +} + +// installPackage installs the specified package and its dependencies. +func installPackage(pkg, parent string, tree *build.Tree, retry bool) (installErr error) { + printf("%s: install\n", pkg) + + // Read package information. + dir := filepath.Join(tree.SrcDir(), filepath.FromSlash(pkg)) dirInfo, err := build.ScanDir(dir) if err != nil { - terrorf(tree, "%s: %v\n", pkg, err) - return + return &PackageError{pkg, err} } + // We reserve package main to identify commands. if parent != "" && dirInfo.Package == "main" { - terrorf(tree, "%s: found only package main in %s; cannot import", pkg, dir) - return - } - for _, p := range dirInfo.Imports { - if p != "C" { - install(p, pkg) - } - } - if errors { - return + return &PackageError{pkg, fmt.Errorf("found only package main in %s; cannot import", dir)} } - // Install this package. - if *useMake { - err := domake(dir, pkg, tree, dirInfo.IsCommand()) - if err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) + // Run gofix if we fail to build and -fix is set. + defer func() { + if retry || installErr == nil || !*doGofix { return } - } else { - script, err := build.Build(tree, pkg, dirInfo) - if err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) - return + if e, ok := (installErr).(*DependencyError); ok { + // If this package failed to build due to a + // DependencyError, only attempt to gofix it if its + // dependency failed for some reason other than a + // DependencyError or BuildError. + // (If a dep or one of its deps doesn't build there's + // no way that gofixing this package can help.) + switch e.err.(type) { + case *DependencyError: + return + case *BuildError: + return + } } - if *nuke { - printf("%s: nuke\n", pkg) - script.Nuke() - } else if *clean { - printf("%s: clean\n", pkg) - script.Clean() + gofix(pkg, dir, dirInfo) + installErr = installPackage(pkg, parent, tree, true) // retry + }() + + // Install prerequisites. + for _, p := range dirInfo.Imports { + if p == "C" { + continue } - if *doInstall { - if script.Stale() { - printf("%s: install\n", pkg) - if err := script.Run(); err != nil { - terrorf(tree, "%s: install: %v\n", pkg, err) - return - } - } else { - printf("%s: up-to-date\n", pkg) - } + if err := install(p, pkg); err != nil { + return &DependencyError{pkg, err} } } - if remote { - // mark package as installed in goinstall.log - logged := logPackage(pkg, tree) + // Install this package. + err = domake(dir, pkg, tree, dirInfo.IsCommand()) + if err != nil { + return &BuildError{pkg, err} + } + return nil +} - // report installation to the dashboard if this is the first - // install from a public repository. - if logged && public { - maybeReportToDashboard(pkg) - } +// gofix runs gofix against the GoFiles and CgoFiles of dirInfo in dir. +func gofix(pkg, dir string, dirInfo *build.DirInfo) { + printf("%s: gofix\n", pkg) + files := append([]string{}, dirInfo.GoFiles...) + files = append(files, dirInfo.CgoFiles...) + for i, file := range files { + files[i] = filepath.Join(dir, file) + } + cmd := exec.Command("gofix", files...) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + logf("%s: gofix: %v", pkg, err) } } -// Is this a standard package path? strings container/vector etc. +// Is this a standard package path? strings container/list etc. // Assume that if the first element has a dot, it's a domain name // and is not the standard package path. func isStandardPath(s string) bool { @@ -302,34 +369,32 @@ func isStandardPath(s string) bool { } // run runs the command cmd in directory dir with standard input stdin. -// If the command fails, run prints the command and output on standard error -// in addition to returning a non-nil os.Error. -func run(dir string, stdin []byte, cmd ...string) os.Error { - return genRun(dir, stdin, cmd, false) -} - -// quietRun is like run but prints nothing on failure unless -v is used. -func quietRun(dir string, stdin []byte, cmd ...string) os.Error { - return genRun(dir, stdin, cmd, true) -} - -// genRun implements run and quietRun. -func genRun(dir string, stdin []byte, arg []string, quiet bool) os.Error { +// If verbose is set and the command fails it prints the output to stderr. +func run(dir string, stdin []byte, arg ...string) error { cmd := exec.Command(arg[0], arg[1:]...) cmd.Stdin = bytes.NewBuffer(stdin) cmd.Dir = dir - printf("%s: %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " ")) - out, err := cmd.CombinedOutput() - if err != nil { - if !quiet || *verbose { - if dir != "" { - dir = "cd " + dir + "; " - } - fmt.Fprintf(os.Stderr, "%s: === %s%s\n", cmd.Path, dir, strings.Join(cmd.Args, " ")) - os.Stderr.Write(out) - fmt.Fprintf(os.Stderr, "--- %s\n", err) + printf("cd %s && %s %s\n", dir, cmd.Path, strings.Join(arg[1:], " ")) + if out, err := cmd.CombinedOutput(); err != nil { + if *verbose { + fmt.Fprintf(os.Stderr, "%v\n%s\n", err, out) } - return os.NewError("running " + arg[0] + ": " + err.String()) + return &RunError{strings.Join(arg, " "), dir, out, err} } return nil } + +// isRemote returns true if the first part of the package name looks like a +// hostname - i.e. contains at least one '.' and the last part is at least 2 +// characters. +func isRemote(pkg string) bool { + parts := strings.SplitN(pkg, "/", 2) + if len(parts) != 2 { + return false + } + parts = strings.Split(parts[0], ".") + if len(parts) < 2 || len(parts[len(parts)-1]) < 2 { + return false + } + return true +} diff --git a/src/cmd/goinstall/make.go b/src/cmd/goinstall/make.go index 7f41a913f..1e40d6ea3 100644 --- a/src/cmd/goinstall/make.go +++ b/src/cmd/goinstall/make.go @@ -8,17 +8,17 @@ package main import ( "bytes" + "errors" "go/build" - "os" "path" // use for import paths "strings" - "template" + "text/template" ) // domake builds the package in dir. // domake generates a standard Makefile and passes it // to make on standard input. -func domake(dir, pkg string, tree *build.Tree, isCmd bool) (err os.Error) { +func domake(dir, pkg string, tree *build.Tree, isCmd bool) (err error) { makefile, err := makeMakefile(dir, pkg, tree, isCmd) if err != nil { return err @@ -29,16 +29,21 @@ func domake(dir, pkg string, tree *build.Tree, isCmd bool) (err os.Error) { } else if *clean { cmd = append(cmd, "clean") } - cmd = append(cmd, "install") + if *doInstall { + cmd = append(cmd, "install") + } + if len(cmd) <= 3 { // nothing to do + return nil + } return run(dir, makefile, cmd...) } // makeMakefile computes the standard Makefile for the directory dir // installing as package pkg. It includes all *.go files in the directory // except those in package main and those ending in _test.go. -func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Error) { +func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, error) { if !safeName(pkg) { - return nil, os.NewError("unsafe name: " + pkg) + return nil, errors.New("unsafe name: " + pkg) } targ := pkg targDir := tree.PkgDir() @@ -56,7 +61,7 @@ func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Err isCgo := make(map[string]bool, len(cgoFiles)) for _, file := range cgoFiles { if !safeName(file) { - return nil, os.NewError("bad name: " + file) + return nil, errors.New("bad name: " + file) } isCgo[file] = true } @@ -64,7 +69,7 @@ func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Err goFiles := make([]string, 0, len(dirInfo.GoFiles)) for _, file := range dirInfo.GoFiles { if !safeName(file) { - return nil, os.NewError("unsafe name: " + file) + return nil, errors.New("unsafe name: " + file) } if !isCgo[file] { goFiles = append(goFiles, file) @@ -75,7 +80,7 @@ func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Err cgoOFiles := make([]string, 0, len(dirInfo.CFiles)) for _, file := range dirInfo.CFiles { if !safeName(file) { - return nil, os.NewError("unsafe name: " + file) + return nil, errors.New("unsafe name: " + file) } // When cgo is in use, C files are compiled with gcc, // otherwise they're compiled with gc. @@ -88,7 +93,7 @@ func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Err for _, file := range dirInfo.SFiles { if !safeName(file) { - return nil, os.NewError("unsafe name: " + file) + return nil, errors.New("unsafe name: " + file) } oFiles = append(oFiles, file[:len(file)-2]+".$O") } @@ -109,7 +114,7 @@ func makeMakefile(dir, pkg string, tree *build.Tree, isCmd bool) ([]byte, os.Err return buf.Bytes(), nil } -var safeBytes = []byte("+-./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz") +var safeBytes = []byte("+-~./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz") func safeName(s string) bool { if s == "" { @@ -118,6 +123,9 @@ func safeName(s string) bool { if strings.Contains(s, "..") { return false } + if s[0] == '~' { + return false + } for i := 0; i < len(s); i++ { if c := s[i]; c < 0x80 && bytes.IndexByte(safeBytes, c) < 0 { return false diff --git a/src/cmd/gopack/ar.c b/src/cmd/gopack/ar.c index 96f36605f..40c99f6c7 100644 --- a/src/cmd/gopack/ar.c +++ b/src/cmd/gopack/ar.c @@ -37,7 +37,6 @@ #define rcmd your_rcmd #include <u.h> -#include <time.h> #include <libc.h> #include <bio.h> #include <mach.h> @@ -612,12 +611,49 @@ qcmd(char *arname, int count, char **files) } /* + * does the object header line p match the last one we saw? + * update *lastp if it gets more specific. + */ +int +matchhdr(char *p, char **lastp) +{ + int n; + char *last; + + // no information? + last = *lastp; + if(last == nil) { + *lastp = strdup(p); + return 1; + } + + // identical match? + if(strcmp(last, p) == 0) + return 1; + + // last has extra fields + n = strlen(p); + if(n < strlen(last) && last[n] == ' ') + return 1; + + // p has extra fields - save in last + n = strlen(last); + if(n < strlen(p) && p[n] == ' ') { + free(last); + *lastp = strdup(p); + return 1; + } + + return 0; +} + +/* * extract the symbol references from an object file */ void scanobj(Biobuf *b, Arfile *ap, long size) { - int obj; + int obj, goobject; vlong offset, offset1; Dir *d; static int lastobj = -1; @@ -658,9 +694,19 @@ scanobj(Biobuf *b, Arfile *ap, long size) return; } + goobject = 1; offset1 = Boffset(b); Bseek(b, offset, 0); p = Brdstr(b, '\n', 1); + + // After the go object header comes the Go metadata, + // followed by ! on a line by itself. If this is not a Go object, + // the ! comes immediately. Catch that so we can avoid + // the call to scanpkg below, since scanpkg assumes that the + // Go metadata is present. + if(Bgetc(b) == '!') + goobject = 0; + Bseek(b, offset1, 0); if(p == nil || strncmp(p, "go object ", 10) != 0) { fprint(2, "gopack: malformed object file %s\n", file); @@ -670,18 +716,23 @@ scanobj(Biobuf *b, Arfile *ap, long size) return; } - if ((lastobj >= 0 && obj != lastobj) || (objhdr != nil && strcmp(p, objhdr) != 0)) { - fprint(2, "gopack: inconsistent object file %s\n", file); + if (!matchhdr(p, &objhdr)) { + fprint(2, "gopack: inconsistent object file %s: [%s] vs [%s]\n", file, p, objhdr); errors++; allobj = 0; free(p); return; } + free(p); + + // Old check. Should be impossible since objhdrs match, but keep the check anyway. + if (lastobj >= 0 && obj != lastobj) { + fprint(2, "gopack: inconsistent object file %s\n", file); + errors++; + allobj = 0; + return; + } lastobj = obj; - if(objhdr == nil) - objhdr = p; - else - free(p); if (!readar(b, obj, offset+size, 0)) { fprint(2, "gopack: invalid symbol reference in file %s\n", file); @@ -692,7 +743,7 @@ scanobj(Biobuf *b, Arfile *ap, long size) } Bseek(b, offset, 0); objtraverse(objsym, ap); - if (gflag) { + if (gflag && goobject) { scanpkg(b, size); Bseek(b, offset, 0); } @@ -786,7 +837,6 @@ foundstart: goto bad; /* how big is it? */ - pkg = nil; first = 1; start = end = 0; for (n=0; n<size; n+=Blinelen(b)) { @@ -1050,7 +1100,7 @@ armove(Biobuf *b, Arfile *ap, Armember *bp) for (cp = strchr(bp->hdr.name, 0); /* blank pad on right */ cp < bp->hdr.name+sizeof(bp->hdr.name); cp++) *cp = ' '; - sprint(bp->hdr.date, "%-12ld", 0); // was d->mtime but removed for idempotent builds + sprint(bp->hdr.date, "%-12ld", 0L); // was d->mtime but removed for idempotent builds sprint(bp->hdr.uid, "%-6d", 0); sprint(bp->hdr.gid, "%-6d", 0); sprint(bp->hdr.mode, "%-8lo", d->mode); @@ -1184,7 +1234,7 @@ rl(int fd) len = symdefsize; if(len&01) len++; - sprint(a.date, "%-12ld", 0); // time(0) + sprint(a.date, "%-12ld", 0L); // time(0) sprint(a.uid, "%-6d", 0); sprint(a.gid, "%-6d", 0); sprint(a.mode, "%-8lo", 0644L); @@ -1221,7 +1271,7 @@ rl(int fd) if (gflag) { len = pkgdefsize; - sprint(a.date, "%-12ld", 0); // time(0) + sprint(a.date, "%-12ld", 0L); // time(0) sprint(a.uid, "%-6d", 0); sprint(a.gid, "%-6d", 0); sprint(a.mode, "%-8lo", 0644L); @@ -1368,15 +1418,12 @@ void longt(Armember *bp) { char *cp; - time_t date; pmode(strtoul(bp->hdr.mode, 0, 8)); Bprint(&bout, "%3ld/%1ld", strtol(bp->hdr.uid, 0, 0), strtol(bp->hdr.gid, 0, 0)); Bprint(&bout, "%7ld", bp->size); - date = bp->date; - cp = ctime(&date); - /* using unix ctime, not plan 9 time, so cp+20 for year, not cp+24 */ - Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+20); + cp = ctime(bp->date); + Bprint(&bout, " %-12.12s %-4.4s ", cp+4, cp+24); } int m1[] = { 1, ROWN, 'r', '-' }; diff --git a/src/cmd/gotest/doc.go b/src/cmd/gotest/doc.go index 5be06f817..8d729f025 100644 --- a/src/cmd/gotest/doc.go +++ b/src/cmd/gotest/doc.go @@ -26,6 +26,26 @@ signature, func BenchmarkXXX(b *testing.B) { ... } +Example functions may also be written. They are similar to test functions but, +instead of using *testing.T to report success or failure, their output to +os.Stdout and os.Stderr is compared against their doc comment. + + // The output of this example function. + func ExampleXXX() { + fmt.Println("The output of this example function.") + } + +The following naming conventions are used to declare examples for a function F, +a type T and method M on type T: + func ExampleF() { ... } and func ExampleF_suffix() { ... } + func ExampleT() { ... } and func ExampleT_suffix() { ... } + func ExampleT_M() { ... } and func ExampleT_M_suffix() { ... } + +Multiple example functions may be provided by appending a distinct suffix +to the name. The suffix must start with a lowercase letter. + +Example functions without doc comments are compiled but not executed. + See the documentation of the testing package for more information. By default, gotest needs no arguments. It compiles all the .go files @@ -54,7 +74,8 @@ Usage: 6.out [-test.v] [-test.run pattern] [-test.bench pattern] \ [-test.cpuprofile=cpu.out] \ [-test.memprofile=mem.out] [-test.memprofilerate=1] \ - [-test.timeout=10] [-test.short] \ + [-test.parallel=$GOMAXPROCS] \ + [-test.timeout=10s] [-test.short] \ [-test.benchtime=3] [-test.cpu=1,2,3,4] The -test.v flag causes the tests to be logged as they run. The @@ -86,12 +107,17 @@ collection. Use -test.run or -test.bench to limit profiling to a particular test or benchmark. +The -test.parallel flag allows parallel execution of Test functions +that call test.Parallel. The value of the flag is the maximum +number of tests to run simultaneously; by default, it is set to the +value of GOMAXPROCS. + The -test.short flag tells long-running tests to shorten their run time. It is off by default but set by all.bash so installations of the Go tree can do a sanity check but not spend time running exhaustive tests. -The -test.timeout flag sets a timeout for the test in seconds. If the +The -test.timeout flag sets a timeout for the test. If the test runs for longer than that, it will panic, dumping a stack trace of all existing goroutines. diff --git a/src/cmd/gotest/flag.go b/src/cmd/gotest/flag.go index c3a28f9a3..b0b0cae5c 100644 --- a/src/cmd/gotest/flag.go +++ b/src/cmd/gotest/flag.go @@ -28,6 +28,7 @@ var usageMessage = `Usage of %s: -cpuprofile="": passes -test.cpuprofile to test -memprofile="": passes -test.memprofile to test -memprofilerate=0: passes -test.memprofilerate to test + -parallel=0: passes -test.parallel to test -run="": passes -test.run to test -short=false: passes -test.short to test -timeout=0: passes -test.timeout to test @@ -52,21 +53,22 @@ type flagSpec struct { // flagDefn is the set of flags we process. var flagDefn = []*flagSpec{ // gotest-local. - &flagSpec{name: "c", isBool: true}, - &flagSpec{name: "file", multiOK: true}, - &flagSpec{name: "x", isBool: true}, + {name: "c", isBool: true}, + {name: "file", multiOK: true}, + {name: "x", isBool: true}, // passed to 6.out, adding a "test." prefix to the name if necessary: -v becomes -test.v. - &flagSpec{name: "bench", passToTest: true}, - &flagSpec{name: "benchtime", passToTest: true}, - &flagSpec{name: "cpu", passToTest: true}, - &flagSpec{name: "cpuprofile", passToTest: true}, - &flagSpec{name: "memprofile", passToTest: true}, - &flagSpec{name: "memprofilerate", passToTest: true}, - &flagSpec{name: "run", passToTest: true}, - &flagSpec{name: "short", isBool: true, passToTest: true}, - &flagSpec{name: "timeout", passToTest: true}, - &flagSpec{name: "v", isBool: true, passToTest: true}, + {name: "bench", passToTest: true}, + {name: "benchtime", passToTest: true}, + {name: "cpu", passToTest: true}, + {name: "cpuprofile", passToTest: true}, + {name: "memprofile", passToTest: true}, + {name: "memprofilerate", passToTest: true}, + {name: "parallel", passToTest: true}, + {name: "run", passToTest: true}, + {name: "short", isBool: true, passToTest: true}, + {name: "timeout", passToTest: true}, + {name: "v", isBool: true, passToTest: true}, } // flags processes the command line, grabbing -x and -c, rewriting known flags @@ -105,6 +107,10 @@ func flag(i int) (f *flagSpec, value string, extra bool) { if strings.HasPrefix(arg, "--") { // reduce two minuses to one arg = arg[1:] } + switch arg { + case "-?", "-h", "-help": + usage() + } if arg == "" || arg[0] != '-' { return } @@ -150,7 +156,7 @@ func flag(i int) (f *flagSpec, value string, extra bool) { // setBoolFlag sets the addressed boolean to the value. func setBoolFlag(flag *bool, value string) { - x, err := strconv.Atob(value) + x, err := strconv.ParseBool(value) if err != nil { fmt.Fprintf(os.Stderr, "gotest: illegal bool flag value %s\n", value) usage() diff --git a/src/cmd/gotest/gotest.go b/src/cmd/gotest/gotest.go index 88c746c1b..6697aeb2a 100644 --- a/src/cmd/gotest/gotest.go +++ b/src/cmd/gotest/gotest.go @@ -6,7 +6,6 @@ package main import ( "bufio" - "exec" "fmt" "go/ast" "go/build" @@ -14,12 +13,13 @@ import ( "go/token" "io/ioutil" "os" + "os/exec" "runtime" "sort" "strings" "time" "unicode" - "utf8" + "unicode/utf8" ) // Environment for commands. @@ -55,10 +55,10 @@ var ( // elapsed returns the number of seconds since gotest started. func elapsed() float64 { - return float64(time.Nanoseconds()-start) / 1e9 + return time.Now().Sub(start).Seconds() } -var start = time.Nanoseconds() +var start = time.Now() // File represents a file that contains tests. type File struct { @@ -68,6 +68,12 @@ type File struct { astFile *ast.File tests []string // The names of the TestXXXs. benchmarks []string // The names of the BenchmarkXXXs. + examples []example +} + +type example struct { + name string // The name of the example function (ExampleXXX). + output string // The expected output (stdout/stderr) of the function. } func main() { @@ -107,13 +113,6 @@ func Fatalf(s string, args ...interface{}) { os.Exit(2) } -// theChar is the map from architecture to object character. -var theChar = map[string]string{ - "arm": "5", - "amd64": "6", - "386": "8", -} - // addEnv adds a name=value pair to the environment passed to subcommands. // If the item is already in the environment, addEnv replaces the value. func addEnv(name, value string) { @@ -131,14 +130,12 @@ func setEnvironment() { // Basic environment. GOROOT = runtime.GOROOT() addEnv("GOROOT", GOROOT) - GOARCH = os.Getenv("GOARCH") - if GOARCH == "" { - GOARCH = runtime.GOARCH - } + GOARCH = build.DefaultContext.GOARCH addEnv("GOARCH", GOARCH) - O = theChar[GOARCH] - if O == "" { - Fatalf("unknown architecture %s", GOARCH) + var err error + O, err = build.ArchChar(GOARCH) + if err != nil { + Fatalf("unknown architecture: %s", err) } // Commands and their flags. @@ -146,8 +143,12 @@ func setEnvironment() { if gc == "" { gc = O + "g" } - XGC = []string{gc, "-I", "_test", "-o", "_xtest_." + O} - GC = []string{gc, "-I", "_test", "_testmain.go"} + var gcflags []string + if gf := strings.TrimSpace(os.Getenv("GCFLAGS")); gf != "" { + gcflags = strings.Fields(gf) + } + XGC = append([]string{gc, "-I", "_test", "-o", "_xtest_." + O}, gcflags...) + GC = append(append([]string{gc, "-I", "_test"}, gcflags...), "_testmain.go") gl := os.Getenv("GL") if gl == "" { gl = O + "l" @@ -190,7 +191,7 @@ func parseFiles() { fileSet := token.NewFileSet() for _, f := range files { // Report declaration errors so we can abort if the files are incorrect Go. - file, err := parser.ParseFile(fileSet, f.name, nil, parser.DeclarationErrors) + file, err := parser.ParseFile(fileSet, f.name, nil, parser.DeclarationErrors|parser.ParseComments) if err != nil { Fatalf("parse error: %s", err) } @@ -219,10 +220,20 @@ func getTestNames() { f.tests = append(f.tests, name) } else if isTest(name, "Benchmark") { f.benchmarks = append(f.benchmarks, name) + } else if isTest(name, "Example") { + output := n.Doc.Text() + if output == "" { + // Don't run examples with no output. + continue + } + f.examples = append(f.examples, example{ + name: name, + output: output, + }) } // TODO: worth checking the signature? Probably not. } - if strings.HasSuffix(f.pkg, "_test") { + if isOutsideTest(f.pkg) { outsideFileNames = append(outsideFileNames, f.name) } else { insideFileNames = append(insideFileNames, f.name) @@ -272,10 +283,10 @@ func runTestWithArgs(binary string) { func doRun(argv []string, returnStdout bool) string { if xFlag { fmt.Printf("gotest %.2fs: %s\n", elapsed(), strings.Join(argv, " ")) - t := -time.Nanoseconds() + start := time.Now() defer func() { - t += time.Nanoseconds() - fmt.Printf(" [+%.2fs]\n", float64(t)/1e9) + t := time.Now().Sub(start) + fmt.Printf(" [+%.2fs]\n", t.Seconds()) }() } command := argv[0] @@ -291,7 +302,7 @@ func doRun(argv []string, returnStdout bool) string { command = "bash" argv = []string{"bash", "-c", cmd} } - var err os.Error + var err error argv[0], err = exec.LookPath(argv[0]) if err != nil { Fatalf("can't find %s: %s", command, err) @@ -356,51 +367,62 @@ func writeTestmainGo() { insideTests := false for _, f := range files { //println(f.name, f.pkg) - if len(f.tests) == 0 && len(f.benchmarks) == 0 { + if len(f.tests) == 0 && len(f.benchmarks) == 0 && len(f.examples) == 0 { continue } - if strings.HasSuffix(f.pkg, "_test") { + if isOutsideTest(f.pkg) { outsideTests = true } else { insideTests = true } } + // Rename the imports for the system under test, + // in case the tested package has the same name + // as any of the other imports, variables or methods. if insideTests { switch importPath { case "testing": case "main": // Import path main is reserved, so import with // explicit reference to ./_test/main instead. - // Also, the file we are writing defines a function named main, - // so rename this import to __main__ to avoid name conflict. - fmt.Fprintf(b, "import __main__ %q\n", "./_test/main") + fmt.Fprintf(b, "import target %q\n", "./_test/main") default: - fmt.Fprintf(b, "import %q\n", importPath) + fmt.Fprintf(b, "import target %q\n", importPath) } } if outsideTests { - fmt.Fprintf(b, "import %q\n", "./_xtest_") + // It is possible to have both inside and outside tests + // at the same time, so a different import name is needed. + fmt.Fprintf(b, "import target_test %q\n", "./_xtest_") } fmt.Fprintf(b, "import %q\n", "testing") - fmt.Fprintf(b, "import __os__ %q\n", "os") // rename in case tested package is called os - fmt.Fprintf(b, "import __regexp__ %q\n", "regexp") // rename in case tested package is called regexp - fmt.Fprintln(b) // for gofmt + fmt.Fprintf(b, "import %q\n", "regexp") + fmt.Fprintln(b) // for gofmt // Tests. - fmt.Fprintln(b, "var tests = []testing.InternalTest{") + fmt.Fprintf(b, "var tests = []testing.InternalTest{\n") for _, f := range files { for _, t := range f.tests { - fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, t, notMain(f.pkg), t) + fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, t, renamedPackage(f.pkg), t) } } fmt.Fprintln(b, "}") fmt.Fprintln(b) // Benchmarks. - fmt.Fprintf(b, "var benchmarks = []testing.InternalBenchmark{") + fmt.Fprintf(b, "var benchmarks = []testing.InternalBenchmark{\n") for _, f := range files { for _, bm := range f.benchmarks { - fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, bm, notMain(f.pkg), bm) + fmt.Fprintf(b, "\t{\"%s.%s\", %s.%s},\n", f.pkg, bm, renamedPackage(f.pkg), bm) + } + } + fmt.Fprintln(b, "}") + + // Examples. + fmt.Fprintf(b, "var examples = []testing.InternalExample{") + for _, f := range files { + for _, eg := range f.examples { + fmt.Fprintf(b, "\t{%q, %s.%s, %q},\n", eg.name, renamedPackage(f.pkg), eg.name, eg.output) } } fmt.Fprintln(b, "}") @@ -409,23 +431,27 @@ func writeTestmainGo() { fmt.Fprintln(b, testBody) } -// notMain returns the package, renaming as appropriate if it's "main". -func notMain(pkg string) string { - if pkg == "main" { - return "__main__" +// renamedPackage returns the name under which the test package was imported. +func renamedPackage(pkg string) string { + if isOutsideTest(pkg) { + return "target_test" } - return pkg + return "target" +} + +func isOutsideTest(pkg string) bool { + return strings.HasSuffix(pkg, "_test") } // testBody is just copied to the output. It's the code that runs the tests. var testBody = ` var matchPat string -var matchRe *__regexp__.Regexp +var matchRe *regexp.Regexp -func matchString(pat, str string) (result bool, err __os__.Error) { +func matchString(pat, str string) (result bool, err error) { if matchRe == nil || matchPat != pat { matchPat = pat - matchRe, err = __regexp__.Compile(matchPat) + matchRe, err = regexp.Compile(matchPat) if err != nil { return } @@ -434,5 +460,5 @@ func matchString(pat, str string) (result bool, err __os__.Error) { } func main() { - testing.Main(matchString, tests, benchmarks) + testing.Main(matchString, tests, benchmarks, examples) }` diff --git a/src/cmd/gotry/Makefile b/src/cmd/gotry/Makefile deleted file mode 100644 index 6a32bbf2d..000000000 --- a/src/cmd/gotry/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright 2010 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include ../../Make.inc - -TARG=install - -clean: - @true - -install: install-gotry - -install-%: % - ! test -f "$(GOBIN)"/$* || chmod u+w "$(GOBIN)"/$* - sed 's`@@GOROOT@@`$(GOROOT_FINAL)`' $* >"$(GOBIN)"/$* - chmod +x "$(GOBIN)"/$* - diff --git a/src/cmd/gotry/gotry b/src/cmd/gotry/gotry deleted file mode 100755 index c81b6c7d0..000000000 --- a/src/cmd/gotry/gotry +++ /dev/null @@ -1,168 +0,0 @@ -#!/usr/bin/env bash -# Copyright 2010 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -# Using all the non-test *.go files in the named directory, write -# out a file /tmp/$USER.try.go to evaluate the expressions on the -# command line, perhaps to discover a function or method that -# gives the desired results. See usage message. -# Compile the program and run it. - -# Makes egrep,grep work better in general if we put them -# in ordinary C mode instead of what the current language is. -unset LANG -export LC_ALL=C -export LC_CTYPE=C - -export GOROOT=${GOROOT:-"@@GOROOT@@"} -eval $(gomake -j1 --no-print-directory -f "$GOROOT"/src/Make.inc go-env) -if [ -z "$O" ]; then - echo 'missing $O - maybe no Make.$GOARCH?' 1>&2 - exit 2 -fi - -# Allow overrides -GC="${_GC:-$GC} -I _test" -GL="${GL:-$LD} -L _test" -AS="$AS" -CC="$CC" -LD="$LD" -export GC GL O AS CC LD - -# Macros for tab and quotes for easier readability. -T=' ' -BQ='`' -SQ="'" -DQ='"' -SD="$SQ$DQ" -DS="$DQ$SQ" - -usage="usage: gotry [packagedirectory] expression ... -Given one expression, gotry attempts to evaluate that expression. -Given multiple expressions, gotry treats them as a list of arguments -and result values and attempts to find a function in the package -that, given the first few expressions as arguments, evaluates to -the remaining expressions as results. If the first expression has -methods, it will also search for applicable methods. - -If there are multiple expressions, a package directory must be -specified. If there is a package argument, the expressions are -evaluated in an environment that includes - import . ${DQ}packagedirectory${DQ} - -Examples: - gotry 3+4 - # evaluates to 7 - gotry strings ${SD}abc${DS} ${SD}c${DS} 7-5 - # finds strings.Index etc. - gotry regexp ${SQ}MustCompile(${DQ}^[0-9]+${DQ})${SQ} ${SD}12345${DS} true - # finds Regexp.MatchString - -" - -function fail() { - echo 2>&1 "$@" - exit 2 -} - -case $# in - 0) - fail "$usage" - ;; - *) - case "$1" in - -*help|-*'?'|'?') - fail "$usage" - esac - if test -d "$GOROOT/src/pkg/$1" - then - pkg=$(basename $1) - dir=$GOROOT/src/pkg/$1 - importdir=$1 - shift - case "$pkg" in - os|syscall) - fail "gotry: cannot try packages os or syscall; they are too dangerous" - esac - fi - ;; -esac - -spaces='[ ][ ]*' - -function getFunctions() { - if [ "$pkg" = "" ] - then - return - fi - for file in $dir/*.go - do - case $file in - *_test*) - continue - esac - grep "func$spaces[A-Z]" $file | # TODO: should be Unicode upper case - sed "s/func$spaces//;s/(.*//" - done | sort -u -} - -# Generate list of public functions. -functions=$(getFunctions) - -# Write file to compile -file="/tmp/$USER.try" -rm -f "$file.go" -( -cat <<'!' -package main - -import ( - "os" - "try" -! - -if [ "$pkg" != "" ] -then - echo "$T" . '"'$importdir'"' -fi - -cat <<! -) -func main() { - try.Main("$pkg", firstArg, functions, args) -} -var functions = map[string] interface{}{ -! - -for i in $functions -do - echo "$T"'"'$i'": '$i',' -done -echo "}" - -echo 'var args = []interface{}{' - -if [ $# = 1 ] -then - echo "${T}toSlice($1)", -else -for i - do - echo "$T$i", - done -fi -echo "}" - -cat <<! -var firstArg = $BQ$1$BQ -var _ os.Error -func toSlice(a ...interface{}) []interface{} { return a } -! - -)>"$file.go" - -$GC -o "$file.$O" "$file.go" && -$GL -o "$file" "$file.$O" && -"$file" "_$@" -rm -f "$file" "$file.go" "$file.$O" diff --git a/src/cmd/gotype/Makefile b/src/cmd/gotype/Makefile deleted file mode 100644 index 18171945d..000000000 --- a/src/cmd/gotype/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2011 The Go Authors. All rights reserved. -# Use of this source code is governed by a BSD-style -# license that can be found in the LICENSE file. - -include ../../Make.inc - -TARG=gotype -GOFILES=\ - gotype.go\ - -include ../../Make.cmd - -test: - gotest - -testshort: - gotest -test.short diff --git a/src/cmd/gotype/doc.go b/src/cmd/gotype/doc.go deleted file mode 100644 index 1aa0faa75..000000000 --- a/src/cmd/gotype/doc.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* -The gotype command does syntactic and semantic analysis of Go files -and packages similar to the analysis performed by the front-end of -a Go compiler. Errors are reported if the analysis fails; otherwise -gotype is quiet (unless -v is set). - -Without a list of paths, gotype processes the standard input, which must -be the source of a single package file. - -Given a list of file names, each file must be a source file belonging to -the same package unless the package name is explicitly specified with the --p flag. - -Given a directory name, gotype collects all .go files in the directory -and processes them as if they were provided as an explicit list of file -names. Each directory is processed independently. Files starting with . -or not ending in .go are ignored. - -Usage: - gotype [flags] [path ...] - -The flags are: - -e - Print all (including spurious) errors. - -p pkgName - Process only those files in package pkgName. - -r - Recursively process subdirectories. - -v - Verbose mode. - -Debugging flags: - -ast - Print AST (disables concurrent parsing). - -trace - Print parse trace (disables concurrent parsing). - - -Examples - -To check the files file.go, old.saved, and .ignored: - - gotype file.go old.saved .ignored - -To check all .go files belonging to package main in the current directory -and recursively in all subdirectories: - - gotype -p main -r . - -To verify the output of a pipe: - - echo "package foo" | gotype - -*/ -package documentation - -// BUG(gri): At the moment, only single-file scope analysis is performed. diff --git a/src/cmd/gotype/gotype.go b/src/cmd/gotype/gotype.go deleted file mode 100644 index e5e9417ff..000000000 --- a/src/cmd/gotype/gotype.go +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "fmt" - "go/ast" - "go/parser" - "go/scanner" - "go/token" - "go/types" - "io/ioutil" - "os" - "path/filepath" - "strings" -) - -var ( - // main operation modes - pkgName = flag.String("p", "", "process only those files in package pkgName") - recursive = flag.Bool("r", false, "recursively process subdirectories") - verbose = flag.Bool("v", false, "verbose mode") - allErrors = flag.Bool("e", false, "print all (including spurious) errors") - - // debugging support - printTrace = flag.Bool("trace", false, "print parse trace") - printAST = flag.Bool("ast", false, "print AST") -) - -var exitCode = 0 - -func usage() { - fmt.Fprintf(os.Stderr, "usage: gotype [flags] [path ...]\n") - flag.PrintDefaults() - os.Exit(2) -} - -func report(err os.Error) { - scanner.PrintError(os.Stderr, err) - exitCode = 2 -} - -// parse returns the AST for the Go source src. -// The filename is for error reporting only. -// The result is nil if there were errors or if -// the file does not belong to the -p package. -func parse(fset *token.FileSet, filename string, src []byte) *ast.File { - if *verbose { - fmt.Println(filename) - } - - // ignore files with different package name - if *pkgName != "" { - file, err := parser.ParseFile(fset, filename, src, parser.PackageClauseOnly) - if err != nil { - report(err) - return nil - } - if file.Name.Name != *pkgName { - if *verbose { - fmt.Printf("\tignored (package %s)\n", file.Name.Name) - } - return nil - } - } - - // parse entire file - mode := parser.DeclarationErrors - if *allErrors { - mode |= parser.SpuriousErrors - } - if *printTrace { - mode |= parser.Trace - } - file, err := parser.ParseFile(fset, filename, src, mode) - if err != nil { - report(err) - return nil - } - if *printAST { - ast.Print(fset, file) - } - - return file -} - -func parseStdin(fset *token.FileSet) (files map[string]*ast.File) { - files = make(map[string]*ast.File) - src, err := ioutil.ReadAll(os.Stdin) - if err != nil { - report(err) - return - } - const filename = "<standard input>" - if file := parse(fset, filename, src); file != nil { - files[filename] = file - } - return -} - -func parseFiles(fset *token.FileSet, filenames []string) (files map[string]*ast.File) { - files = make(map[string]*ast.File) - for _, filename := range filenames { - src, err := ioutil.ReadFile(filename) - if err != nil { - report(err) - continue - } - if file := parse(fset, filename, src); file != nil { - if files[filename] != nil { - report(os.NewError(fmt.Sprintf("%q: duplicate file", filename))) - continue - } - files[filename] = file - } - } - return -} - -func isGoFilename(filename string) bool { - // ignore non-Go files - return !strings.HasPrefix(filename, ".") && strings.HasSuffix(filename, ".go") -} - -func processDirectory(dirname string) { - f, err := os.Open(dirname) - if err != nil { - report(err) - return - } - filenames, err := f.Readdirnames(-1) - f.Close() - if err != nil { - report(err) - // continue since filenames may not be empty - } - for i, filename := range filenames { - filenames[i] = filepath.Join(dirname, filename) - } - processFiles(filenames, false) -} - -func processFiles(filenames []string, allFiles bool) { - i := 0 - for _, filename := range filenames { - switch info, err := os.Stat(filename); { - case err != nil: - report(err) - case info.IsRegular(): - if allFiles || isGoFilename(info.Name) { - filenames[i] = filename - i++ - } - case info.IsDirectory(): - if allFiles || *recursive { - processDirectory(filename) - } - } - } - fset := token.NewFileSet() - processPackage(fset, parseFiles(fset, filenames[0:i])) -} - -func processPackage(fset *token.FileSet, files map[string]*ast.File) { - // make a package (resolve all identifiers) - pkg, err := ast.NewPackage(fset, files, types.GcImporter, types.Universe) - if err != nil { - report(err) - return - } - _, err = types.Check(fset, pkg) - if err != nil { - report(err) - } -} - -func main() { - flag.Usage = usage - flag.Parse() - - if flag.NArg() == 0 { - fset := token.NewFileSet() - processPackage(fset, parseStdin(fset)) - } else { - processFiles(flag.Args(), true) - } - - os.Exit(exitCode) -} diff --git a/src/cmd/gotype/gotype_test.go b/src/cmd/gotype/gotype_test.go deleted file mode 100644 index ad0bc8903..000000000 --- a/src/cmd/gotype/gotype_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "path/filepath" - "runtime" - "testing" -) - -func runTest(t *testing.T, path, pkg string) { - exitCode = 0 - *pkgName = pkg - *recursive = false - - if pkg == "" { - processFiles([]string{path}, true) - } else { - processDirectory(path) - } - - if exitCode != 0 { - t.Errorf("processing %s failed: exitCode = %d", path, exitCode) - } -} - -var tests = []struct { - path string - pkg string -}{ - // individual files - {"testdata/test1.go", ""}, - - // directories - {filepath.Join(runtime.GOROOT(), "src/pkg/go/ast"), "ast"}, - {filepath.Join(runtime.GOROOT(), "src/pkg/go/doc"), "doc"}, - {filepath.Join(runtime.GOROOT(), "src/pkg/go/token"), "scanner"}, - {filepath.Join(runtime.GOROOT(), "src/pkg/go/scanner"), "scanner"}, - {filepath.Join(runtime.GOROOT(), "src/pkg/go/parser"), "parser"}, - {filepath.Join(runtime.GOROOT(), "src/pkg/go/types"), "types"}, -} - -func Test(t *testing.T) { - for _, test := range tests { - runTest(t, test.path, test.pkg) - } -} diff --git a/src/cmd/gotype/testdata/test1.go b/src/cmd/gotype/testdata/test1.go deleted file mode 100644 index a3298e6e5..000000000 --- a/src/cmd/gotype/testdata/test1.go +++ /dev/null @@ -1,23 +0,0 @@ -package p - -func _() { - // the scope of a local type declaration starts immediately after the type name - type T struct{ _ *T } -} - -func _(x interface{}) { - // the variable defined by a TypeSwitchGuard is declared in each TypeCaseClause - switch t := x.(type) { - case int: - _ = t - case float32: - _ = t - default: - _ = t - } - - // the variable defined by a TypeSwitchGuard must not conflict with other - // variables declared in the initial simple statement - switch t := 0; t := x.(type) { - } -} diff --git a/src/cmd/govet/Makefile b/src/cmd/govet/Makefile index f565b78f5..dae3ae51d 100644 --- a/src/cmd/govet/Makefile +++ b/src/cmd/govet/Makefile @@ -7,8 +7,11 @@ include ../../Make.inc TARG=govet GOFILES=\ govet.go\ + method.go\ + print.go\ + structtag.go\ include ../../Make.cmd test testshort: $(TARG) - ../../../test/errchk $(TARG) -printfuncs='Warn:1,Warnf:1' govet.go + ../../../test/errchk $(TARG) -printfuncs='Warn:1,Warnf:1' print.go diff --git a/src/cmd/govet/govet.go b/src/cmd/govet/govet.go index 9aa97e316..283f1613f 100644 --- a/src/cmd/govet/govet.go +++ b/src/cmd/govet/govet.go @@ -7,22 +7,20 @@ package main import ( + "bytes" "flag" "fmt" - "io" "go/ast" "go/parser" "go/token" + "io" "os" "path/filepath" - "reflect" "strconv" "strings" - "utf8" ) var verbose = flag.Bool("v", false, "verbose") -var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") var exitCode = 0 // setExit sets the value for os.Exit when it is called, later. It @@ -43,7 +41,9 @@ func Usage() { // File is a wrapper for the state of a file used in the parser. // The parse tree walkers are all methods of this type. type File struct { - file *token.File + fset *token.FileSet + file *ast.File + b bytes.Buffer // for use by methods } func main() { @@ -57,7 +57,7 @@ func main() { } skip := 0 if colon := strings.LastIndex(name, ":"); colon > 0 { - var err os.Error + var err error skip, err = strconv.Atoi(name[colon+1:]) if err != nil { errorf(`illegal format for "Func:N" argument %q; %s`, name, err) @@ -78,7 +78,7 @@ func main() { } else { for _, name := range flag.Args() { // Is it a directory? - if fi, err := os.Stat(name); err == nil && fi.IsDirectory() { + if fi, err := os.Stat(name); err == nil && fi.IsDir() { walkDir(name) } else { doFile(name, nil) @@ -97,16 +97,16 @@ func doFile(name string, reader io.Reader) { errorf("%s: %s", name, err) return } - file := &File{fs.File(parsedFile.Pos())} - file.checkFile(name, parsedFile) + file := &File{fset: fs, file: parsedFile} + file.walkFile(name, parsedFile) } -func visit(path string, f *os.FileInfo, err os.Error) os.Error { +func visit(path string, f os.FileInfo, err error) error { if err != nil { errorf("walk error: %s", err) return nil } - if f.IsRegular() && strings.HasSuffix(path, ".go") { + if !f.IsDir() && strings.HasSuffix(path, ".go") { doFile(path, nil) } return nil @@ -154,18 +154,18 @@ func (f *File) Badf(pos token.Pos, format string, args ...interface{}) { // Warn reports an error but does not set the exit code. func (f *File) Warn(pos token.Pos, args ...interface{}) { - loc := f.file.Position(pos).String() + ": " + loc := f.fset.Position(pos).String() + ": " fmt.Fprint(os.Stderr, loc+fmt.Sprintln(args...)) } // Warnf reports a formatted error but does not set the exit code. func (f *File) Warnf(pos token.Pos, format string, args ...interface{}) { - loc := f.file.Position(pos).String() + ": " + loc := f.fset.Position(pos).String() + ": " fmt.Fprintf(os.Stderr, loc+format+"\n", args...) } -// checkFile checks all the top-level declarations in a file. -func (f *File) checkFile(name string, file *ast.File) { +// walkFile walks the file's tree. +func (f *File) walkFile(name string, file *ast.File) { Println("Checking file", name) ast.Walk(f, file) } @@ -174,217 +174,59 @@ func (f *File) checkFile(name string, file *ast.File) { func (f *File) Visit(node ast.Node) ast.Visitor { switch n := node.(type) { case *ast.CallExpr: - f.checkCallExpr(n) + f.walkCallExpr(n) case *ast.Field: - f.checkFieldTag(n) + f.walkFieldTag(n) + case *ast.FuncDecl: + f.walkMethodDecl(n) + case *ast.InterfaceType: + f.walkInterfaceType(n) } return f } -// checkField checks a struct field tag. -func (f *File) checkFieldTag(field *ast.Field) { - if field.Tag == nil { - return - } - - tag, err := strconv.Unquote(field.Tag.Value) - if err != nil { - f.Warnf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) - return - } - - // Check tag for validity by appending - // new key:value to end and checking that - // the tag parsing code can find it. - if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" { - f.Warnf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get", field.Tag.Value) - return - } +// walkCall walks a call expression. +func (f *File) walkCall(call *ast.CallExpr, name string) { + f.checkFmtPrintfCall(call, name) } -// checkCallExpr checks a call expression. -func (f *File) checkCallExpr(call *ast.CallExpr) { - switch x := call.Fun.(type) { - case *ast.Ident: - f.checkCall(call, x.Name) - case *ast.SelectorExpr: - f.checkCall(call, x.Sel.Name) +// walkFieldTag walks a struct field tag. +func (f *File) walkFieldTag(field *ast.Field) { + if field.Tag == nil { + return } + f.checkCanonicalFieldTag(field) } -// printfList records the formatted-print functions. The value is the location -// of the format parameter. Names are lower-cased so the lookup is -// case insensitive. -var printfList = map[string]int{ - "errorf": 0, - "fatalf": 0, - "fprintf": 1, - "panicf": 0, - "printf": 0, - "sprintf": 0, +// walkMethodDecl walks the method's signature. +func (f *File) walkMethod(id *ast.Ident, t *ast.FuncType) { + f.checkCanonicalMethod(id, t) } -// printList records the unformatted-print functions. The value is the location -// of the first parameter to be printed. Names are lower-cased so the lookup is -// case insensitive. -var printList = map[string]int{ - "error": 0, - "fatal": 0, - "fprint": 1, "fprintln": 1, - "panic": 0, "panicln": 0, - "print": 0, "println": 0, - "sprint": 0, "sprintln": 0, -} - -// checkCall triggers the print-specific checks if the call invokes a print function. -func (f *File) checkCall(call *ast.CallExpr, Name string) { - name := strings.ToLower(Name) - if skip, ok := printfList[name]; ok { - f.checkPrintf(call, Name, skip) - return - } - if skip, ok := printList[name]; ok { - f.checkPrint(call, Name, skip) +// walkMethodDecl walks the method signature in the declaration. +func (f *File) walkMethodDecl(d *ast.FuncDecl) { + if d.Recv == nil { + // not a method return } + f.walkMethod(d.Name, d.Type) } -// checkPrintf checks a call to a formatted print routine such as Printf. -// The skip argument records how many arguments to ignore; that is, -// call.Args[skip] is (well, should be) the format argument. -func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) { - if len(call.Args) <= skip { - return - } - // Common case: literal is first argument. - arg := call.Args[skip] - lit, ok := arg.(*ast.BasicLit) - if !ok { - // Too hard to check. - if *verbose { - f.Warn(call.Pos(), "can't check args for call to", name) +// walkInterfaceType walks the method signatures of an interface. +func (f *File) walkInterfaceType(t *ast.InterfaceType) { + for _, field := range t.Methods.List { + for _, id := range field.Names { + f.walkMethod(id, field.Type.(*ast.FuncType)) } - return } - if lit.Kind == token.STRING { - if !strings.Contains(lit.Value, "%") { - if len(call.Args) > skip+1 { - f.Badf(call.Pos(), "no formatting directive in %s call", name) - } - return - } - } - // Hard part: check formats against args. - // Trivial but useful test: count. - numArgs := 0 - for i, w := 0, 0; i < len(lit.Value); i += w { - w = 1 - if lit.Value[i] == '%' { - nbytes, nargs := parsePrintfVerb(lit.Value[i:]) - w = nbytes - numArgs += nargs - } - } - expect := len(call.Args) - (skip + 1) - if numArgs != expect { - f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect) - } -} - -// parsePrintfVerb returns the number of bytes and number of arguments -// consumed by the Printf directive that begins s, including its percent sign -// and verb. -func parsePrintfVerb(s string) (nbytes, nargs int) { - // There's guaranteed a percent sign. - nbytes = 1 - end := len(s) - // There may be flags. -FlagLoop: - for nbytes < end { - switch s[nbytes] { - case '#', '0', '+', '-', ' ': - nbytes++ - default: - break FlagLoop - } - } - getNum := func() { - if nbytes < end && s[nbytes] == '*' { - nbytes++ - nargs++ - } else { - for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' { - nbytes++ - } - } - } - // There may be a width. - getNum() - // If there's a period, there may be a precision. - if nbytes < end && s[nbytes] == '.' { - nbytes++ - getNum() - } - // Now a verb. - c, w := utf8.DecodeRuneInString(s[nbytes:]) - nbytes += w - if c != '%' { - nargs++ - } - return } -// checkPrint checks a call to an unformatted print routine such as Println. -// The skip argument records how many arguments to ignore; that is, -// call.Args[skip] is the first argument to be printed. -func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) { - isLn := strings.HasSuffix(name, "ln") - args := call.Args - if len(args) <= skip { - if *verbose && !isLn { - f.Badf(call.Pos(), "no args in %s call", name) - } - return - } - arg := args[skip] - if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { - if strings.Contains(lit.Value, "%") { - f.Badf(call.Pos(), "possible formatting directive in %s call", name) - } - } - if isLn { - // The last item, if a string, should not have a newline. - arg = args[len(call.Args)-1] - if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { - if strings.HasSuffix(lit.Value, `\n"`) { - f.Badf(call.Pos(), "%s call ends with newline", name) - } - } +// walkCallExpr walks a call expression. +func (f *File) walkCallExpr(call *ast.CallExpr) { + switch x := call.Fun.(type) { + case *ast.Ident: + f.walkCall(call, x.Name) + case *ast.SelectorExpr: + f.walkCall(call, x.Sel.Name) } } - -// This function never executes, but it serves as a simple test for the program. -// Test with make test. -func BadFunctionUsedInTests() { - fmt.Println() // not an error - fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call" - fmt.Printf("%s", "hi", 3) // ERROR "wrong number of args in Printf call" - fmt.Printf("%s%%%d", "hi", 3) // correct - fmt.Printf("%.*d", 3, 3) // correct - fmt.Printf("%.*d", 3, 3, 3) // ERROR "wrong number of args in Printf call" - printf("now is the time", "buddy") // ERROR "no formatting directive" - Printf("now is the time", "buddy") // ERROR "no formatting directive" - Printf("hi") // ok - f := new(File) - f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call" - f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call" -} - -type BadTypeUsedInTests struct { - X int "hello" // ERROR "struct field tag" -} - -// printf is used by the test. -func printf(format string, args ...interface{}) { - panic("don't call - testing only") -} diff --git a/src/cmd/govet/method.go b/src/cmd/govet/method.go new file mode 100644 index 000000000..55bf11d16 --- /dev/null +++ b/src/cmd/govet/method.go @@ -0,0 +1,161 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the code to check canonical methods. + +package main + +import ( + "fmt" + "go/ast" + "go/printer" + "strings" +) + +type MethodSig struct { + args []string + results []string +} + +// canonicalMethods lists the input and output types for Go methods +// that are checked using dynamic interface checks. Because the +// checks are dynamic, such methods would not cause a compile error +// if they have the wrong signature: instead the dynamic check would +// fail, sometimes mysteriously. If a method is found with a name listed +// here but not the input/output types listed here, govet complains. +// +// A few of the canonical methods have very common names. +// For example, a type might implement a Scan method that +// has nothing to do with fmt.Scanner, but we still want to check +// the methods that are intended to implement fmt.Scanner. +// To do that, the arguments that have a + prefix are treated as +// signals that the canonical meaning is intended: if a Scan +// method doesn't have a fmt.ScanState as its first argument, +// we let it go. But if it does have a fmt.ScanState, then the +// rest has to match. +var canonicalMethods = map[string]MethodSig{ + // "Flush": {{}, {"error"}}, // http.Flusher and jpeg.writer conflict + "Format": {[]string{"=fmt.State", "rune"}, []string{}}, // fmt.Formatter + "GobDecode": {[]string{"[]byte"}, []string{"error"}}, // gob.GobDecoder + "GobEncode": {[]string{}, []string{"[]byte", "error"}}, // gob.GobEncoder + "MarshalJSON": {[]string{}, []string{"[]byte", "error"}}, // json.Marshaler + "MarshalXML": {[]string{}, []string{"[]byte", "error"}}, // xml.Marshaler + "Peek": {[]string{"=int"}, []string{"[]byte", "error"}}, // image.reader (matching bufio.Reader) + "ReadByte": {[]string{}, []string{"byte", "error"}}, // io.ByteReader + "ReadFrom": {[]string{"=io.Reader"}, []string{"int64", "error"}}, // io.ReaderFrom + "ReadRune": {[]string{}, []string{"rune", "int", "error"}}, // io.RuneReader + "Scan": {[]string{"=fmt.ScanState", "rune"}, []string{"error"}}, // fmt.Scanner + "Seek": {[]string{"=int64", "int"}, []string{"int64", "error"}}, // io.Seeker + "UnmarshalJSON": {[]string{"[]byte"}, []string{"error"}}, // json.Unmarshaler + "UnreadByte": {[]string{}, []string{"error"}}, + "UnreadRune": {[]string{}, []string{"error"}}, + "WriteByte": {[]string{"byte"}, []string{"error"}}, // jpeg.writer (matching bufio.Writer) + "WriteTo": {[]string{"=io.Writer"}, []string{"int64", "error"}}, // io.WriterTo +} + +func (f *File) checkCanonicalMethod(id *ast.Ident, t *ast.FuncType) { + // Expected input/output. + expect, ok := canonicalMethods[id.Name] + if !ok { + return + } + + // Actual input/output + args := typeFlatten(t.Params.List) + var results []ast.Expr + if t.Results != nil { + results = typeFlatten(t.Results.List) + } + + // Do the =s (if any) all match? + if !f.matchParams(expect.args, args, "=") || !f.matchParams(expect.results, results, "=") { + return + } + + // Everything must match. + if !f.matchParams(expect.args, args, "") || !f.matchParams(expect.results, results, "") { + expectFmt := id.Name + "(" + argjoin(expect.args) + ")" + if len(expect.results) == 1 { + expectFmt += " " + argjoin(expect.results) + } else if len(expect.results) > 1 { + expectFmt += " (" + argjoin(expect.results) + ")" + } + + f.b.Reset() + if err := printer.Fprint(&f.b, f.fset, t); err != nil { + fmt.Fprintf(&f.b, "<%s>", err) + } + actual := f.b.String() + if strings.HasPrefix(actual, "func(") { + actual = actual[4:] + } + actual = id.Name + actual + + f.Warnf(id.Pos(), "method %s should have signature %s", actual, expectFmt) + } +} + +func argjoin(x []string) string { + y := make([]string, len(x)) + for i, s := range x { + if s[0] == '=' { + s = s[1:] + } + y[i] = s + } + return strings.Join(y, ", ") +} + +// Turn parameter list into slice of types +// (in the ast, types are Exprs). +// Have to handle f(int, bool) and f(x, y, z int) +// so not a simple 1-to-1 conversion. +func typeFlatten(l []*ast.Field) []ast.Expr { + var t []ast.Expr + for _, f := range l { + if len(f.Names) == 0 { + t = append(t, f.Type) + continue + } + for _ = range f.Names { + t = append(t, f.Type) + } + } + return t +} + +// Does each type in expect with the given prefix match the corresponding type in actual? +func (f *File) matchParams(expect []string, actual []ast.Expr, prefix string) bool { + for i, x := range expect { + if !strings.HasPrefix(x, prefix) { + continue + } + if i >= len(actual) { + return false + } + if !f.matchParamType(x, actual[i]) { + return false + } + } + if prefix == "" && len(actual) > len(expect) { + return false + } + return true +} + +// Does this one type match? +func (f *File) matchParamType(expect string, actual ast.Expr) bool { + if strings.HasPrefix(expect, "=") { + expect = expect[1:] + } + // Strip package name if we're in that package. + if n := len(f.file.Name.Name); len(expect) > n && expect[:n] == f.file.Name.Name && expect[n] == '.' { + expect = expect[n+1:] + } + + // Overkill but easy. + f.b.Reset() + printer.Fprint(&f.b, f.fset, actual) + return f.b.String() == expect +} diff --git a/src/cmd/govet/print.go b/src/cmd/govet/print.go new file mode 100644 index 000000000..861a337c6 --- /dev/null +++ b/src/cmd/govet/print.go @@ -0,0 +1,267 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the printf-checker. + +package main + +import ( + "flag" + "fmt" + "go/ast" + "go/token" + "strings" + "unicode/utf8" +) + +var printfuncs = flag.String("printfuncs", "", "comma-separated list of print function names to check") + +// printfList records the formatted-print functions. The value is the location +// of the format parameter. Names are lower-cased so the lookup is +// case insensitive. +var printfList = map[string]int{ + "errorf": 0, + "fatalf": 0, + "fprintf": 1, + "panicf": 0, + "printf": 0, + "sprintf": 0, +} + +// printList records the unformatted-print functions. The value is the location +// of the first parameter to be printed. Names are lower-cased so the lookup is +// case insensitive. +var printList = map[string]int{ + "error": 0, + "fatal": 0, + "fprint": 1, "fprintln": 1, + "panic": 0, "panicln": 0, + "print": 0, "println": 0, + "sprint": 0, "sprintln": 0, +} + +// checkCall triggers the print-specific checks if the call invokes a print function. +func (f *File) checkFmtPrintfCall(call *ast.CallExpr, Name string) { + name := strings.ToLower(Name) + if skip, ok := printfList[name]; ok { + f.checkPrintf(call, Name, skip) + return + } + if skip, ok := printList[name]; ok { + f.checkPrint(call, Name, skip) + return + } +} + +// checkPrintf checks a call to a formatted print routine such as Printf. +// The skip argument records how many arguments to ignore; that is, +// call.Args[skip] is (well, should be) the format argument. +func (f *File) checkPrintf(call *ast.CallExpr, name string, skip int) { + if len(call.Args) <= skip { + return + } + // Common case: literal is first argument. + arg := call.Args[skip] + lit, ok := arg.(*ast.BasicLit) + if !ok { + // Too hard to check. + if *verbose { + f.Warn(call.Pos(), "can't check non-literal format in call to", name) + } + return + } + if lit.Kind == token.STRING { + if !strings.Contains(lit.Value, "%") { + if len(call.Args) > skip+1 { + f.Badf(call.Pos(), "no formatting directive in %s call", name) + } + return + } + } + // Hard part: check formats against args. + // Trivial but useful test: count. + numArgs := 0 + for i, w := 0, 0; i < len(lit.Value); i += w { + w = 1 + if lit.Value[i] == '%' { + nbytes, nargs := f.parsePrintfVerb(call, lit.Value[i:]) + w = nbytes + numArgs += nargs + } + } + expect := len(call.Args) - (skip + 1) + if numArgs != expect { + f.Badf(call.Pos(), "wrong number of args in %s call: %d needed but %d args", name, numArgs, expect) + } +} + +// parsePrintfVerb returns the number of bytes and number of arguments +// consumed by the Printf directive that begins s, including its percent sign +// and verb. +func (f *File) parsePrintfVerb(call *ast.CallExpr, s string) (nbytes, nargs int) { + // There's guaranteed a percent sign. + flags := make([]byte, 0, 5) + nbytes = 1 + end := len(s) + // There may be flags. +FlagLoop: + for nbytes < end { + switch s[nbytes] { + case '#', '0', '+', '-', ' ': + flags = append(flags, s[nbytes]) + nbytes++ + default: + break FlagLoop + } + } + getNum := func() { + if nbytes < end && s[nbytes] == '*' { + nbytes++ + nargs++ + } else { + for nbytes < end && '0' <= s[nbytes] && s[nbytes] <= '9' { + nbytes++ + } + } + } + // There may be a width. + getNum() + // If there's a period, there may be a precision. + if nbytes < end && s[nbytes] == '.' { + flags = append(flags, '.') // Treat precision as a flag. + nbytes++ + getNum() + } + // Now a verb. + c, w := utf8.DecodeRuneInString(s[nbytes:]) + nbytes += w + if c != '%' { + nargs++ + f.checkPrintfVerb(call, c, flags) + } + return +} + +type printVerb struct { + verb rune + flags string // known flags are all ASCII +} + +// Common flag sets for printf verbs. +const ( + numFlag = " -+.0" + sharpNumFlag = " -+.0#" + allFlags = " -+.0#" +) + +// printVerbs identifies which flags are known to printf for each verb. +// TODO: A type that implements Formatter may do what it wants, and govet +// will complain incorrectly. +var printVerbs = []printVerb{ + // '-' is a width modifier, always valid. + // '.' is a precision for float, max width for strings. + // '+' is required sign for numbers, Go format for %v. + // '#' is alternate format for several verbs. + // ' ' is spacer for numbers + {'b', numFlag}, + {'c', "-"}, + {'d', numFlag}, + {'e', "-."}, + {'E', numFlag}, + {'f', numFlag}, + {'F', numFlag}, + {'g', numFlag}, + {'G', numFlag}, + {'o', sharpNumFlag}, + {'p', "-#"}, + {'q', "-+#."}, + {'s', "-."}, + {'t', "-"}, + {'T', "-"}, + {'U', "-#"}, + {'v', allFlags}, + {'x', sharpNumFlag}, + {'X', sharpNumFlag}, +} + +const printfVerbs = "bcdeEfFgGopqstTvxUX" + +func (f *File) checkPrintfVerb(call *ast.CallExpr, verb rune, flags []byte) { + // Linear scan is fast enough for a small list. + for _, v := range printVerbs { + if v.verb == verb { + for _, flag := range flags { + if !strings.ContainsRune(v.flags, rune(flag)) { + f.Badf(call.Pos(), "unrecognized printf flag for verb %q: %q", verb, flag) + } + } + return + } + } + f.Badf(call.Pos(), "unrecognized printf verb %q", verb) +} + +// checkPrint checks a call to an unformatted print routine such as Println. +// The skip argument records how many arguments to ignore; that is, +// call.Args[skip] is the first argument to be printed. +func (f *File) checkPrint(call *ast.CallExpr, name string, skip int) { + isLn := strings.HasSuffix(name, "ln") + args := call.Args + if len(args) <= skip { + if *verbose && !isLn { + f.Badf(call.Pos(), "no args in %s call", name) + } + return + } + arg := args[skip] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.Contains(lit.Value, "%") { + f.Badf(call.Pos(), "possible formatting directive in %s call", name) + } + } + if isLn { + // The last item, if a string, should not have a newline. + arg = args[len(call.Args)-1] + if lit, ok := arg.(*ast.BasicLit); ok && lit.Kind == token.STRING { + if strings.HasSuffix(lit.Value, `\n"`) { + f.Badf(call.Pos(), "%s call ends with newline", name) + } + } + } +} + +// This function never executes, but it serves as a simple test for the program. +// Test with make test. +func BadFunctionUsedInTests() { + fmt.Println() // not an error + fmt.Println("%s", "hi") // ERROR "possible formatting directive in Println call" + fmt.Printf("%s", "hi", 3) // ERROR "wrong number of args in Printf call" + fmt.Printf("%s%%%d", "hi", 3) // correct + fmt.Printf("%.*d", 3, 3) // correct + fmt.Printf("%.*d", 3, 3, 3) // ERROR "wrong number of args in Printf call" + printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("now is the time", "buddy") // ERROR "no formatting directive" + Printf("hi") // ok + f := new(File) + f.Warn(0, "%s", "hello", 3) // ERROR "possible formatting directive in Warn call" + f.Warnf(0, "%s", "hello", 3) // ERROR "wrong number of args in Warnf call" + f.Warnf(0, "%r", "hello") // ERROR "unrecognized printf verb" + f.Warnf(0, "%#s", "hello") // ERROR "unrecognized printf flag" +} + +type BadTypeUsedInTests struct { + X int "hello" // ERROR "struct field tag" +} + +func (t *BadTypeUsedInTests) Scan(x fmt.ScanState, c byte) { // ERROR "method Scan[(]x fmt.ScanState, c byte[)] should have signature Scan[(]fmt.ScanState, rune[)] error" +} + +type BadInterfaceUsedInTests interface { + ReadByte() byte // ERROR "method ReadByte[(][)] byte should have signature ReadByte[(][)] [(]byte, error[)]" +} + +// printf is used by the test. +func printf(format string, args ...interface{}) { + panic("don't call - testing only") +} diff --git a/src/cmd/govet/structtag.go b/src/cmd/govet/structtag.go new file mode 100644 index 000000000..ea2a9d863 --- /dev/null +++ b/src/cmd/govet/structtag.go @@ -0,0 +1,34 @@ +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// This file contains the test for canonical struct tags. + +package main + +import ( + "go/ast" + "reflect" + "strconv" +) + +// checkField checks a struct field tag. +func (f *File) checkCanonicalFieldTag(field *ast.Field) { + if field.Tag == nil { + return + } + + tag, err := strconv.Unquote(field.Tag.Value) + if err != nil { + f.Warnf(field.Pos(), "unable to read struct tag %s", field.Tag.Value) + return + } + + // Check tag for validity by appending + // new key:value to end and checking that + // the tag parsing code can find it. + if reflect.StructTag(tag+` _gofix:"_magic"`).Get("_gofix") != "_magic" { + f.Warnf(field.Pos(), "struct field tag %s not compatible with reflect.StructTag.Get", field.Tag.Value) + return + } +} diff --git a/src/cmd/goyacc/Makefile b/src/cmd/goyacc/Makefile index ac0f427cc..a616e8534 100644 --- a/src/cmd/goyacc/Makefile +++ b/src/cmd/goyacc/Makefile @@ -12,6 +12,7 @@ include ../../Make.cmd units: goyacc units.y ./goyacc -p units_ units.y - $(GC) y.go + $(GC) $(GCFLAGS) $(GCIMPORTS) y.go $(LD) -o units y.$O +CLEANFILES += units y.go y.output diff --git a/src/cmd/goyacc/goyacc.go b/src/cmd/goyacc/goyacc.go index 481540188..e1b99bed2 100644 --- a/src/cmd/goyacc/goyacc.go +++ b/src/cmd/goyacc/goyacc.go @@ -45,12 +45,12 @@ package main // import ( + "bufio" + "bytes" "flag" "fmt" - "bufio" "os" "strings" - "bytes" ) // the following are adjustable @@ -506,19 +506,20 @@ outer: // non-literals c := tokset[i].name[0] if c != ' ' && c != '$' { - fmt.Fprintf(ftable, "const\t%v\t= %v\n", tokset[i].name, tokset[i].value) + fmt.Fprintf(ftable, "const %v = %v\n", tokset[i].name, tokset[i].value) } } // put out names of token names - fmt.Fprintf(ftable, "var\t%sToknames\t =[]string {\n", prefix) + ftable.WriteRune('\n') + fmt.Fprintf(ftable, "var %sToknames = []string{\n", prefix) for i := TOKSTART; i <= ntokens; i++ { fmt.Fprintf(ftable, "\t\"%v\",\n", tokset[i].name) } fmt.Fprintf(ftable, "}\n") // put out names of state names - fmt.Fprintf(ftable, "var\t%sStatenames\t =[]string {\n", prefix) + fmt.Fprintf(ftable, "var %sStatenames = []string{", prefix) // for i:=TOKSTART; i<=ntokens; i++ { // fmt.Fprintf(ftable, "\t\"%v\",\n", tokset[i].name); // } @@ -595,7 +596,7 @@ outer: break } levprd[nprod] |= ACTFLAG - fmt.Fprintf(fcode, "\ncase %v:", nprod) + fmt.Fprintf(fcode, "\n\tcase %v:", nprod) cpyact(curprod, mem) // action within rule... @@ -652,8 +653,8 @@ outer: if tempty != nontrst[curprod[0]-NTBASE].value { errorf("default action causes potential type clash") } - fmt.Fprintf(fcode, "\ncase %v:", nprod) - fmt.Fprintf(fcode, "\n\t%sVAL.%v = %sS[%spt-0].%v;", + fmt.Fprintf(fcode, "\n\tcase %v:", nprod) + fmt.Fprintf(fcode, "\n\t\t%sVAL.%v = %sS[%spt-0].%v", prefix, typeset[tempty], prefix, prefix, typeset[tempty]) } moreprod() @@ -671,9 +672,10 @@ outer: fmt.Fprintf(fcode, "\n\t}") - fmt.Fprintf(ftable, "const %sEofCode = 1\n", prefix) - fmt.Fprintf(ftable, "const %sErrCode = 2\n", prefix) - fmt.Fprintf(ftable, "const %sMaxDepth = %v\n", prefix, stacksize) + ftable.WriteRune('\n') + fmt.Fprintf(ftable, "const %sEofCode = 1\n", prefix) + fmt.Fprintf(ftable, "const %sErrCode = 2\n", prefix) + fmt.Fprintf(ftable, "const %sMaxDepth = %v\n", prefix, stacksize) // // copy any postfix code @@ -811,7 +813,8 @@ func defin(nt int, s string) int { var peekline = 0 func gettok() int { - var i, match, c int + var i int + var match, c rune tokname = "" for { @@ -917,25 +920,25 @@ func gettok() int { getword(c) // find a reserved word - for c = 0; c < len(resrv); c++ { - if tokname == resrv[c].name { + for i := range resrv { + if tokname == resrv[i].name { if tokflag { fmt.Printf(">>> %%%v %v %v\n", tokname, - resrv[c].value-PRIVATE, lineno) + resrv[i].value-PRIVATE, lineno) } - return resrv[c].value + return resrv[i].value } } errorf("invalid escape, or illegal reserved word: %v", tokname) case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': - numbval = c - '0' + numbval = int(c - '0') for { c = getrune(finput) if !isdigit(c) { break } - numbval = numbval*10 + c - '0' + numbval = numbval*10 + int(c-'0') } ungetrune(finput, c) if tokflag { @@ -951,7 +954,7 @@ func gettok() int { if tokflag { fmt.Printf(">>> OPERATOR %v %v\n", string(c), lineno) } - return c + return int(c) } // look ahead to distinguish IDENTIFIER from IDENTCOLON @@ -980,7 +983,7 @@ func gettok() int { return IDENTIFIER } -func getword(c int) { +func getword(c rune) { tokname = "" for isword(c) || isdigit(c) || c == '_' || c == '.' || c == '$' { tokname += string(c) @@ -1039,7 +1042,7 @@ func cpyunion() { if !lflag { fmt.Fprintf(ftable, "\n//line %v:%v\n", infile, lineno) } - fmt.Fprintf(ftable, "type\t%sSymType\tstruct", prefix) + fmt.Fprintf(ftable, "type %sSymType struct", prefix) level := 0 @@ -1055,7 +1058,7 @@ out: lineno++ case '{': if level == 0 { - fmt.Fprintf(ftable, "\n\tyys\tint;") + fmt.Fprintf(ftable, "\n\tyys int") } level++ case '}': @@ -1065,7 +1068,7 @@ out: } } } - fmt.Fprintf(ftable, "\n") + fmt.Fprintf(ftable, "\n\n") } // @@ -1105,7 +1108,7 @@ func cpycode() { // skipcom is called after reading a '/' // func skipcom() int { - var c int + var c rune c = getrune(finput) if c == '/' { @@ -1163,7 +1166,7 @@ func dumpprod(curprod []int, max int) { func cpyact(curprod []int, max int) { if !lflag { - fmt.Fprintf(fcode, "\n//line %v:%v\n", infile, lineno) + fmt.Fprintf(fcode, "\n\t\t//line %v:%v\n\t\t", infile, lineno) } lno := lineno @@ -1177,14 +1180,13 @@ loop: switch c { case ';': if brac == 0 { - ftable.WriteRune(c) + fcode.WriteRune(c) return } case '{': if brac == 0 { } - ftable.WriteRune('\t') brac++ case '$': @@ -1220,7 +1222,7 @@ loop: j := 0 if isdigit(c) { for isdigit(c) { - j = j*10 + c - '0' + j = j*10 + int(c-'0') c = getrune(finput) } ungetrune(finput, c) @@ -1345,7 +1347,9 @@ loop: errorf("action does not terminate") case '\n': + fmt.Fprint(fcode, "\n\t") lineno++ + continue loop } fcode.WriteRune(c) @@ -2072,7 +2076,7 @@ func output() { var c, u, v int fmt.Fprintf(ftable, "\n//line yacctab:1\n") - fmt.Fprintf(ftable, "var\t%sExca = []int {\n", prefix) + fmt.Fprintf(ftable, "var %sExca = []int{\n", prefix) noset := mkset() @@ -2145,10 +2149,12 @@ func output() { } fmt.Fprintf(ftable, "}\n") - fmt.Fprintf(ftable, "const\t%sNprod\t= %v\n", prefix, nprod) - fmt.Fprintf(ftable, "const\t%sPrivate\t= %v\n", prefix, PRIVATE) - fmt.Fprintf(ftable, "var\t%sTokenNames []string\n", prefix) - fmt.Fprintf(ftable, "var\t%sStates []string\n", prefix) + ftable.WriteRune('\n') + fmt.Fprintf(ftable, "const %sNprod = %v\n", prefix, nprod) + fmt.Fprintf(ftable, "const %sPrivate = %v\n", prefix, PRIVATE) + ftable.WriteRune('\n') + fmt.Fprintf(ftable, "var %sTokenNames []string\n", prefix) + fmt.Fprintf(ftable, "var %sStates []string\n", prefix) } // @@ -2264,7 +2270,7 @@ func wract(i int) { continue } if flag == 0 { - fmt.Fprintf(ftable, "-1, %v,\n", i) + fmt.Fprintf(ftable, "\t-1, %v,\n", i) } flag++ fmt.Fprintf(ftable, "\t%v, %v,\n", p, p1) @@ -2723,7 +2729,8 @@ nextn: // write out the optimized parser // func aoutput() { - fmt.Fprintf(ftable, "const\t%sLast\t= %v\n", prefix, maxa+1) + ftable.WriteRune('\n') + fmt.Fprintf(ftable, "const %sLast = %v\n\n", prefix, maxa+1) arout("Act", amem, maxa+1) arout("Pact", indgo, nstate) arout("Pgo", pgo, nnonter+1) @@ -2805,7 +2812,7 @@ func others() { arout("Tok2", temp1, c+1) // table 3 has everything else - fmt.Fprintf(ftable, "var\t%sTok3\t= []int {\n", prefix) + fmt.Fprintf(ftable, "var %sTok3 = []int{\n\t", prefix) c = 0 for i = 1; i <= ntokens; i++ { j = tokset[i].value @@ -2816,19 +2823,25 @@ func others() { continue } - fmt.Fprintf(ftable, "%4d,%4d,", j, i) + if c%5 != 0 { + ftable.WriteRune(' ') + } + fmt.Fprintf(ftable, "%d, %d,", j, i) c++ if c%5 == 0 { - ftable.WriteRune('\n') + fmt.Fprint(ftable, "\n\t") } } - fmt.Fprintf(ftable, "%4d,\n };\n", 0) + if c%5 != 0 { + ftable.WriteRune(' ') + } + fmt.Fprintf(ftable, "%d,\n}\n", 0) // copy parser text - c = getrune(finput) - for c != EOF { - ftable.WriteRune(c) - c = getrune(finput) + ch := getrune(finput) + for ch != EOF { + ftable.WriteRune(ch) + ch = getrune(finput) } // copy yaccpar @@ -2842,15 +2855,16 @@ func others() { func arout(s string, v []int, n int) { s = prefix + s - fmt.Fprintf(ftable, "var\t%v\t= []int {\n", s) + fmt.Fprintf(ftable, "var %v = []int{\n", s) for i := 0; i < n; i++ { if i%10 == 0 { - ftable.WriteRune('\n') + fmt.Fprintf(ftable, "\n\t") + } else { + ftable.WriteRune(' ') } - fmt.Fprintf(ftable, "%4d", v[i]) - ftable.WriteRune(',') + fmt.Fprintf(ftable, "%d,", v[i]) } - fmt.Fprintf(ftable, "\n};\n") + fmt.Fprintf(ftable, "\n}\n") } // @@ -2963,11 +2977,11 @@ func prlook(p Lkset) { // // utility routines // -var peekrune int +var peekrune rune -func isdigit(c int) bool { return c >= '0' && c <= '9' } +func isdigit(c rune) bool { return c >= '0' && c <= '9' } -func isword(c int) bool { +func isword(c rune) bool { return c >= 0xa0 || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') } @@ -2997,8 +3011,8 @@ func putrune(f *bufio.Writer, c int) { } } -func getrune(f *bufio.Reader) int { - var r int +func getrune(f *bufio.Reader) rune { + var r rune if peekrune != 0 { if peekrune == EOF { @@ -3020,7 +3034,7 @@ func getrune(f *bufio.Reader) int { return c } -func ungetrune(f *bufio.Reader, c int) { +func ungetrune(f *bufio.Reader, c rune) { if f != finput { panic("ungetc - not finput") } @@ -3257,10 +3271,9 @@ $$default: } } - /* the current p has no shift onn "error", pop stack */ + /* the current p has no shift on "error", pop stack */ if $$Debug >= 2 { - fmt.Printf("error recovery pops state %d, uncovers %d\n", - $$S[$$p].yys, $$S[$$p-1].yys) + fmt.Printf("error recovery pops state %d\n", $$S[$$p].yys) } $$p-- } @@ -3286,7 +3299,7 @@ $$default: $$nt := $$n $$pt := $$p - _ = $$pt // guard against "declared and not used" + _ = $$pt // guard against "declared and not used" $$p -= $$R2[$$n] $$VAL = $$S[$$p+1] diff --git a/src/cmd/goyacc/units.y b/src/cmd/goyacc/units.y index d9ef663d9..3833486ad 100644 --- a/src/cmd/goyacc/units.y +++ b/src/cmd/goyacc/units.y @@ -14,7 +14,7 @@ // units.y // example of a goyacc program // usage is -// goyacc units.y (produces y.go) +// goyacc -p "units_" units.y (produces y.go) // 6g y.go // 6l y.6 // ./6.out $GOROOT/src/cmd/goyacc/units @@ -33,7 +33,7 @@ import ( "os" "math" "strconv" - "utf8" + "unicode/utf8" ) const ( @@ -58,21 +58,19 @@ var lineno int // current input line number var linep int // index to next rune in unput var nerrors int // error count var one Node // constant one -var peekrune int // backup runt from input +var peekrune rune // backup runt from input var retnode1 Node var retnode2 Node var retnode Node var sym string var vflag bool - %} -%union -{ - node Node - vvar *Var - numb int - vval float64 +%union { + node Node + vvar *Var + numb int + vval float64 } %type <node> prog expr expr0 expr1 expr2 expr3 expr4 @@ -85,7 +83,6 @@ prog: ':' VAR expr { var f int - f = int($2.node.dim[0]) $2.node = $3 $2.node.dim[0] = 1 @@ -98,26 +95,23 @@ prog: | ':' VAR '#' { var f, i int - - for i=1; i<Ndim; i++ { + for i = 1; i < Ndim; i++ { if fund[i] == nil { break } } if i >= Ndim { Error("too many dimensions") - i = Ndim-1 + i = Ndim - 1 } fund[i] = $2 - f = int($2.node.dim[0]) $2.node = one $2.node.dim[0] = 1 $2.node.dim[i] = 1 if f != 0 { Errorf("redefinition of %v", $2.name) - } else - if vflag { + } else if vflag { fmt.Printf("%v\t#\n", $2.name) } } @@ -171,8 +165,7 @@ expr2: | expr2 '^' expr1 { var i int - - for i=1; i<Ndim; i++ { + for i = 1; i < Ndim; i++ { if $3.dim[i] != 0 { Error("exponent has units") $$ = $1 @@ -219,7 +212,8 @@ expr0: type UnitsLex int func (UnitsLex) Lex(yylval *units_SymType) int { - var c, i int + var c rune + var i int c = peekrune peekrune = ' ' @@ -249,7 +243,7 @@ loop: yylval.numb = 3 return SUP } - return c + return int(c) alpha: sym = "" @@ -274,7 +268,7 @@ numb: } } peekrune = c - f, err := strconv.Atof64(sym) + f, err := strconv.ParseFloat(sym, 64) if err != nil { fmt.Printf("error converting %v\n", sym) f = 0 @@ -369,7 +363,7 @@ func main() { * all characters that have some * meaning. rest are usable as names */ -func ralpha(c int) bool { +func ralpha(c rune) bool { switch c { case 0, '+', '-', '*', '/', '[', ']', '(', ')', '^', ':', '?', ' ', '\t', '.', '|', '#', @@ -382,7 +376,7 @@ func ralpha(c int) bool { /* * number forming character */ -func rdigit(c int) bool { +func rdigit(c rune) bool { switch c { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', 'e', '+', '-': @@ -584,8 +578,9 @@ func readline() bool { return false } -func getrune() int { - var c, n int +func getrune() rune { + var c rune + var n int if linep >= len(line) { return 0 @@ -690,7 +685,6 @@ func pname() float64 { return 0 } - // careful multiplication // exponents (log) are checked before multiply func fmul(a, b float64) float64 { diff --git a/src/cmd/hgpatch/doc.go b/src/cmd/hgpatch/doc.go deleted file mode 100644 index 1e0f1da38..000000000 --- a/src/cmd/hgpatch/doc.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -/* - -Hgpatch applies a patch to the local Mercurial repository. -The patch should have been been generated by -a version control system like CVS, Git, Mercurial, or Subversion. -If successful, hgpatch writes a list of affected files to standard output. - -Hgpatch is meant to be used by the Mercurial codereview extension. - -Usage: - hgpatch [patchfile] - -*/ -package documentation diff --git a/src/cmd/hgpatch/main.go b/src/cmd/hgpatch/main.go deleted file mode 100644 index d4169ae85..000000000 --- a/src/cmd/hgpatch/main.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package main - -import ( - "bytes" - "exec" - "flag" - "fmt" - "io/ioutil" - "os" - "patch" - "path/filepath" - "sort" - "strings" -) - -var checkSync = flag.Bool("checksync", true, "check whether repository is out of sync") - -func usage() { - fmt.Fprintf(os.Stderr, "usage: hgpatch [options] [patchfile]\n") - flag.PrintDefaults() - os.Exit(2) -} - -func main() { - flag.Usage = usage - flag.Parse() - - args := flag.Args() - var data []byte - var err os.Error - switch len(args) { - case 0: - data, err = ioutil.ReadAll(os.Stdin) - case 1: - data, err = ioutil.ReadFile(args[0]) - default: - usage() - } - chk(err) - - pset, err := patch.Parse(data) - chk(err) - - // Change to hg root directory, because - // patch paths are relative to root. - root, err := hgRoot() - chk(err) - chk(os.Chdir(root)) - - // Make sure there are no pending changes on the server. - if *checkSync && hgIncoming() { - fmt.Fprintf(os.Stderr, "incoming changes waiting; run hg sync first\n") - os.Exit(2) - } - - // Make sure we won't be editing files with local pending changes. - dirtylist, err := hgModified() - chk(err) - dirty := make(map[string]bool) - for _, f := range dirtylist { - dirty[f] = true - } - conflict := make(map[string]bool) - for _, f := range pset.File { - if f.Verb == patch.Delete || f.Verb == patch.Rename { - if dirty[f.Src] { - conflict[f.Src] = true - } - } - if f.Verb != patch.Delete { - if dirty[f.Dst] { - conflict[f.Dst] = true - } - } - } - if len(conflict) > 0 { - fmt.Fprintf(os.Stderr, "cannot apply patch to locally modified files:\n") - for name := range conflict { - fmt.Fprintf(os.Stderr, "\t%s\n", name) - } - os.Exit(2) - } - - // Apply changes in memory. - op, err := pset.Apply(ioutil.ReadFile) - chk(err) - - // Write changes to disk copy: order of commands matters. - // Accumulate undo log as we go, in case there is an error. - // Also accumulate list of modified files to print at end. - changed := make(map[string]int) - - // Copy, Rename create the destination file, so they - // must happen before we write the data out. - // A single patch may have a Copy and a Rename - // with the same source, so we have to run all the - // Copy in one pass, then all the Rename. - for i := range op { - o := &op[i] - if o.Verb == patch.Copy { - makeParent(o.Dst) - chk(hgCopy(o.Dst, o.Src)) - undoRevert(o.Dst) - changed[o.Dst] = 1 - } - } - for i := range op { - o := &op[i] - if o.Verb == patch.Rename { - makeParent(o.Dst) - chk(hgRename(o.Dst, o.Src)) - undoRevert(o.Dst) - undoRevert(o.Src) - changed[o.Src] = 1 - changed[o.Dst] = 1 - } - } - - // Run Delete before writing to files in case one of the - // deleted paths is becoming a directory. - for i := range op { - o := &op[i] - if o.Verb == patch.Delete { - chk(hgRemove(o.Src)) - undoRevert(o.Src) - changed[o.Src] = 1 - } - } - - // Write files. - for i := range op { - o := &op[i] - if o.Verb == patch.Delete { - continue - } - if o.Verb == patch.Add { - makeParent(o.Dst) - changed[o.Dst] = 1 - } - if o.Data != nil { - chk(ioutil.WriteFile(o.Dst, o.Data, 0644)) - if o.Verb == patch.Add { - undoRm(o.Dst) - } else { - undoRevert(o.Dst) - } - changed[o.Dst] = 1 - } - if o.Mode != 0 { - chk(os.Chmod(o.Dst, uint32(o.Mode&0755))) - undoRevert(o.Dst) - changed[o.Dst] = 1 - } - } - - // hg add looks at the destination file, so it must happen - // after we write the data out. - for i := range op { - o := &op[i] - if o.Verb == patch.Add { - chk(hgAdd(o.Dst)) - undoRevert(o.Dst) - changed[o.Dst] = 1 - } - } - - // Finished editing files. Write the list of changed files to stdout. - list := make([]string, len(changed)) - i := 0 - for f := range changed { - list[i] = f - i++ - } - sort.Strings(list) - for _, f := range list { - fmt.Printf("%s\n", f) - } -} - -// make parent directory for name, if necessary -func makeParent(name string) { - parent, _ := filepath.Split(name) - chk(mkdirAll(parent, 0755)) -} - -// Copy of os.MkdirAll but adds to undo log after -// creating a directory. -func mkdirAll(path string, perm uint32) os.Error { - dir, err := os.Lstat(path) - if err == nil { - if dir.IsDirectory() { - return nil - } - return &os.PathError{"mkdir", path, os.ENOTDIR} - } - - i := len(path) - for i > 0 && path[i-1] == '/' { // Skip trailing slashes. - i-- - } - - j := i - for j > 0 && path[j-1] != '/' { // Scan backward over element. - j-- - } - - if j > 0 { - err = mkdirAll(path[0:j-1], perm) - if err != nil { - return err - } - } - - err = os.Mkdir(path, perm) - if err != nil { - // Handle arguments like "foo/." by - // double-checking that directory doesn't exist. - dir, err1 := os.Lstat(path) - if err1 == nil && dir.IsDirectory() { - return nil - } - return err - } - undoRm(path) - return nil -} - -// If err != nil, process the undo log and exit. -func chk(err os.Error) { - if err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - runUndo() - os.Exit(2) - } -} - -// Undo log -type undo func() os.Error - -var undoLog []undo - -func undoRevert(name string) { - undoLog = append(undoLog, undo(func() os.Error { return hgRevert(name) })) -} - -func undoRm(name string) { undoLog = append(undoLog, undo(func() os.Error { return os.Remove(name) })) } - -func runUndo() { - for i := len(undoLog) - 1; i >= 0; i-- { - if err := undoLog[i](); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - } - } -} - -// hgRoot returns the root directory of the repository. -func hgRoot() (string, os.Error) { - out, err := run([]string{"hg", "root"}, nil) - if err != nil { - return "", err - } - return strings.TrimSpace(out), nil -} - -// hgIncoming returns true if hg sync will pull in changes. -func hgIncoming() bool { - // hg -q incoming exits 0 when there is nothing incoming, 1 otherwise. - _, err := run([]string{"hg", "-q", "incoming"}, nil) - return err == nil -} - -// hgModified returns a list of the modified files in the -// repository. -func hgModified() ([]string, os.Error) { - out, err := run([]string{"hg", "status", "-n"}, nil) - if err != nil { - return nil, err - } - return strings.Split(strings.TrimSpace(out), "\n"), nil -} - -// hgAdd adds name to the repository. -func hgAdd(name string) os.Error { - _, err := run([]string{"hg", "add", name}, nil) - return err -} - -// hgRemove removes name from the repository. -func hgRemove(name string) os.Error { - _, err := run([]string{"hg", "rm", name}, nil) - return err -} - -// hgRevert reverts name. -func hgRevert(name string) os.Error { - _, err := run([]string{"hg", "revert", name}, nil) - return err -} - -// hgCopy copies src to dst in the repository. -// Note that the argument order matches io.Copy, not "hg cp". -func hgCopy(dst, src string) os.Error { - _, err := run([]string{"hg", "cp", src, dst}, nil) - return err -} - -// hgRename renames src to dst in the repository. -// Note that the argument order matches io.Copy, not "hg mv". -func hgRename(dst, src string) os.Error { - _, err := run([]string{"hg", "mv", src, dst}, nil) - return err -} - -func dup(a []string) []string { - b := make([]string, len(a)) - copy(b, a) - return b -} - -var lookPathCache = make(map[string]string) - -// run runs the command argv, resolving argv[0] if necessary by searching $PATH. -// It provides input on standard input to the command. -func run(argv []string, input []byte) (out string, err os.Error) { - if len(argv) < 1 { - return "", &runError{dup(argv), os.EINVAL} - } - - prog, ok := lookPathCache[argv[0]] - if !ok { - prog, err = exec.LookPath(argv[0]) - if err != nil { - return "", &runError{dup(argv), err} - } - lookPathCache[argv[0]] = prog - } - - cmd := exec.Command(prog, argv[1:]...) - if len(input) > 0 { - cmd.Stdin = bytes.NewBuffer(input) - } - bs, err := cmd.CombinedOutput() - if err != nil { - return "", &runError{dup(argv), err} - } - return string(bs), nil -} - -// A runError represents an error that occurred while running a command. -type runError struct { - cmd []string - err os.Error -} - -func (e *runError) String() string { return strings.Join(e.cmd, " ") + ": " + e.err.String() } diff --git a/src/cmd/ld/data.c b/src/cmd/ld/data.c index a7f61c927..d34d23c77 100644 --- a/src/cmd/ld/data.c +++ b/src/cmd/ld/data.c @@ -824,6 +824,8 @@ dodata(void) datsize = 0; s = datap; for(; s != nil && s->type < SSYMTAB; s = s->next) { + if(s->align != 0) + datsize = rnd(datsize, s->align); s->type = SRODATA; s->value = datsize; datsize += rnd(s->size, PtrSize); @@ -855,6 +857,8 @@ dodata(void) /* read-only ELF sections */ for(; s != nil && s->type < SELFSECT; s = s->next) { sect = addsection(&segtext, s->name, 04); + if(s->align != 0) + datsize = rnd(datsize, s->align); sect->vaddr = datsize; s->type = SRODATA; s->value = datsize; @@ -866,6 +870,8 @@ dodata(void) datsize = 0; for(; s != nil && s->type < SDATA; s = s->next) { sect = addsection(&segdata, s->name, 06); + if(s->align != 0) + datsize = rnd(datsize, s->align); sect->vaddr = datsize; s->type = SDATA; s->value = datsize; @@ -887,7 +893,9 @@ dodata(void) t = rnd(t, PtrSize); else if(t > 2) t = rnd(t, 4); - if(t & 1) { + if(s->align != 0) + datsize = rnd(datsize, s->align); + else if(t & 1) { ; } else if(t & 2) datsize = rnd(datsize, 2); @@ -913,7 +921,9 @@ dodata(void) t = rnd(t, PtrSize); else if(t > 2) t = rnd(t, 4); - if(t & 1) { + if(s->align != 0) + datsize = rnd(datsize, s->align); + else if(t & 1) { ; } else if(t & 2) datsize = rnd(datsize, 2); @@ -947,6 +957,8 @@ textaddress(void) for(sym = textp; sym != nil; sym = sym->next) { if(sym->type & SSUB) continue; + if(sym->align != 0) + va = rnd(va, sym->align); sym->value = 0; for(sub = sym; sub != S; sub = sub->sub) { sub->value += va; diff --git a/src/cmd/ld/dwarf.c b/src/cmd/ld/dwarf.c index 77536018a..d0ecabf8a 100644 --- a/src/cmd/ld/dwarf.c +++ b/src/cmd/ld/dwarf.c @@ -775,7 +775,7 @@ enum { KindNoPointers = 1<<7, // size of Type interface header + CommonType structure. - CommonSize = 2*PtrSize+ 4*PtrSize + 8, + CommonSize = 2*PtrSize+ 5*PtrSize + 8, }; static Reloc* @@ -1356,7 +1356,7 @@ synthesizemaptypes(DWDie *die) getattr(keytype, DW_AT_name)->data, getattr(valtype, DW_AT_name)->data)); copychildren(dwhs, hash_subtable); - substitutetype(dwhs, "end", defptrto(dwhe)); + substitutetype(dwhs, "last", defptrto(dwhe)); substitutetype(dwhs, "entry", dwhe); // todo: []hash_entry with dynamic size newattr(dwhs, DW_AT_byte_size, DW_CLS_CONSTANT, getattr(hash_subtable, DW_AT_byte_size)->value, nil); @@ -1439,7 +1439,7 @@ defdwsymb(Sym* sym, char *s, int t, vlong v, vlong size, int ver, Sym *gotype) if (strncmp(s, "go.string.", 10) == 0) return; - if (strncmp(s, "type.", 5) == 0 && strcmp(s, "type.*") != 0) { + if (strncmp(s, "type.", 5) == 0 && strcmp(s, "type.*") != 0 && strncmp(s, "type..", 6) != 0) { defgotype(sym); return; } diff --git a/src/cmd/ld/elf.c b/src/cmd/ld/elf.c index 00cfc8c8c..de9e6b854 100644 --- a/src/cmd/ld/elf.c +++ b/src/cmd/ld/elf.c @@ -318,29 +318,77 @@ elfwritedynentsymsize(Sym *s, int tag, Sym *t) } int -elfwriteinterp(void) +elfinterp(ElfShdr *sh, uint64 startva, uint64 resoff, char *p) { int n; - if(interp == nil) - return 0; - + interp = p; n = strlen(interp)+1; - cseek(ELFRESERVE-n); - cwrite(interp, n); + sh->addr = startva + resoff - n; + sh->off = resoff - n; + sh->size = n; + return n; } -void -elfinterp(ElfShdr *sh, uint64 startva, char *p) +int +elfwriteinterp(vlong stridx) +{ + ElfShdr *sh = nil; + int i; + + for(i = 0; i < hdr.shnum; i++) + if(shdr[i]->name == stridx) + sh = shdr[i]; + if(sh == nil || interp == nil) + return 0; + + cseek(sh->off); + cwrite(interp, sh->size); + return sh->size; +} + +// Defined in NetBSD's sys/exec_elf.h +#define ELF_NOTE_TYPE_NETBSD_TAG 1 +#define ELF_NOTE_NETBSD_NAMESZ 7 +#define ELF_NOTE_NETBSD_DESCSZ 4 +#define ELF_NOTE_NETBSD_NAME "NetBSD\0\0" +#define ELF_NOTE_NETBSD_VERSION 599000000 /* NetBSD 5.99 */ + +int +elfnetbsdsig(ElfShdr *sh, uint64 startva, uint64 resoff) { int n; - interp = p; - n = strlen(interp)+1; - sh->addr = startva + ELFRESERVE - n; - sh->off = ELFRESERVE - n; + n = sizeof(Elf_Note) + ELF_NOTE_NETBSD_NAMESZ + ELF_NOTE_NETBSD_DESCSZ + 1; + n += resoff % 4; + sh->addr = startva + resoff - n; + sh->off = resoff - n; sh->size = n; + + return n; +} + +int +elfwritenetbsdsig(vlong stridx) { + ElfShdr *sh = nil; + int i; + + for(i = 0; i < hdr.shnum; i++) + if(shdr[i]->name == stridx) + sh = shdr[i]; + if(sh == nil) + return 0; + + // Write Elf_Note header followed by NetBSD string. + cseek(sh->off); + LPUT(ELF_NOTE_NETBSD_NAMESZ); + LPUT(ELF_NOTE_NETBSD_DESCSZ); + LPUT(ELF_NOTE_TYPE_NETBSD_TAG); + cwrite(ELF_NOTE_NETBSD_NAME, 8); + LPUT(ELF_NOTE_NETBSD_VERSION); + + return sh->size; } extern int nelfsym; diff --git a/src/cmd/ld/elf.h b/src/cmd/ld/elf.h index c63df2241..690ade975 100644 --- a/src/cmd/ld/elf.h +++ b/src/cmd/ld/elf.h @@ -968,8 +968,10 @@ extern int numelfphdr; extern int numelfshdr; extern int iself; extern int elfverneed; -int elfwriteinterp(void); -void elfinterp(ElfShdr*, uint64, char*); +int elfinterp(ElfShdr*, uint64, uint64, char*); +int elfwriteinterp(vlong); +int elfnetbsdsig(ElfShdr*, uint64, uint64); +int elfwritenetbsdsig(vlong); void elfdynhash(void); ElfPhdr* elfphload(Segment*); ElfShdr* elfshbits(Section*); diff --git a/src/cmd/ld/go.c b/src/cmd/ld/go.c index fd7278a7b..2bda628cd 100644 --- a/src/cmd/ld/go.c +++ b/src/cmd/ld/go.c @@ -235,7 +235,7 @@ loadpkgdata(char *file, char *pkg, char *data, int len) x = ilookup(name); if(x->prefix == nil) { x->prefix = prefix; - x->def = def; + x->def = strdup(def); x->file = file; } else if(strcmp(x->prefix, prefix) != 0) { fprint(2, "%s: conflicting definitions for %s\n", argv0, name); @@ -248,7 +248,10 @@ loadpkgdata(char *file, char *pkg, char *data, int len) fprint(2, "%s:\t%s %s %s\n", file, prefix, name, def); nerrors++; } + free(name); + free(def); } + free(file); } // replace all "". with pkg. @@ -264,7 +267,7 @@ expandpkg(char *t0, char *pkg) n++; if(n == 0) - return t0; + return strdup(t0); // use malloc, not mal, so that caller can free w0 = malloc(strlen(t0) + strlen(pkg)*n); diff --git a/src/cmd/ld/ldelf.c b/src/cmd/ld/ldelf.c index 924687867..bd4f3e7d8 100644 --- a/src/cmd/ld/ldelf.c +++ b/src/cmd/ld/ldelf.c @@ -538,6 +538,7 @@ ldelf(Biobuf *f, char *pkg, int64 len, char *pn) s->np = sect->size; } s->size = sect->size; + s->align = sect->align; if(s->type == STEXT) { if(etextp) etextp->next = s; diff --git a/src/cmd/ld/ldpe.c b/src/cmd/ld/ldpe.c index 680557075..feb8620bd 100644 --- a/src/cmd/ld/ldpe.c +++ b/src/cmd/ld/ldpe.c @@ -282,8 +282,10 @@ ldpe(Biobuf *f, char *pkg, int64 len, char *pn) diag("%s: unknown relocation type %d;", pn, type); case IMAGE_REL_I386_REL32: case IMAGE_REL_AMD64_REL32: + case IMAGE_REL_AMD64_ADDR32: // R_X86_64_PC32 + case IMAGE_REL_AMD64_ADDR32NB: rp->type = D_PCREL; - rp->add = 0; + rp->add = le32(rsect->base+rp->off); break; case IMAGE_REL_I386_DIR32NB: case IMAGE_REL_I386_DIR32: @@ -291,10 +293,6 @@ ldpe(Biobuf *f, char *pkg, int64 len, char *pn) // load addend from image rp->add = le32(rsect->base+rp->off); break; - case IMAGE_REL_AMD64_ADDR32: // R_X86_64_PC32 - rp->type = D_PCREL; - rp->add += 4; - break; case IMAGE_REL_AMD64_ADDR64: // R_X86_64_64 rp->siz = 8; rp->type = D_ADDR; @@ -408,13 +406,15 @@ readsym(PeObj *obj, int i, PeSym **y) sym = &obj->pesym[i]; *y = sym; - name = sym->name; - if(sym->sclass == IMAGE_SYM_CLASS_STATIC && sym->value == 0) // section + if(sym->name[0] == '.') // .section name = obj->sect[sym->sectnum-1].sym->name; - if(strncmp(sym->name, "__imp__", 7) == 0) - name = &sym->name[7]; // __imp__Name => Name - else if(sym->name[0] == '_') - name = &sym->name[1]; // _Name => Name + else { + name = sym->name; + if(strncmp(name, "__imp_", 6) == 0) + name = &name[6]; // __imp_Name => Name + if(thechar == '8' && name[0] == '_') + name = &name[1]; // _Name => Name + } // remove last @XXX p = strchr(name, '@'); if(p) @@ -443,8 +443,8 @@ readsym(PeObj *obj, int i, PeSym **y) if(s != nil && s->type == 0 && !(sym->sclass == IMAGE_SYM_CLASS_STATIC && sym->value == 0)) s->type = SXREF; - if(strncmp(sym->name, "__imp__", 7) == 0) - s->got = -2; // flag for __imp__ + if(strncmp(sym->name, "__imp_", 6) == 0) + s->got = -2; // flag for __imp_ sym->sym = s; return 0; diff --git a/src/cmd/ld/lib.c b/src/cmd/ld/lib.c index 37379e186..34440b875 100644 --- a/src/cmd/ld/lib.c +++ b/src/cmd/ld/lib.c @@ -46,6 +46,7 @@ static int cout = -1; char* goroot; char* goarch; char* goos; +char* theline; void Lflag(char *arg) @@ -70,7 +71,12 @@ libinit(void) // add goroot to the end of the libdir list. libdir[nlibdir++] = smprint("%s/pkg/%s_%s", goroot, goos, goarch); + // Unix doesn't like it when we write to a running (or, sometimes, + // recently run) binary, so remove the output file before writing it. + // On Windows 7, remove() can force the following create() to fail. +#ifndef _WIN32 remove(outfile); +#endif cout = create(outfile, 1, 0775); if(cout < 0) { diag("cannot create %s", outfile); @@ -109,7 +115,7 @@ addlib(char *src, char *obj) sprint(name, ""); i = 1; } else - if(isalpha(histfrog[0]->name[1]) && histfrog[0]->name[2] == ':') { + if(isalpha((uchar)histfrog[0]->name[1]) && histfrog[0]->name[2] == ':') { strcpy(name, histfrog[0]->name+1); i = 1; } else @@ -268,6 +274,7 @@ loadlib(void) for(i=0; i<libraryp; i++) { if(debug['v']) Bprint(&bso, "%5.2f autolib: %s (from %s)\n", cputime(), library[i].file, library[i].objref); + iscgo |= strcmp(library[i].pkg, "runtime/cgo") == 0; objfile(library[i].file, library[i].pkg); } @@ -307,15 +314,9 @@ nextar(Biobuf *bp, int off, struct ar_hdr *a) return 0; return -1; } - if(r == SAR_HDR) { - memmove(a, buf, SAR_HDR); - } else if (r == SAR_HDR-SARNAME+16) { // old Plan 9 - memset(a->name, ' ', sizeof a->name); - memmove(a, buf, 16); - memmove((char*)a+SARNAME, buf+16, SAR_HDR-SARNAME); - } else { // unexpected + if(r != SAR_HDR) return -1; - } + memmove(a, buf, SAR_HDR); if(strncmp(a->fmag, ARFMAG, sizeof a->fmag)) return -1; arsize = strtol(a->size, 0, 0); @@ -350,6 +351,7 @@ objfile(char *file, char *pkg) Bseek(f, 0L, 0); ldobj(f, pkg, l, file, FileObj); Bterm(f); + free(pkg); return; } @@ -411,6 +413,7 @@ objfile(char *file, char *pkg) out: Bterm(f); + free(pkg); } void @@ -438,14 +441,17 @@ ldobj(Biobuf *f, char *pkg, int64 len, char *pn, int whence) magic = c1<<24 | c2<<16 | c3<<8 | c4; if(magic == 0x7f454c46) { // \x7F E L F ldelf(f, pkg, len, pn); + free(pn); return; } if((magic&~1) == 0xfeedface || (magic&~0x01000000) == 0xcefaedfe) { ldmacho(f, pkg, len, pn); + free(pn); return; } if(c1 == 0x4c && c2 == 0x01 || c1 == 0x64 && c2 == 0x86) { ldpe(f, pkg, len, pn); + free(pn); return; } @@ -471,14 +477,36 @@ ldobj(Biobuf *f, char *pkg, int64 len, char *pn, int whence) return; } diag("%s: not an object file", pn); + free(pn); return; } - t = smprint("%s %s %s", getgoos(), thestring, getgoversion()); - if(strcmp(line+10, t) != 0 && !debug['f']) { + + // First, check that the basic goos, string, and version match. + t = smprint("%s %s %s ", goos, thestring, getgoversion()); + line[n] = ' '; + if(strncmp(line+10, t, strlen(t)) != 0 && !debug['f']) { + line[n] = '\0'; diag("%s: object is [%s] expected [%s]", pn, line+10, t); free(t); + free(pn); return; } + + // Second, check that longer lines match each other exactly, + // so that the Go compiler and write additional information + // that must be the same from run to run. + line[n] = '\0'; + if(n-10 > strlen(t)) { + if(theline == nil) + theline = strdup(line+10); + else if(strcmp(theline, line+10) != 0) { + line[n] = '\0'; + diag("%s: object is [%s] expected [%s]", pn, line+10, theline); + free(t); + free(pn); + return; + } + } free(t); line[n] = '\n'; @@ -501,10 +529,12 @@ ldobj(Biobuf *f, char *pkg, int64 len, char *pn, int whence) Bseek(f, import1, 0); ldobj1(f, pkg, eof - Boffset(f), pn); + free(pn); return; eof: diag("truncated object file: %s", pn); + free(pn); } static Sym* @@ -884,18 +914,26 @@ unmal(void *v, uint32 n) * Convert raw string to the prefix that will be used in the symbol table. * Invalid bytes turn into %xx. Right now the only bytes that need * escaping are %, ., and ", but we escape all control characters too. + * + * Must be same as ../gc/subr.c:/^pathtoprefix. */ static char* pathtoprefix(char *s) { static char hex[] = "0123456789abcdef"; - char *p, *r, *w; + char *p, *r, *w, *l; int n; + // find first character past the last slash, if any. + l = s; + for(r=s; *r; r++) + if(*r == '/') + l = r+1; + // check for chars that need escaping n = 0; for(r=s; *r; r++) - if(*r <= ' ' || *r == '.' || *r == '%' || *r == '"') + if(*r <= ' ' || (*r == '.' && r >= l) || *r == '%' || *r == '"' || *r >= 0x7f) n++; // quick exit @@ -905,7 +943,7 @@ pathtoprefix(char *s) // escape p = mal((r-s)+1+2*n); for(r=s, w=p; *r; r++) { - if(*r <= ' ' || *r == '.' || *r == '%' || *r == '"') { + if(*r <= ' ' || (*r == '.' && r >= l) || *r == '%' || *r == '"' || *r >= 0x7f) { *w++ = '%'; *w++ = hex[(*r>>4)&0xF]; *w++ = hex[*r&0xF]; diff --git a/src/cmd/ld/lib.h b/src/cmd/ld/lib.h index d13eea31e..bbaa52d43 100644 --- a/src/cmd/ld/lib.h +++ b/src/cmd/ld/lib.h @@ -125,10 +125,12 @@ EXTERN int32 nsymbol; EXTERN char* thestring; EXTERN int ndynexp; EXTERN int havedynamic; +EXTERN int iscgo; EXTERN Segment segtext; EXTERN Segment segdata; EXTERN Segment segsym; +EXTERN Segment segdwarf; void addlib(char *src, char *obj); void addlibpath(char *srcref, char *objref, char *file, char *pkg); diff --git a/src/cmd/ld/macho.c b/src/cmd/ld/macho.c index 70133d665..6781c25a4 100644 --- a/src/cmd/ld/macho.c +++ b/src/cmd/ld/macho.c @@ -413,9 +413,9 @@ asmbmacho(void) // must match domacholink below s1 = lookup(".dynsym", 0); - s2 = lookup(".dynstr", 0); - s3 = lookup(".linkedit.plt", 0); - s4 = lookup(".linkedit.got", 0); + s2 = lookup(".linkedit.plt", 0); + s3 = lookup(".linkedit.got", 0); + s4 = lookup(".dynstr", 0); ms = newMachoSeg("__LINKEDIT", 0); ms->vaddr = va+v+rnd(segdata.len, INITRND); @@ -428,8 +428,8 @@ asmbmacho(void) ml = newMachoLoad(2, 4); /* LC_SYMTAB */ ml->data[0] = linkoff; /* symoff */ ml->data[1] = s1->size / (macho64 ? 16 : 12); /* nsyms */ - ml->data[2] = linkoff + s1->size; /* stroff */ - ml->data[3] = s2->size; /* strsize */ + ml->data[2] = linkoff + s1->size + s2->size + s3->size; /* stroff */ + ml->data[3] = s4->size; /* strsize */ ml = newMachoLoad(11, 18); /* LC_DYSYMTAB */ ml->data[0] = 0; /* ilocalsym */ @@ -444,8 +444,8 @@ asmbmacho(void) ml->data[9] = 0; /* nmodtab */ ml->data[10] = 0; /* extrefsymoff */ ml->data[11] = 0; /* nextrefsyms */ - ml->data[12] = linkoff + s1->size + s2->size; /* indirectsymoff */ - ml->data[13] = (s3->size + s4->size) / 4; /* nindirectsyms */ + ml->data[12] = linkoff + s1->size; /* indirectsymoff */ + ml->data[13] = (s2->size + s3->size) / 4; /* nindirectsyms */ ml->data[14] = 0; /* extreloff */ ml->data[15] = 0; /* nextrel */ ml->data[16] = 0; /* locreloff */ @@ -495,17 +495,34 @@ domacholink(void) // write data that will be linkedit section s1 = lookup(".dynsym", 0); relocsym(s1); - s2 = lookup(".dynstr", 0); - s3 = lookup(".linkedit.plt", 0); - s4 = lookup(".linkedit.got", 0); - - while(s2->size%4) - adduint8(s2, 0); + s2 = lookup(".linkedit.plt", 0); + s3 = lookup(".linkedit.got", 0); + s4 = lookup(".dynstr", 0); + + // Force the linkedit section to end on a 16-byte + // boundary. This allows pure (non-cgo) Go binaries + // to be code signed correctly. + // + // Apple's codesign_allocate (a helper utility for + // the codesign utility) can do this fine itself if + // it is run on a dynamic Mach-O binary. However, + // when it is run on a pure (non-cgo) Go binary, where + // the linkedit section is mostly empty, it fails to + // account for the extra padding that it itself adds + // when adding the LC_CODE_SIGNATURE load command + // (which must be aligned on a 16-byte boundary). + // + // By forcing the linkedit section to end on a 16-byte + // boundary, codesign_allocate will not need to apply + // any alignment padding itself, working around the + // issue. + while(s4->size%16) + adduint8(s4, 0); size = s1->size + s2->size + s3->size + s4->size; if(size > 0) { - linkoff = rnd(HEADR+segtext.len, INITRND) + rnd(segdata.filelen, INITRND); + linkoff = rnd(HEADR+segtext.len, INITRND) + rnd(segdata.filelen, INITRND) + rnd(segdwarf.filelen, INITRND); cseek(linkoff); cwrite(s1->p, s1->size); diff --git a/src/cmd/ld/pe.c b/src/cmd/ld/pe.c index df6c95976..1d70b4808 100644 --- a/src/cmd/ld/pe.c +++ b/src/cmd/ld/pe.c @@ -620,7 +620,7 @@ asmbpe(void) set(Magic, 0x10b); // PE32 oh.BaseOfData = d->VirtualAddress; } - set(MajorLinkerVersion, 1); + set(MajorLinkerVersion, 3); set(MinorLinkerVersion, 0); set(SizeOfCode, t->SizeOfRawData); set(SizeOfInitializedData, d->SizeOfRawData); @@ -650,8 +650,21 @@ asmbpe(void) // Commit size must be strictly less than reserve // size otherwise reserve will be rounded up to a // larger size, as verified with VMMap. - set(SizeOfStackReserve, 0x00010000); - set(SizeOfStackCommit, 0x0000ffff); + + // Go code would be OK with 64k stacks, but we need larger stacks for cgo. + // That default stack reserve size affects only the main thread, + // for other threads we specify stack size in runtime explicitly + // (runtime knows whether cgo is enabled or not). + // If you change stack reserve sizes here, + // change them in runtime/cgo/windows_386/amd64.c as well. + if(!iscgo) { + set(SizeOfStackReserve, 0x00010000); + set(SizeOfStackCommit, 0x0000ffff); + } else { + set(SizeOfStackReserve, pe64 ? 0x00200000 : 0x00100000); + // account for 2 guard pages + set(SizeOfStackCommit, (pe64 ? 0x00200000 : 0x00100000) - 0x2000); + } set(SizeOfHeapReserve, 0x00100000); set(SizeOfHeapCommit, 0x00001000); set(NumberOfRvaAndSizes, 16); diff --git a/src/cmd/prof/Makefile b/src/cmd/prof/Makefile index 6cefceb8e..292a6482a 100644 --- a/src/cmd/prof/Makefile +++ b/src/cmd/prof/Makefile @@ -25,6 +25,7 @@ endif install: install-$(NAME) install-pprof install-linux: install-default install-freebsd: install-default +install-netbsd: install-default install-openbsd: install-default install-windows: install-default diff --git a/src/cmd/prof/gopprof b/src/cmd/prof/gopprof index 83438b7cd..49052ac06 100755 --- a/src/cmd/prof/gopprof +++ b/src/cmd/prof/gopprof @@ -2615,7 +2615,6 @@ sub RemoveUninterestingFrames { 'mal', 'runtime.new', 'makeslice1', - 'runtime.gostringsize', 'runtime.malloc', 'unsafe.New', 'runtime.mallocgc', |